import type { Page } from "@playwright/test"; import { expect, test } from "../helpers/serve-dist"; interface FocusGeometry { readonly description: string; readonly focusVisible: boolean; readonly insideSafeBounds: boolean; readonly overlapArea: number; } async function currentFocusGeometry(page: Page): Promise { return page.evaluate(() => { const active = document.activeElement; if (!(active instanceof HTMLElement)) { throw new Error( "Keyboard traversal did not leave an HTMLElement focused.", ); } const rect = active.getBoundingClientRect(); const style = getComputedStyle(active); const viewport = window.visualViewport; const left = viewport?.offsetLeft ?? 0; const top = viewport?.offsetTop ?? 0; const right = left + (viewport?.width ?? window.innerWidth); const bottom = top + (viewport?.height ?? window.innerHeight); const outlineWidth = Number.parseFloat(style.outlineWidth) || 0; const outlineOffset = Number.parseFloat(style.outlineOffset) || 0; const hasInsetIndicator = /\binset\b/iu.test(style.boxShadow); const indicatorExtent = hasInsetIndicator ? 0 : Math.max(0, outlineWidth + outlineOffset); const fixedSurfaces = [ ...document.querySelectorAll( ".journey-hud, .drive-controls", ), ].filter((surface) => !surface.contains(active)); const overlapArea = fixedSurfaces.reduce((total, surface) => { const fixed = surface.getBoundingClientRect(); const width = Math.max( 0, Math.min(rect.right, fixed.right) - Math.max(rect.left, fixed.left), ); const height = Math.max( 0, Math.min(rect.bottom, fixed.bottom) - Math.max(rect.top, fixed.top), ); return total + width * height; }, 0); return { description: active.getAttribute("href") ?? active.getAttribute("aria-label") ?? active.textContent?.trim() ?? active.tagName, focusVisible: ((style.outlineStyle !== "none" && outlineWidth >= 2) || hasInsetIndicator) && rect.width > 0 && rect.height > 0, insideSafeBounds: rect.left - indicatorExtent >= left - 1 && rect.right + indicatorExtent <= right + 1 && rect.top - indicatorExtent >= top - 1 && rect.bottom + indicatorExtent <= bottom + 1, overlapArea, }; }); } test("every enabled author pointer target is 44 by 44 CSS pixels or a valid inline sentence link", async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: "domcontentloaded" }); const failures = await page .locator("a[href], button, input, select, textarea") .evaluateAll((targets) => targets.flatMap((target) => { if (!(target instanceof HTMLElement)) return []; if ( (target instanceof HTMLButtonElement || target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) && target.disabled ) return []; if (target.getAttribute("aria-disabled") === "true") return []; const style = getComputedStyle(target); const rect = target.getBoundingClientRect(); if ( style.display === "none" || style.visibility === "hidden" || rect.width === 0 || rect.height === 0 ) return []; const exception = target.getAttribute("data-target-size-exception"); const validInlineException = exception === "inline-link" && target instanceof HTMLAnchorElement && target.closest("p") !== null; const invalidException = exception !== null && !validInlineException; if ( !invalidException && (validInlineException || (rect.width >= 44 && rect.height >= 44)) ) { return []; } return [ { target: target.outerHTML.slice(0, 180), width: rect.width, height: rect.height, exception, }, ]; }), ); expect(failures).toEqual([]); }); test("keyboard traversal reaches Contact in document order with visible unobscured focus", async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: "domcontentloaded" }); const visited: string[] = []; for (let index = 0; index < 64; index += 1) { await page.keyboard.press("Tab"); await expect .poll(async () => { const geometry = await currentFocusGeometry(page); return { focusVisible: geometry.focusVisible, insideSafeBounds: geometry.insideSafeBounds, overlapArea: geometry.overlapArea, }; }) .toEqual({ focusVisible: true, insideSafeBounds: true, overlapArea: 0, }); const geometry = await currentFocusGeometry(page); visited.push(geometry.description); if (geometry.description.startsWith("mailto:")) break; } expect(visited.at(0)).toBe("#portfolio-content"); expect(visited.slice(1, 9)).toEqual([ "#start", "#profile", "#experience", "#infrastructure", "#selected-builds", "#additional-work", "#toolkit", "#contact", ]); expect(visited.some((description) => description.startsWith("mailto:"))).toBe( true, ); }); test("explicit keyboard navigation focuses a section heading without a fixed-surface collision", async ({ page, distUrl, }) => { await page.goto(distUrl, { waitUntil: "domcontentloaded" }); for (let index = 0; index < 9; index += 1) await page.keyboard.press("Tab"); await expect(page.locator('nav a[href="#contact"]')).toBeFocused(); await page.keyboard.press("Enter"); await expect(page.locator("#contact-heading")).toBeFocused(); const geometry = await currentFocusGeometry(page); expect(geometry.focusVisible).toBe(true); expect(geometry.insideSafeBounds).toBe(true); expect(geometry.overlapArea).toBe(0); }); test("responsive navigation exposes the exact visible label as each link's accessible name", async ({ page, distUrl, }) => { await page.setViewportSize({ width: 1440, height: 900 }); await page.goto(distUrl, { waitUntil: "domcontentloaded" }); await expect(page.getByRole("link", { name: "Career", exact: true })).toBeVisible(); await expect(page.getByRole("link", { name: "More work", exact: true })).toBeVisible(); await expect(page.getByRole("link", { name: "Experience", exact: true })).toHaveCount(0); await expect(page.getByRole("link", { name: "Additional work", exact: true })).toHaveCount(0); await page.setViewportSize({ width: 320, height: 800 }); const career = page.getByRole("link", { name: "Career", exact: true }); await expect(career).toHaveText("Career"); await expect(page.getByRole("link", { name: "More work", exact: true })).toHaveText("More work"); await expect(page.getByRole("link", { name: "Experience", exact: true })).toHaveCount(0); await expect(page.getByRole("link", { name: "Additional work", exact: true })).toHaveCount(0); await career.focus(); await page.keyboard.press("Enter"); await expect(page.locator("#experience-heading")).toBeFocused(); });