(null);
const isDraggingQuest = ref(false);
@@ -262,6 +292,7 @@ async function saveExternalAppConfiguration() {
await Promise.all([
workspacesClient.updateWorkspace(workspace.id, {
externalAppAccess: workspace.externalAppAccess,
+ overrideConflicts: workspaceOverrideConflicts.value,
}),
workspacesClient.saveLongFormQuestSettings(workspace.id, {
type,
diff --git a/types/workspaces.ts b/types/workspaces.ts
index 6b9cc3a..b36dfe4 100644
--- a/types/workspaces.ts
+++ b/types/workspaces.ts
@@ -48,6 +48,7 @@ export interface Workspace {
externalAppAccess: WorkspaceAppAccess;
kartaViewToken?: string;
autoFlagReview?: boolean;
+ overrideConflicts?: boolean;
role?: WorkspaceRole;
center?: WorkspaceCenter;
projectsCount?: number | null;
@@ -72,6 +73,7 @@ export interface WorkspacePatch {
description?: string;
externalAppAccess?: WorkspaceAppAccess;
autoFlagReview?: boolean;
+ overrideConflicts?: boolean;
}
export type QuestSettingsType = 'NONE' | 'JSON' | 'URL';
From 826954ada156f66731f492f1abd973a905a93260 Mon Sep 17 00:00:00 2001
From: Rajesh Kantipudi <44539669+iamrajeshk@users.noreply.github.com>
Date: Mon, 31 Aug 2026 14:04:14 +0530
Subject: [PATCH 2/2] Add tests for conflict resolution options in workspace
settings
---
components/settings/panel/Apps.vue | 2 +-
pages/workspace/[id]/settings.vue | 2 +
test/e2e/settings.spec.ts | 65 +++++++++++++++++++++++++++---
3 files changed, 62 insertions(+), 7 deletions(-)
diff --git a/components/settings/panel/Apps.vue b/components/settings/panel/Apps.vue
index 9a8a7d0..a82b831 100644
--- a/components/settings/panel/Apps.vue
+++ b/components/settings/panel/Apps.vue
@@ -31,7 +31,7 @@
Publish this workspace for external apps
-
+
During conflicts
diff --git a/pages/workspace/[id]/settings.vue b/pages/workspace/[id]/settings.vue
index e00b52d..0bbe9d7 100644
--- a/pages/workspace/[id]/settings.vue
+++ b/pages/workspace/[id]/settings.vue
@@ -6,6 +6,8 @@
// confirmation and sends the proper API call to the server (Swagger here: https://new-api.workspaces-stage.sidewalks.washington.edu/openapi.json)
// @test e2e: Under "External Apps", turning on "Publish this workspace" enables the other buttons and when clicking "Save" shows a confirmation and sends
// the proper API call.
+// @test e2e: Under "External Apps" > "During conflicts", choosing "Override" (vs. the default "Resolve") and clicking "Save" sends the proper
+// API call with the chosen overrideConflicts value.
// @test e2e: While External Apps settings are being saved, the Save button is disabled and displays "Saving..." to prevent duplicate submissions.
// @test e2e: the "Custom Imagery" box is validated against the JSON schema here (https://raw.githubusercontent.com/TaskarCenterAtUW/asr-imagery-list/refs/heads/main/schema/schema.json),
// and a toast shown when it passes and the API call to set its value is successful on the backend.
diff --git a/test/e2e/settings.spec.ts b/test/e2e/settings.spec.ts
index d72de8d..46abcd4 100644
--- a/test/e2e/settings.spec.ts
+++ b/test/e2e/settings.spec.ts
@@ -177,7 +177,9 @@ test.describe('workspace settings', () => {
if (req.method() === 'GET') {
// Apps.vue binds the checkbox with :true-value="1"; seed externalAppAccess
// as 1 (published) so it renders checked, matching the true-value.
- return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 1 } });
+ // overrideConflicts seeded true; this test doesn't touch the "During
+ // conflicts" radios, so the value should pass through unchanged.
+ return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 1, overrideConflicts: true } });
}
if (req.method() === 'PATCH') {
patchBody = JSON.parse(req.postData() ?? '{}');
@@ -209,8 +211,9 @@ test.describe('workspace settings', () => {
// Outline: a confirmation is shown (the page renders a success toast).
await expect(successToast(page)).toBeVisible();
- // Proper API call: PATCH workspaces/1 with externalAppAccess set to 0 (off).
- expect(patchBody).toEqual({ externalAppAccess: 0 });
+ // Proper API call: PATCH workspaces/1 with externalAppAccess set to 0 (off),
+ // plus the unchanged overrideConflicts value seeded above.
+ expect(patchBody).toEqual({ externalAppAccess: 0, overrideConflicts: true });
});
// @test e2e: While External Apps settings are being saved, the Save button is
@@ -262,7 +265,9 @@ test.describe('workspace settings', () => {
await page.route('**/workspaces/1', (route) => {
const req = route.request();
if (req.method() === 'GET') {
- return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 0 } });
+ // overrideConflicts seeded false; this test doesn't touch the "During
+ // conflicts" radios, so the value should pass through unchanged.
+ return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 0, overrideConflicts: false } });
}
if (req.method() === 'PATCH') {
patchBody = JSON.parse(req.postData() ?? '{}');
@@ -298,8 +303,56 @@ test.describe('workspace settings', () => {
await apps.getByRole('button', { name: 'Save' }).click();
await expect(successToast(page)).toBeVisible();
- // Proper API call: externalAppAccess turned on (enabled === 1).
- expect(patchBody).toEqual({ externalAppAccess: 1 });
+ // Proper API call: externalAppAccess turned on (enabled === 1), plus the
+ // unchanged overrideConflicts value seeded above.
+ expect(patchBody).toEqual({ externalAppAccess: 1, overrideConflicts: false });
+ });
+
+ // @test e2e: Under "External Apps" > "During conflicts", choosing "Override" (vs. the default
+ // "Resolve") and clicking "Save" sends the proper API call with the chosen value.
+ test('choosing "Override" under During Conflicts and saving sends overrideConflicts: true', async ({ page }) => {
+ await seedAuthenticatedSession(page);
+ let patchBody: unknown = null;
+ // Published, with overrideConflicts seeded false so "Resolve" starts selected.
+ await page.route('**/workspaces/1', (route) => {
+ const req = route.request();
+ if (req.method() === 'GET') {
+ return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 1, overrideConflicts: false } });
+ }
+ if (req.method() === 'PATCH') {
+ patchBody = JSON.parse(req.postData() ?? '{}');
+ return route.fulfill({ status: 204, body: '' });
+ }
+ return route.fallback();
+ });
+ await page.route('**/workspaces/1/quests/long/settings', (route) => {
+ if (route.request().method() === 'GET') {
+ return route.fulfill({ json: emptyQuestSettings });
+ }
+ return route.fulfill({ status: 204, body: '' });
+ });
+ await page.route('**/workspaces/1/imagery/settings', route =>
+ route.fulfill({ json: emptyImagerySettings })
+ );
+ await page.route(/(?:imagery-schema|schema)\.json$/, route =>
+ route.fulfill({ json: imagerySchema })
+ );
+
+ await page.goto('/workspace/1/settings');
+
+ const apps = page.locator('form.card', { hasText: 'External Apps' });
+ const resolve = apps.getByLabel('Resolve');
+ const override = apps.getByLabel('Override');
+ // Seeded overrideConflicts: false, so "Resolve" starts selected.
+ await expect(resolve).toBeChecked();
+ await expect(override).not.toBeChecked();
+
+ await override.check();
+ await apps.getByRole('button', { name: 'Save' }).click();
+
+ await expect(successToast(page)).toBeVisible();
+ // Proper API call: PATCH workspaces/1 with the chosen overrideConflicts value.
+ expect(patchBody).toEqual({ externalAppAccess: 1, overrideConflicts: true });
});
// @test e2e: the "Custom Imagery" box is validated against the JSON schema, and a toast shown