diff --git a/docs/src/window-regression-fixtures.tsx b/docs/src/window-regression-fixtures.tsx index 34d188a..93155d2 100644 --- a/docs/src/window-regression-fixtures.tsx +++ b/docs/src/window-regression-fixtures.tsx @@ -29,7 +29,7 @@ function RegressionCase({ children, label, state, width }: RegressionCaseProps) style={style} >
- {width}px container + Up to {width}px {label}
{children} @@ -202,7 +202,7 @@ export function WindowRegressionFixtures() { - + 2 items @@ -210,7 +210,7 @@ export function WindowRegressionFixtures() { - + Wide layout diff --git a/tests/browser/docs-geometry.spec.ts b/tests/browser/docs-geometry.spec.ts index 033f818..3ee80d6 100644 --- a/tests/browser/docs-geometry.spec.ts +++ b/tests/browser/docs-geometry.spec.ts @@ -8,7 +8,7 @@ const viewports = [ ] as const; for (const viewport of viewports) { - test(`window fixtures stay contained at ${viewport.width}px`, async ({ page }) => { + test(`window fixtures stay contained at ${viewport.width}px`, async ({ page }, testInfo) => { await page.setViewportSize(viewport); await page.goto("/"); @@ -58,6 +58,10 @@ for (const viewport of viewports) { return messages; }); + await suite.locator("[data-regression-width='760']").screenshot({ + path: testInfo.outputPath("application-window.png"), + }); + expect(await headerTextOverlaps(page)).toEqual([]); expect(failures).toEqual([]); const windowSurfaceFailures = await page.evaluate(() => { @@ -306,3 +310,45 @@ async function expectViewportContainment(page: Page, popup: Locator) { expect(box!.x + box!.width).toBeLessThanOrEqual(viewport!.width); expect(box!.y + box!.height).toBeLessThanOrEqual(viewport!.height); } + +test("window headers adapt across responsive breakpoints", async ({ page }) => { + await page.goto("/"); + for (const width of [ + 360, 375, 393, 414, 428, 519, 520, 521, 619, 620, 621, 767, 769, 820, 1024, 1440, + ]) { + // Resize sequentially to exercise transitions in the same mounted components. + // eslint-disable-next-line no-await-in-loop + await page.setViewportSize({ width, height: 900 }); + // eslint-disable-next-line no-await-in-loop + expect(await headerTextOverlaps(page), `Header overlap at ${width}px`).toEqual([]); + // eslint-disable-next-line no-await-in-loop + expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual( + width, + ); + } +}); + +async function headerTextOverlaps(page: Page) { + return page.locator(".greyui-window-header-layout").evaluateAll((headers) => + headers.flatMap((header) => { + const description = header.querySelector(".greyui-window-description"); + const actions = header.querySelector(".greyui-window-actions"); + if (!description || !actions || !description.getClientRects().length) return []; + const bounds = actions.getBoundingClientRect(); + const range = document.createRange(); + range.selectNodeContents(description); + const overlaps = [...range.getClientRects()].some( + (line) => + line.left < bounds.right && + line.right > bounds.left && + line.top < bounds.bottom && + line.bottom > bounds.top, + ); + return overlaps + ? [ + `Description overlaps actions in ${header.closest(".greyui-window")?.querySelector(".greyui-window-title")?.textContent}`, + ] + : []; + }), + ); +}