import { expect, test, type Page } from "@playwright/test"; async function expectDashboardTitleHelp(page: Page) { const title = page.getByRole("heading", { level: 1, name: new URL(page.url()).searchParams.get("language") === "de" ? "Übersicht" : "Dashboard", exact: true }); await expect(title).toBeVisible(); const anchor = title.locator(".."); const help = anchor.getByRole("link"); await expect(help).toBeVisible(); await expect(title.locator("a")).toHaveCount(0); const headingBounds = await title.boundingBox(); const helpBounds = await help.boundingBox(); expect(helpBounds!.x - headingBounds!.x - headingBounds!.width).toBeGreaterThanOrEqual(4); expect(helpBounds!.x - headingBounds!.x - headingBounds!.width).toBeLessThanOrEqual(8); await expect(page.locator('[data-page-action-slot="help"], .page-action-bar-trailing .documentation-help-link')).toHaveCount(0); } async function dashboardFixture(page: Page) { const writes: string[] = []; const errors: string[] = []; page.on("pageerror", error => errors.push(error.message)); await page.route(url => url.pathname.startsWith("/api/"), route => { if (route.request().method() !== "GET") writes.push(new URL(route.request().url()).pathname); return route.fulfill({ json: { exists: false, view_id: null, layout_version: 1, revision: 0, placements: [], known_widget_ids: [], updated_at: null } }); }); return { writes, errors }; } for (const language of ["en", "de"]) { test(`Dashboard Cancel exits a clean configuration without saving (${language})`, async ({ page }) => { const fixture = await dashboardFixture(page); await page.goto(`/?dashboard-configuration&language=${language}`); await expectDashboardTitleHelp(page); const configure = page.locator('[data-page-action-archetype="overview"] [data-page-action-slot="primary"] button'); for (let attempt = 0; attempt < 2; attempt += 1) { await expect(configure).toBeEnabled(); await configure.click(); await expectDashboardTitleHelp(page); const editor = page.locator('[data-page-action-archetype="editor"]'); await expect(editor.locator('[data-page-action-slot="save"] button')).toBeDisabled(); const cancel = editor.locator('[data-page-action-slot="discard"] button'); await expect(cancel).toBeEnabled(); await expect(cancel).toHaveText(language === "de" ? "Abbrechen" : "Cancel"); await cancel.click(); await expect(configure).toBeVisible(); await expectDashboardTitleHelp(page); await expect(page.getByRole("alertdialog")).toHaveCount(0); } expect(fixture.writes).toEqual([]); expect(fixture.errors).toEqual([]); }); } test("Dashboard Cancel confirms dirty drafts and never saves when discarding", async ({ page }) => { const fixture = await dashboardFixture(page); await page.goto("/?dashboard-configuration&language=en"); const configure = page.locator('[data-page-action-archetype="overview"] [data-page-action-slot="primary"] button'); await configure.click(); await page.getByRole("button", { name: "Remove Active interface modules", exact: true }).click(); await expectDashboardTitleHelp(page); const editor = page.locator('[data-page-action-archetype="editor"]'); await expect(editor.locator('[data-page-action-slot="save"] button')).toBeEnabled(); const cancel = editor.locator('[data-page-action-slot="discard"] button'); await cancel.click(); const confirmation = page.getByRole("alertdialog"); await expect(confirmation).toBeVisible(); await confirmation.getByRole("button", { name: "Cancel", exact: true }).click(); await expect(editor).toBeVisible(); await expect(editor.locator('[data-page-action-slot="save"] button')).toBeEnabled(); await cancel.click(); await confirmation.getByRole("button", { name: "Discard", exact: true }).click(); await expect(configure).toBeVisible(); await expect(page.getByRole("heading", { name: "Active interface modules", exact: true })).toBeVisible(); await configure.click(); await expect(editor.locator('[data-page-action-slot="save"] button')).toBeDisabled(); await expect(cancel).toBeEnabled(); expect(fixture.writes).toEqual([]); expect(fixture.errors).toEqual([]); }); test("Dashboard Cancel can save and leave without restoring the previous draft", async ({ page }) => { const fixture = await dashboardFixture(page); let releaseSave!: () => void; const savePending = new Promise(resolve => { releaseSave = resolve; }); let saved: Record | null = null; const updates: Record[] = []; await page.route("**/api/v1/dashboard/layout*", async route => { if (route.request().method() === "PUT") { const payload = route.request().postDataJSON(); updates.push(payload); await savePending; saved = { ...payload, exists: true, view_id: null, revision: 1, updated_at: "2026-09-08T12:00:00Z" }; await route.fulfill({ json: saved }); } else if (saved) { await route.fulfill({ json: saved }); } else { await route.fallback(); } }); await page.goto("/?dashboard-configuration&language=en"); const configure = page.locator('[data-page-action-archetype="overview"] [data-page-action-slot="primary"] button'); const editor = page.locator('[data-page-action-archetype="editor"]'); await configure.click(); await page.getByRole("button", { name: "Remove Active interface modules", exact: true }).click(); await editor.locator('[data-page-action-slot="discard"] button').click(); const confirmation = page.getByRole("alertdialog"); await confirmation.getByRole("button", { name: "Save and leave", exact: true }).click(); await expect.poll(() => updates.length).toBe(1); await expect(editor.locator('[data-page-action-slot="discard"] button')).toBeDisabled(); await expect(editor.locator('[data-page-action-slot="save"] button')).toBeDisabled(); await expect(confirmation.getByRole("button", { name: "Discard", exact: true })).toBeDisabled(); releaseSave(); await expect(configure).toBeVisible(); await expect(confirmation).toHaveCount(0); expect(updates[0].placements).toEqual([]); await page.reload(); await configure.click(); await expect(page.getByRole("button", { name: "Remove Active interface modules", exact: true })).toHaveCount(0); await expect(editor.locator('[data-page-action-slot="save"] button')).toBeDisabled(); await expect(editor.locator('[data-page-action-slot="discard"] button')).toBeEnabled(); expect(updates).toHaveLength(1); expect(fixture.writes).toEqual([]); expect(fixture.errors).toEqual([]); }); test("actual Scheduling widget contributes help at its Dashboard title without duplicating it", async ({ page }) => { const fixture = await dashboardFixture(page); await page.route("**/api/v1/dashboard/layout*", route => route.fulfill({ json: { exists: true, view_id: null, layout_version: 1, revision: 1, placements: [{ instance_id: "scheduling-widget", widget_id: "scheduling.open-requests", size: "medium", column_start: 1, configuration: {} }], known_widget_ids: ["dashboard.installed-modules", "scheduling.open-requests"], updated_at: "2026-09-08T12:00:00Z" } })); await page.route("**/api/v1/scheduling/requests*", route => route.fulfill({ json: { requests: [] } })); await page.goto("/?dashboard-configuration&widget-help&language=en"); await expectDashboardTitleHelp(page); const widget = page.locator(".dashboard-widget").filter({ has: page.getByRole("heading", { level: 2, name: "Scheduling requests", exact: true }) }); await expect(widget).toHaveCount(1); await expect(widget.getByRole("heading", { name: "Scheduling requests", exact: true })).toHaveCount(1); const titleHelp = widget.locator(".card-title-with-help .documentation-help-link"); await expect(titleHelp).toHaveAttribute("href", /topic=scheduling\.find-and-decide-meeting-time/); await expect(titleHelp).toBeVisible(); await expect(widget.locator(".card-body .documentation-help-link")).toHaveCount(0); await widget.getByRole("button", { name: "Show header only", exact: true }).click(); await expect(titleHelp).toBeVisible(); await page.locator('[data-page-action-archetype="overview"] [data-page-action-slot="primary"] button').click(); await expectDashboardTitleHelp(page); await expect(titleHelp).toBeVisible(); await expect(widget.locator(".card-actions .documentation-help-link")).toHaveCount(0); expect(fixture.writes).toEqual([]); expect(fixture.errors).toEqual([]); });