Show the whole nav tree without :has(), raise text contrast to 4.5:1, drop unused auth rules
CI / full-gate (push) Successful in 3m1s

This commit is contained in:
2026-10-31 01:24:34 +01:00
parent 3e70768433
commit 635006b5fc
7 changed files with 124 additions and 133 deletions
+43
View File
@@ -0,0 +1,43 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
const css = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "..", "public", "css", "styles.css"), "utf8");
const token = (name: string): string => {
const hex = new RegExp(`--${name}:\\s*(#[0-9a-f]{6})\\b`, "i").exec(css)?.[1];
assert.ok(hex, `--${name} is not a six-digit hex colour`);
return hex;
};
const luminance = (hex: string): number => {
const [r = 0, g = 0, b = 0] = [1, 3, 5].map((i) => {
const c = Number.parseInt(hex.slice(i, i + 2), 16) / 255;
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const contrast = (a: string, b: string): number => {
const [hi = 0, lo = 0] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
};
// WCAG AA for normal text.
for (const theme of ["l", "d"]) {
for (const tone of ["pos", "neg", "warn", "info"]) {
test(`${tone} badge text reaches 4.5:1 in ${theme === "l" ? "light" : "dark"} mode`, () => {
assert.ok(contrast(token(`${theme}-${tone}`), token(`${theme}-${tone}-bg`)) >= 4.5);
});
}
test(`text on the accent reaches 4.5:1 in ${theme === "l" ? "light" : "dark"} mode`, () => {
assert.ok(contrast(token(`${theme}-on-accent`), token(`${theme}-accent`)) >= 4.5);
});
}
test("the primary button and the current page button use --on-accent", () => {
assert.match(css, /\.btn-primary\s*\{[^}]*color:\s*var\(--on-accent\)/);
assert.match(css, /\.page-btn\[aria-current="page"\]\s*\{[^}]*color:\s*var\(--on-accent\)/);
});
+17
View File
@@ -13,3 +13,20 @@ for (const file of readdirSync(cssDir).filter((f) => f.endsWith(".css"))) {
assert.ok(!/oklch\(/i.test(readFileSync(join(cssDir, file), "utf8")));
});
}
const stylesheet = readFileSync(join(cssDir, "styles.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
// Below Chromium 105 nothing but `:has()` reveals a folded branch, so the fold may only exist where
// the engine can undo it.
test("styles.css hides .nav-children only inside @supports selector(:has(a))", () => {
const guard = stylesheet.indexOf("@supports selector(:has(a))");
assert.ok(guard >= 0, "no @supports selector(:has(a)) guard");
const hide = /\.nav-node\s*>\s*\.nav-children\s*\{\s*display:\s*none/.exec(stylesheet);
assert.ok(hide && hide.index > guard, "the fold rule is missing or precedes the guard");
const between = stylesheet.slice(guard, hide.index);
assert.equal(between.split("{").length - between.split("}").length, 1, "the guard closes before the fold rule");
});
test("styles.css hides the theme switch where :has() is missing", () => {
assert.match(stylesheet, /@supports not selector\(:has\(a\)\)\s*\{\s*\.theme-switch\s*\{\s*display:\s*none/);
});