import { expect, test, type Page } from "@playwright/test"; async function mockModules(page: Page) { const errors: string[] = []; const writes: string[] = []; page.on("pageerror", error => errors.push(error.message)); await page.route(url => url.pathname.startsWith("/api/"), async route => { const path = new URL(route.request().url()).pathname; if (route.request().method() !== "GET") writes.push(path); let body: unknown = {}; if (path.startsWith("/api/v1/committee/workspace/")) body = { records: [], total: 0, offset: 0, limit: 200 }; else if (path === "/api/v1/voting") body = { ballots: [] }; else if (path === "/api/v1/scheduling/requests") body = { requests: [] }; else if (path.endsWith("/source-snapshots")) body = { available: true, snapshots: [] }; else if (path.endsWith("/list-snapshots")) body = { snapshots: [] }; else if (path.endsWith("/review-queue")) body = { candidates: [] }; else if (path.endsWith("/assurance/nodes")) body = { nodes: [] }; else if (path.endsWith("/assurance/summary")) body = { node_count: 0, edge_count: 0, by_kind: {}, by_state: {} }; else if (path === "/api/v1/organizations/model") body = { unit_types: [{ id: "unit-type", name: "Department", slug: "department", is_active: true }], structures: [], relation_types: [], units: [], relations: [], function_types: [], functions: [] }; else if (path === "/api/v1/idm/typed-groups") body = { groups: [{ id: "group", tenant_id: "layout-tenant", key: "reviewers", name: "Reviewers", group_type: "team", status: "active", source_provider: "manual", properties: {}, provenance: {}, revision: 1 }] }; else if (path === "/api/v1/idm/relationships") body = { relationships: [] }; else if (path === "/api/v1/idm/organization-identities") body = { identities: [] }; return route.fulfill({ json: body }); }); return { errors, writes }; } for (const module of ["committee", "voting", "scheduling"] as const) { test(`${module} keeps Reload immediately before New at the upper right`, async ({ page }) => { const fixture = await mockModules(page); await page.setViewportSize({ width: 1280, height: 900 }); await page.goto(`/?module-layouts=${module}&language=en&theme=light`); const toolbar = page.locator('[data-workspace-action-scope="workspace"]').first(); const create = toolbar.locator('[data-page-action-slot="create"] button'); const reload = toolbar.locator('[data-page-action-slot="reload"] button'); await expect(create).toBeVisible(); await expect(reload).toBeEnabled(); const [barBox, createBox, reloadBox] = await Promise.all([toolbar.boundingBox(), create.boundingBox(), reload.boundingBox()]); expect(createBox!.x).toBeGreaterThan(barBox!.x + barBox!.width / 2); expect(Math.abs(barBox!.x + barBox!.width - createBox!.x - createBox!.width)).toBeLessThanOrEqual(20); expect(Math.abs(createBox!.y - reloadBox!.y)).toBeLessThan(3); expect(reloadBox!.x + reloadBox!.width).toBeLessThanOrEqual(createBox!.x + 1); expect(createBox!.x - reloadBox!.x - reloadBox!.width).toBeLessThan(20); expect(barBox!.y).toBeLessThan(20); expect(fixture.errors).toEqual([]); expect(fixture.writes).toEqual([]); }); } test("Scheduling retains workspace actions when its editor opens and in read-only mode", async ({ page }) => { const fixture = await mockModules(page); await page.goto("/?module-layouts=scheduling&language=en&theme=light"); const toolbar = page.locator('[data-workspace-action-scope="workspace"]'); const create = toolbar.locator('[data-page-action-slot="create"] button'); await expect(create).toBeEnabled(); await create.click(); await expect(page.locator('[data-workspace-action-scope="editor-pane"]')).toBeVisible(); await expect(create).toBeVisible(); await expect(toolbar.locator('[data-page-action-slot="reload"] button')).toBeVisible(); expect(fixture.errors).toEqual([]); expect(fixture.writes).toEqual([]); await page.goto("/?module-layouts=scheduling&language=en&read-only&theme=light"); await expect(create).toBeVisible(); await expect(create).toBeDisabled(); }); for (const module of ["organizations", "idm"] as const) { test(`${module} table cards have no inset or negative overflow`, async ({ page }) => { const fixture = await mockModules(page); await page.setViewportSize({ width: 1280, height: 900 }); await page.goto(`/?module-layouts=${module}&language=en&theme=light`); const cards = page.locator('.card:has([data-card-body-layout="table"])'); await expect(cards.first()).toBeVisible(); await expect(page.getByText(module === "idm" ? "Reviewers" : "Department", { exact: true })).toBeVisible(); const geometry = await cards.evaluateAll(elements => elements.map(card => { const body = card.querySelector('.card-body')!; const grid = card.querySelector('.data-grid')!; const cardRect = card.getBoundingClientRect(); const gridRect = grid.getBoundingClientRect(); return { left: gridRect.left - cardRect.left, right: cardRect.right - gridRect.right, padding: getComputedStyle(body).paddingLeft }; })); expect(geometry.length).toBeGreaterThanOrEqual(2); for (const item of geometry) { expect(Math.abs(item.left)).toBeLessThanOrEqual(2); expect(Math.abs(item.right)).toBeLessThanOrEqual(2); expect(item.padding).toBe("0px"); } if (module === "idm") { const [first, second] = await Promise.all([cards.nth(0).boundingBox(), cards.nth(1).boundingBox()]); expect(second!.y - first!.y - first!.height).toBeGreaterThanOrEqual(12); await expect(page.getByText("Business membership is an institutional fact.", { exact: false })).toHaveCount(0); } expect(fixture.errors).toEqual([]); expect(fixture.writes).toEqual([]); }); } for (const width of [1280, 390]) { test(`Risk Compliance uses shared cards and responsive layout at ${width}px`, async ({ page }) => { const fixture = await mockModules(page); await page.setViewportSize({ width, height: 900 }); await page.goto("/?module-layouts=risk&language=en&theme=light"); await expect(page.getByRole("heading", { name: "Review queue" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Candidate evidence" })).toBeVisible(); const toolbar = page.locator('[data-workspace-action-scope="workspace"]'); await expect(toolbar.locator('[data-page-action-slot="reload"] button')).toBeEnabled(); await page.getByRole("tab", { name: "Sources", exact: true }).click(); await expect(page.getByRole("heading", { name: "Connector evidence" })).toBeVisible(); await page.getByRole("tab", { name: "Assurance", exact: true }).click(); await expect(page.getByRole("heading", { name: "Assurance objects" })).toBeVisible(); await expect(page.locator('.risk-panel, .risk-toolbar')).toHaveCount(0); expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1)).toBe(true); expect(fixture.errors).toEqual([]); expect(fixture.writes).toEqual([]); }); }