117 lines
6.9 KiB
TypeScript
117 lines
6.9 KiB
TypeScript
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([]);
|
|
});
|
|
}
|