test(webui): cover View-focused Quick Access catalogues
This commit is contained in:
@@ -31,7 +31,7 @@ import {
|
|||||||
createQuickAccessLaunchContext,
|
createQuickAccessLaunchContext,
|
||||||
quickAccessLaunchState
|
quickAccessLaunchState
|
||||||
} from "../src/platform/launchContext";
|
} from "../src/platform/launchContext";
|
||||||
import type { ApiSettings, AuthInfo, QuickAccessToolMetadata } from "../src/types";
|
import type { ApiSettings, AuthInfo, EffectiveViewProjection, QuickAccessToolMetadata } from "../src/types";
|
||||||
|
|
||||||
export default function ConformanceApp() {
|
export default function ConformanceApp() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -172,6 +172,20 @@ export default function ConformanceApp() {
|
|||||||
|
|
||||||
function QuickAccessScenario() {
|
function QuickAccessScenario() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const mode = new URLSearchParams(location.search).get("quick-access");
|
||||||
|
const viewContext = useMemo(
|
||||||
|
() => mode === "focused" || mode === "stale-focus"
|
||||||
|
? {
|
||||||
|
...CONFORMANCE_VIEW_PROJECTION,
|
||||||
|
presentation: {
|
||||||
|
quickAccessFocusedToolIds: mode === "focused"
|
||||||
|
? ["files.recent"]
|
||||||
|
: ["mail.compose"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
[mode]
|
||||||
|
);
|
||||||
const launchContext = useMemo(() => createQuickAccessLaunchContext({
|
const launchContext = useMemo(() => createQuickAccessLaunchContext({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
search: location.search,
|
search: location.search,
|
||||||
@@ -185,8 +199,9 @@ function QuickAccessScenario() {
|
|||||||
tenantId: "tenant-1",
|
tenantId: "tenant-1",
|
||||||
label: "RPP-2026-0001 · Anwohnerparkausweis"
|
label: "RPP-2026-0001 · Anwohnerparkausweis"
|
||||||
},
|
},
|
||||||
temporalContext: { validityMode: "current", validAt: null, recordedAt: null }
|
temporalContext: { validityMode: "current", validAt: null, recordedAt: null },
|
||||||
}), [location.hash, location.pathname, location.search]);
|
viewContext
|
||||||
|
}), [location.hash, location.pathname, location.search, viewContext]);
|
||||||
return <QuickAccessRail
|
return <QuickAccessRail
|
||||||
settings={CONFORMANCE_SETTINGS}
|
settings={CONFORMANCE_SETTINGS}
|
||||||
auth={CONFORMANCE_AUTH}
|
auth={CONFORMANCE_AUTH}
|
||||||
@@ -272,8 +287,39 @@ const CONFORMANCE_QUICK_ACCESS_TOOLS: QuickAccessToolMetadata[] = [{
|
|||||||
acceptedReferenceKinds: [],
|
acceptedReferenceKinds: [],
|
||||||
returnedReferenceKinds: ["files.file-version"],
|
returnedReferenceKinds: ["files.file-version"],
|
||||||
helpContextId: "files.quick_access.files"
|
helpContextId: "files.quick_access.files"
|
||||||
|
}, {
|
||||||
|
contractVersion: "1",
|
||||||
|
id: "postbox.unread",
|
||||||
|
moduleId: "postbox",
|
||||||
|
categoryId: "messages",
|
||||||
|
label: "Unread messages",
|
||||||
|
description: "Open an authorized unread message.",
|
||||||
|
iconName: "messages",
|
||||||
|
surfaceId: "postbox.route.postbox",
|
||||||
|
fullPagePath: "/postbox",
|
||||||
|
allOf: [],
|
||||||
|
anyOf: [],
|
||||||
|
order: 10,
|
||||||
|
defaultEnabled: true,
|
||||||
|
modes: ["view"],
|
||||||
|
availability: "global",
|
||||||
|
acceptedReferenceKinds: [],
|
||||||
|
returnedReferenceKinds: ["postbox.message"],
|
||||||
|
helpContextId: "postbox.quick_access.messages"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
const CONFORMANCE_VIEW_PROJECTION: EffectiveViewProjection = {
|
||||||
|
activeViewId: "case-processing",
|
||||||
|
activeRevisionId: "case-processing-r4",
|
||||||
|
activeViewName: "Case processing",
|
||||||
|
visibleSurfaceIds: [],
|
||||||
|
projectionActive: true,
|
||||||
|
locked: false,
|
||||||
|
availableViews: [],
|
||||||
|
provenance: [{ source: "tenant-default", scopeType: "tenant", scopeId: "tenant-1" }],
|
||||||
|
diagnostics: []
|
||||||
|
};
|
||||||
|
|
||||||
function browserHistoryIndex(): number | null {
|
function browserHistoryIndex(): number | null {
|
||||||
const value = (window.history.state as { idx?: unknown } | null)?.idx;
|
const value = (window.history.state as { idx?: unknown } | null)?.idx;
|
||||||
return typeof value === "number" && Number.isInteger(value) ? value : null;
|
return typeof value === "number" && Number.isInteger(value) ? value : null;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export {
|
|||||||
dispatchQuickAccessResult,
|
dispatchQuickAccessResult,
|
||||||
quickAccessLaunchState
|
quickAccessLaunchState
|
||||||
} from "../src/platform/launchContext";
|
} from "../src/platform/launchContext";
|
||||||
|
export { i18nMessage } from "../src/i18n/LanguageContext";
|
||||||
export type {
|
export type {
|
||||||
ApiSettings,
|
ApiSettings,
|
||||||
QuickAccessRailProps,
|
QuickAccessRailProps,
|
||||||
|
|||||||
@@ -91,42 +91,7 @@ test("Quick Access preserves focus and adapts to a narrow viewport", async ({ pa
|
|||||||
await page.route("**/api/v1/quick-access/effective*", async (route) => {
|
await page.route("**/api/v1/quick-access/effective*", async (route) => {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(quickAccessPayload(false))
|
||||||
categories: [{
|
|
||||||
id: "files",
|
|
||||||
label: "Files",
|
|
||||||
description: "Files beside the current task",
|
|
||||||
icon: "files",
|
|
||||||
order: 10,
|
|
||||||
enabled: true,
|
|
||||||
forced: false,
|
|
||||||
locked_by: null,
|
|
||||||
tools: [{
|
|
||||||
contract_version: "1",
|
|
||||||
id: "files.recent",
|
|
||||||
module_id: "files",
|
|
||||||
category_id: "files",
|
|
||||||
label: "Recent files",
|
|
||||||
description: "Select an authorized file without leaving this case.",
|
|
||||||
icon: "files",
|
|
||||||
surface_id: "files.route.files",
|
|
||||||
full_page_path: "/files",
|
|
||||||
required_all: [],
|
|
||||||
required_any: [],
|
|
||||||
order: 10,
|
|
||||||
default_enabled: true,
|
|
||||||
modes: ["select"],
|
|
||||||
availability: "global",
|
|
||||||
accepted_reference_kinds: [],
|
|
||||||
returned_reference_kinds: ["files.file-version"],
|
|
||||||
help_context_id: "files.quick_access.files",
|
|
||||||
enabled: true,
|
|
||||||
forced: false,
|
|
||||||
locked_by: null
|
|
||||||
}]
|
|
||||||
}],
|
|
||||||
diagnostics: []
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
await page.setViewportSize({ width: 390, height: 844 });
|
await page.setViewportSize({ width: 390, height: 844 });
|
||||||
@@ -145,6 +110,52 @@ test("Quick Access preserves focus and adapts to a narrow viewport", async ({ pa
|
|||||||
await expect(filesTrigger).toBeFocused();
|
await expect(filesTrigger).toBeFocused();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("View focus has a deliberate permission-derived all-tools escape", async ({ page }) => {
|
||||||
|
await page.route("**/api/v1/quick-access/effective*", async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(quickAccessPayload(true))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await page.goto("/?theme=light&quick-access=focused");
|
||||||
|
|
||||||
|
const rail = page.locator(".quick-access-rail");
|
||||||
|
await expect(rail).toHaveAttribute("data-view-focus-mode", "focused");
|
||||||
|
await expect(page.getByRole("button", { name: "Files" })).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Messages" })).toHaveCount(0);
|
||||||
|
|
||||||
|
const allTools = page.locator(".quick-access-view-mode-button");
|
||||||
|
await expect(allTools).toHaveAttribute("aria-pressed", "false");
|
||||||
|
await allTools.click();
|
||||||
|
await expect(rail).toHaveAttribute("data-view-focus-mode", "all");
|
||||||
|
await expect(page.getByRole("button", { name: "Messages" })).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Messages" }).click();
|
||||||
|
const explanation = page.locator(".quick-access-view-mode[role='status']");
|
||||||
|
await expect(explanation).toHaveAttribute("data-view-focus-mode", "all");
|
||||||
|
await explanation.getByRole("button").click();
|
||||||
|
await expect(rail).toHaveAttribute("data-view-focus-mode", "focused");
|
||||||
|
await expect(page.getByRole("button", { name: "Messages" })).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a stale View focus falls back safely in a sparse optional-module catalogue", async ({ page }) => {
|
||||||
|
await page.route("**/api/v1/quick-access/effective*", async (route) => {
|
||||||
|
await route.fulfill({
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify(quickAccessPayload(false))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await page.goto("/?theme=light&quick-access=stale-focus");
|
||||||
|
|
||||||
|
const rail = page.locator(".quick-access-rail");
|
||||||
|
await expect(rail).toHaveAttribute("data-view-focus-mode", "unavailable");
|
||||||
|
await expect(page.getByRole("button", { name: "Files" })).toBeVisible();
|
||||||
|
await expect(page.locator(".quick-access-view-mode-button")).toHaveCount(0);
|
||||||
|
await page.getByRole("button", { name: "Files" }).click();
|
||||||
|
await expect(page.locator(".quick-access-view-mode[role='status']"))
|
||||||
|
.toHaveAttribute("data-view-focus-mode", "unavailable");
|
||||||
|
});
|
||||||
|
|
||||||
test("light and dark desktop geometry remains stable", async ({ page }) => {
|
test("light and dark desktop geometry remains stable", async ({ page }) => {
|
||||||
await page.setViewportSize({ width: 1440, height: 1000 });
|
await page.setViewportSize({ width: 1440, height: 1000 });
|
||||||
await page.goto("/?theme=light");
|
await page.goto("/?theme=light");
|
||||||
@@ -179,3 +190,76 @@ test("narrow layout preserves task order without horizontal overflow", async ({
|
|||||||
expect(Math.abs(actionBarRightEdge - saveRightEdge)).toBeLessThanOrEqual(1);
|
expect(Math.abs(actionBarRightEdge - saveRightEdge)).toBeLessThanOrEqual(1);
|
||||||
await expect(page.locator("[data-conformance-id='shared-ui-lab']")).toHaveScreenshot("shared-ui-light-narrow.png", { animations: "disabled", maxDiffPixelRatio: 0.005 });
|
await expect(page.locator("[data-conformance-id='shared-ui-lab']")).toHaveScreenshot("shared-ui-light-narrow.png", { animations: "disabled", maxDiffPixelRatio: 0.005 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function quickAccessPayload(includeMessages: boolean) {
|
||||||
|
const files = {
|
||||||
|
id: "files",
|
||||||
|
label: "Files",
|
||||||
|
description: "Files beside the current task",
|
||||||
|
icon: "files",
|
||||||
|
order: 10,
|
||||||
|
enabled: true,
|
||||||
|
forced: false,
|
||||||
|
locked_by: null,
|
||||||
|
tools: [{
|
||||||
|
contract_version: "1",
|
||||||
|
id: "files.recent",
|
||||||
|
module_id: "files",
|
||||||
|
category_id: "files",
|
||||||
|
label: "Recent files",
|
||||||
|
description: "Select an authorized file without leaving this case.",
|
||||||
|
icon: "files",
|
||||||
|
surface_id: "files.route.files",
|
||||||
|
full_page_path: "/files",
|
||||||
|
required_all: [],
|
||||||
|
required_any: [],
|
||||||
|
order: 10,
|
||||||
|
default_enabled: true,
|
||||||
|
modes: ["select"],
|
||||||
|
availability: "global",
|
||||||
|
accepted_reference_kinds: [],
|
||||||
|
returned_reference_kinds: ["files.file-version"],
|
||||||
|
help_context_id: "files.quick_access.files",
|
||||||
|
enabled: true,
|
||||||
|
forced: false,
|
||||||
|
locked_by: null
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const messages = {
|
||||||
|
id: "messages",
|
||||||
|
label: "Messages",
|
||||||
|
description: "Messages beside the current task",
|
||||||
|
icon: "messages",
|
||||||
|
order: 20,
|
||||||
|
enabled: true,
|
||||||
|
forced: false,
|
||||||
|
locked_by: null,
|
||||||
|
tools: [{
|
||||||
|
contract_version: "1",
|
||||||
|
id: "postbox.unread",
|
||||||
|
module_id: "postbox",
|
||||||
|
category_id: "messages",
|
||||||
|
label: "Unread messages",
|
||||||
|
description: "Open an authorized unread message.",
|
||||||
|
icon: "messages",
|
||||||
|
surface_id: "postbox.route.postbox",
|
||||||
|
full_page_path: "/postbox",
|
||||||
|
required_all: [],
|
||||||
|
required_any: [],
|
||||||
|
order: 10,
|
||||||
|
default_enabled: true,
|
||||||
|
modes: ["view"],
|
||||||
|
availability: "global",
|
||||||
|
accepted_reference_kinds: [],
|
||||||
|
returned_reference_kinds: ["postbox.message"],
|
||||||
|
help_context_id: "postbox.quick_access.messages",
|
||||||
|
enabled: true,
|
||||||
|
forced: false,
|
||||||
|
locked_by: null
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
categories: includeMessages ? [files, messages] : [files],
|
||||||
|
diagnostics: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user