import { expect, test } from "@playwright/test"; test("collapsed rail keeps visible group separators; opening settings is clean", async ({ page }) => { await page.goto("/?navigation-layout"); const rail = page.locator(".icon-rail"); await expect(rail).not.toHaveClass(/expanded/); await expect(rail.getByRole("separator")).toHaveCount(2); for (const separator of await rail.getByRole("separator").all()) { await expect(separator).toBeVisible(); expect((await separator.boundingBox())!.width).toBeGreaterThan(20); } await expect(page.getByTestId("navigation-draft")).toHaveText("null"); await expect(page.locator('[data-navigation-layout-status="inherited"]')).toContainText("standard layout groups available modules by product area"); const spacing = await page.locator(".navigation-preference-list > li").first().evaluate((row) => { const style = getComputedStyle(row); return { padding: Number.parseFloat(style.paddingLeft), gap: Number.parseFloat(style.columnGap) }; }); expect(spacing.padding).toBeGreaterThanOrEqual(8); expect(spacing.gap).toBeGreaterThanOrEqual(8); await page.getByRole("button", { name: "Expand navigation", exact: true }).click(); await expect(rail.getByText("Documents", { exact: true })).toBeVisible(); await expect(rail.getByRole("separator")).toHaveCount(0); await page.getByRole("button", { name: "Collapse navigation", exact: true }).click(); await expect(rail.getByRole("separator")).toHaveCount(2); await expect(rail.getByText("Documents", { exact: true })).toBeHidden(); }); for (const scope of ["system", "tenant", "user", "view"]) { test(`same editor supports add/remove, keyboard reorder and inheritance at ${scope} scope`, async ({ page }) => { await page.goto(`/?navigation-layout&scope=${scope}`); const list = page.getByRole("list", { name: "Navigation layout" }); const files = list.locator('[data-navigation-id="files"]'); await files.getByRole("button", { name: "Remove Files", exact: true }).click(); await expect(files).toHaveCount(0); await page.getByRole("button", { name: "Add module", exact: true }).click(); await expect(list.locator("li").last()).toHaveAttribute("data-navigation-id", "files"); const handle = files.getByRole("button", { name: "Reorder Files", exact: true }); const before = await list.locator("li").evaluateAll((rows) => rows.map((row) => row.getAttribute("data-navigation-id"))); await handle.press("Space"); await handle.press("ArrowUp"); await expect(list.locator("li").last()).not.toHaveAttribute("data-navigation-id", "files"); await handle.press("Escape"); expect(await list.locator("li").evaluateAll((rows) => rows.map((row) => row.getAttribute("data-navigation-id")))).toEqual(before); await handle.press("Space"); await handle.press("ArrowUp"); await handle.press("Enter"); await page.getByRole("button", { name: "Add separator", exact: true }).click(); const separator = list.locator("li").last(); await expect(separator).toHaveAttribute("data-navigation-kind", "separator"); await separator.getByRole("textbox").fill("Custom group"); const saved = JSON.parse(await page.getByTestId("navigation-draft").textContent() ?? "null"); expect(saved.separators.at(-1).label).toBe("Custom group"); await separator.getByRole("button", { name: "Remove Custom group", exact: true }).click(); if (scope !== "system") await expect(list.getByRole("button", { name: "Remove Dashboard", exact: true })).toBeDisabled(); await page.getByRole("button", { name: "Use inherited layout", exact: true }).click(); await expect(page.getByTestId("navigation-draft")).toHaveText("null"); }); } test("native pointer drag reorders modules and separators", async ({ page }) => { await page.goto("/?navigation-layout"); const list = page.getByRole("list", { name: "Navigation layout" }); const files = list.locator('[data-navigation-id="files"]'); await files.getByRole("button", { name: "Reorder Files", exact: true }).dragTo(list.locator("li").first(), { targetPosition: { x: 15, y: 3 } }); await expect(list.locator("li").first()).toHaveAttribute("data-navigation-id", "files"); const separator = list.locator('[data-navigation-kind="separator"]').first(); const separatorId = await separator.getAttribute("data-navigation-id"); await separator.getByRole("button", { name: /^Reorder / }).dragTo(list.locator("li").first(), { targetPosition: { x: 15, y: 3 } }); await expect(list.locator("li").first()).toHaveAttribute("data-navigation-id", separatorId!); }); test("no-op reordering stays clean and optional module positions survive other edits", async ({ page }) => { await page.goto("/?navigation-layout"); const handle = page.getByRole("button", { name: "Reorder Files", exact: true }); await handle.press("Space"); await handle.press("Enter"); await expect(page.getByTestId("navigation-draft")).toHaveText("null"); await page.goto("/?navigation-layout&unavailable"); await expect(page.locator('[data-navigation-layout-status="flat"]')).toContainText("without group headings or dividers"); await expect(page.locator(".icon-rail").getByRole("separator")).toHaveCount(0); await page.getByRole("button", { name: "Move Mail up", exact: true }).click(); const draft = JSON.parse(await page.getByTestId("navigation-draft").textContent() ?? "null"); expect(draft.order).toContain("optional.navigation.absent"); await page.getByRole("button", { name: "Use inherited layout", exact: true }).click(); await expect(page.locator(".icon-rail").getByRole("separator")).toHaveCount(2); await expect(page.getByTestId("navigation-draft")).toHaveText("null"); }); test("German narrow editor does not overflow and read-only controls cannot mutate", async ({ page }) => { await page.setViewportSize({ width: 390, height: 900 }); await page.goto("/?navigation-layout&language=de&disabled"); await expect(page.getByRole("button", { name: "Trennlinie hinzufügen", exact: true })).toBeDisabled(); expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true); await expect(page.getByTestId("navigation-draft")).toHaveText("null"); });