test: enforce accessible portfolio interaction
This commit is contained in:
104
tests/browser/accessibility-reflow.spec.ts
Normal file
104
tests/browser/accessibility-reflow.spec.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
import { 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<void> {
|
||||
const result = await page.evaluate(() => {
|
||||
const root = document.documentElement;
|
||||
const horizontalCandidates = [
|
||||
...document.querySelectorAll<HTMLElement>(
|
||||
".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<HTMLElement>(
|
||||
"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 page.locator('nav a[href="#contact"]').click();
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user