diff --git a/scripts/ci/test-ui-defect-audit.mjs b/scripts/ci/test-ui-defect-audit.mjs index 9019212..723fd64 100644 --- a/scripts/ci/test-ui-defect-audit.mjs +++ b/scripts/ci/test-ui-defect-audit.mjs @@ -95,6 +95,47 @@ const FIXTURES = {

Every feature — health tracking, digital twin, vet network, marketplace, and adoption listings — is included free with no pet limits.

`, + + // A bare wrapper anchor around a styled button. The anchor's own ink is + // never painted — the button paints the label — so measuring the anchor read + // 1:1 on surf-your-life's "Konto erstellen". The button itself passes. + wrapperAnchor: ` +
+ +
`, + + // A disabled control is dim BECAUSE it is disabled — WCAG exempts it. Its + // enabled twin with the same colors is a real finding. + disabledControl: ` +
+ + +
`, + + // A row of padded buttons in a text column: the BUTTON BOX is flush with the + // column and only the labels sit padding deeper — aoz-wohnen's hero read as + // ragged for exactly this. + buttonRow: ` +
+
+

Gemeinsam wohnen

+

Die Wohnung, auf die ihr euch einigen könnt.

+
AusprobierenAnmelden
+

Kein Konto nötig.

+
+
`, + + // The same hero with the button row genuinely off the column: the BOX edge + // returns 16px out and back, and the box edge is what must be measured. + buttonRowRagged: ` +
+
+

Gemeinsam wohnen

+

Die Wohnung, auf die ihr euch einigen könnt.

+
AusprobierenAnmelden
+

Kein Konto nötig.

+
+
`, }; function assert(cond, msg) { @@ -177,6 +218,30 @@ async function main() { ); }); + await check("ignores a wrapper anchor whose label a nested button paints", async () => { + const r = await measure(FIXTURES.wrapperAnchor); + assert(!r.contrast.some((c) => c.tag === "a"), `the wrapper must be skipped, got ${JSON.stringify(r.contrast)}`); + const btn = r.contrast.find((c) => c.tag === "button"); + assert(btn && btn.value >= btn.floor, `the painted button passes on its own fill, got ${btn && btn.value}`); + }); + + await check("exempts a disabled control but reports its enabled twin", async () => { + const r = await measure(FIXTURES.disabledControl); + assert(!r.contrast.some((c) => c.text === "Send"), "the disabled button is exempt (WCAG inactive)"); + const twin = r.contrast.find((c) => c.text === "Send twin"); + assert(twin && twin.value < twin.floor, `the enabled twin is a real finding, got ${twin && twin.value}`); + }); + + await check("measures a padded button row by its box edge, not its label", async () => { + const r = await measure(FIXTURES.buttonRow); + assert(r.ragged.length === 0, `a flush button row is aligned, got ${JSON.stringify(r.ragged)}`); + }); + + await check("still catches a button row whose box is off the column", async () => { + const r = await measure(FIXTURES.buttonRowRagged); + assert(r.ragged.length >= 1, "a 16px box offset that returns must be reported"); + }); + await check("does NOT flag centered copy as a missing hanging indent", async () => { const r = await measure(FIXTURES.centeredCopy); assert( diff --git a/scripts/ci/ui-defect-audit.mjs b/scripts/ci/ui-defect-audit.mjs index d328e94..8d9befe 100755 --- a/scripts/ci/ui-defect-audit.mjs +++ b/scripts/ci/ui-defect-audit.mjs @@ -160,6 +160,15 @@ export const MEASURE = String.raw`(() => { var el = actions[i]; var text = (el.innerText || "").trim(); if (!text) continue; + // A wrapper whose visible label is painted by a nested action measures the + // wrapper's own never-painted ink: surf-your-life's bare around a + // styled