Files
govoplan-core/webui/conformance/tests/data-grid-layout.spec.ts
T

142 lines
8.0 KiB
TypeScript

import { expect, test, type Page } from "@playwright/test";
const actionCell = (page: Page) => page.locator('.data-grid-body-cell[data-column-id="actions"]');
const header = (page: Page, id: string) => page.locator(`.data-grid-header-cell[data-column-id="${id}"]`);
const width = (page: Page, id: string) => header(page, id).evaluate((element) => element.getBoundingClientRect().width);
async function expectActionsUnclipped(page: Page) {
await expect.poll(async () => actionCell(page).evaluate((cell) => {
const bounds = cell.getBoundingClientRect();
const scroller = cell.closest(".data-grid-scroll-region")!;
const viewport = scroller.getBoundingClientRect();
return Array.from(cell.querySelectorAll("button")).every((button) => {
const rect = button.getBoundingClientRect();
return rect.width >= 35 && rect.left >= bounds.left && rect.right <= bounds.right
&& rect.top >= bounds.top && rect.bottom <= bounds.bottom
&& rect.left >= viewport.left && rect.right <= viewport.right;
});
})).toBe(true);
}
test("undersized action tracks fit real controls and stay visible through narrow horizontal scrolling", async ({ page }) => {
const errors: string[] = [];
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/?data-grid-layout");
await expect.poll(() => width(page, "actions")).toBeGreaterThanOrEqual(181);
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Narrow grid", exact: true }).click();
await expect.poll(() => width(page, "actions")).toBeLessThanOrEqual(160);
const scroller = page.locator(".data-grid-scroll-region");
await expectActionsUnclipped(page);
await scroller.evaluate((element) => { element.scrollLeft = element.scrollWidth / 2; });
await expectActionsUnclipped(page);
await scroller.evaluate((element) => { element.scrollLeft = element.scrollWidth; });
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Inspect Alpha", exact: true }).click();
await expect(page.getByTestId("clicked-action")).toHaveText("Inspect Alpha");
await scroller.focus();
await expect(scroller).toBeFocused();
// The application shell's supported viewport floor is 320px; its padded
// content area is narrower and must still contain the complete action set.
await page.setViewportSize({ width: 320, height: 720 });
await expectActionsUnclipped(page);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
expect(errors).toEqual([]);
});
test("pointer and keyboard resizing persists, cancels safely, and adapts to container resize", async ({ page }) => {
await page.goto("/?data-grid-layout");
await expectActionsUnclipped(page);
const handle = header(page, "name").getByRole("separator");
await expect(handle).toHaveAttribute("aria-orientation", "vertical");
const initial = await width(page, "name");
await handle.focus();
await handle.press("Shift+ArrowRight");
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 40, 0);
const box = (await handle.boundingBox())!;
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 2 + 70, box.y + box.height / 2, { steps: 5 });
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 110, 0);
await page.keyboard.press("Escape");
await page.mouse.up();
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 40, 0);
await page.getByRole("button", { name: "Toggle grid mount", exact: true }).click();
await page.getByRole("button", { name: "Toggle grid mount", exact: true }).click();
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 40, 0);
await page.getByRole("button", { name: "Narrow grid", exact: true }).click();
await expect.poll(() => width(page, "name")).toBeLessThan(initial + 40);
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Wide grid", exact: true }).click();
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 40, 0);
await handle.press("Enter");
await expect.poll(() => width(page, "name")).toBeCloseTo(initial, 0);
const resetBox = (await handle.boundingBox())!;
await page.mouse.move(resetBox.x + resetBox.width / 2, resetBox.y + resetBox.height / 2);
await page.mouse.down();
await page.mouse.move(resetBox.x + resetBox.width / 2 + 50, resetBox.y + resetBox.height / 2, { steps: 5 });
await page.mouse.up();
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 50, 0);
await page.getByRole("button", { name: "Toggle grid mount", exact: true }).click();
await page.getByRole("button", { name: "Toggle grid mount", exact: true }).click();
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 50, 0);
});
test("free content tracks retain explicit user resizing on remount", async ({ page }) => {
await page.goto("/?data-grid-layout&mode=free");
const handle = header(page, "name").getByRole("separator");
await expectActionsUnclipped(page);
const initial = await width(page, "name");
await handle.press("ArrowRight");
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 10, 0);
await page.getByRole("button", { name: "Toggle grid mount", exact: true }).click();
await page.getByRole("button", { name: "Toggle grid mount", exact: true }).click();
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 10, 0);
});
test("empty and changing action sets reserve the same complete slots", async ({ page }) => {
await page.goto("/?data-grid-layout");
await expectActionsUnclipped(page);
const initial = await width(page, "actions");
await page.getByRole("button", { name: "Toggle empty rows", exact: true }).click();
await expectActionsUnclipped(page);
await expect.poll(() => width(page, "actions")).toBeCloseTo(initial, 0);
await page.getByRole("button", { name: "Toggle empty rows", exact: true }).click();
await page.getByRole("button", { name: "Toggle extra action", exact: true }).click();
await expect.poll(() => width(page, "actions")).toBeGreaterThan(initial + 35);
await expectActionsUnclipped(page);
});
test("constrained resizing redistributes tracks without introducing overflow", async ({ page }) => {
await page.goto("/?data-grid-layout&mode=constrained");
await expectActionsUnclipped(page);
const initial = await width(page, "name");
await header(page, "name").getByRole("separator").press("Shift+ArrowRight");
await expect.poll(() => width(page, "name")).toBeCloseTo(initial + 40, 0);
expect(await page.locator(".data-grid-scroll-region").evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(1);
await expectActionsUnclipped(page);
});
test("explicit composite action groups include outer controls in their measured minimum", async ({ page }) => {
await page.goto("/?data-grid-layout&mode=composite");
await expect.poll(() => width(page, "actions")).toBeGreaterThanOrEqual(221);
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Narrow grid", exact: true }).click();
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Extra control", exact: true }).click();
await expect(page.getByTestId("clicked-action")).toHaveText("Extra control");
});
test("oversized explicit sticky minima release stickiness instead of obscuring every data column", async ({ page }) => {
await page.goto("/?data-grid-layout&mode=oversized");
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Narrow grid", exact: true }).click();
await expect(page.locator(".data-grid-shell")).toHaveClass(/data-grid-release-sticky/);
const name = page.locator('.data-grid-body-cell[data-column-id="name"]');
await expect(name).toBeInViewport();
await page.locator(".data-grid-scroll-region").evaluate((element) => { element.scrollLeft = element.scrollWidth; });
await expectActionsUnclipped(page);
await page.getByRole("button", { name: "Wide grid", exact: true }).click();
await expect(page.locator(".data-grid-shell")).not.toHaveClass(/data-grid-release-sticky/);
});