import type { Page } from "@playwright/test"; import { clickNavigationLink, expect, test } from "../helpers/serve-dist"; const textSpacingOverride = ` * { line-height: 1.5 !important; letter-spacing: 0.12em !important; word-spacing: 0.16em !important; } p { margin-block-end: 2em !important; } `; async function assertReflowGeometry(page: Page): Promise { const result = await page.evaluate(() => { const root = document.documentElement; const horizontalCandidates = [ ...document.querySelectorAll( ".site-header, .portfolio-main, .portfolio-section, .site-footer, .drive-controls, .journey-hud, h1, h2, h3, p, a, button, input, select, figure, img, video, table", ), ].filter((element) => { const style = getComputedStyle(element); const rect = element.getBoundingClientRect(); return ( style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0 ); }); const clippedText = [ ...document.querySelectorAll( "h1, h2, h3, p, li, dt, dd, a, button, label, figcaption, caption, th, td", ), ].filter((element) => { if (element.classList.contains("visually-hidden")) return false; const style = getComputedStyle(element); return style.overflow === "hidden" || style.textOverflow === "ellipsis"; }); const horizontalFailures = horizontalCandidates.flatMap((element) => { const rect = element.getBoundingClientRect(); if (rect.left >= -1 && rect.right <= root.clientWidth + 1) return []; return [ { element: element.outerHTML.slice(0, 140), left: rect.left, right: rect.right, clientWidth: root.clientWidth, }, ]; }); return { clientWidth: root.clientWidth, scrollWidth: root.scrollWidth, horizontalFailures, clippedText: clippedText.map((element) => element.outerHTML.slice(0, 140), ), }; }); expect(result.scrollWidth).toBeLessThanOrEqual(result.clientWidth + 1); expect(result.horizontalFailures).toEqual([]); expect(result.clippedText).toEqual([]); await clickNavigationLink(page, "#contact"); await expect(page.locator("#contact-heading")).toBeFocused(); await expect(page.locator('#contact a[href^="mailto:"]')).toBeVisible(); } test("reflows at a 320 CSS pixel mobile viewport without lost content or controls", async ({ page, distUrl, }, testInfo) => { test.skip( testInfo.project.name !== "mobile-chromium", "Mobile CSS viewport gate.", ); await page.setViewportSize({ width: 320, height: 800 }); await page.goto(distUrl, { waitUntil: "domcontentloaded" }); await assertReflowGeometry(page); }); test("reflows in desktop Chromium at the effective 320 CSS pixel width produced by 400 percent zoom", async ({ page, distUrl, }, testInfo) => { test.skip( testInfo.project.name !== "desktop-chromium", "Desktop zoom-equivalent gate.", ); await page.setViewportSize({ width: 320, height: 225 }); await page.goto(distUrl, { waitUntil: "domcontentloaded" }); await assertReflowGeometry(page); }); test("preserves content and controls with the WCAG text-spacing override", async ({ page, distUrl, }) => { await page.setViewportSize({ width: 320, height: 800 }); await page.goto(distUrl, { waitUntil: "domcontentloaded" }); await page.addStyleTag({ content: textSpacingOverride }); await assertReflowGeometry(page); });