Files
siftlode/frontend/src/lib/literalA11yText.contract.test.ts
T
peter ff80cc96fd fix(design): rating and live colour tokens, the player's menus over its notices, the info overlay on a light theme (E28)
- F16: --rating (theme-scoped gold, 4.5:1 on the page and its chip), --rating-media, --live/--live-fg,
  --media-chrome replace 14 palette literals; a lint rule refuses palette classes; the theme contract
  measures them.
- The Plex player's notices drew over its open settings/tracks popovers: notices z-base, popovers z-menu.
- The player's info overlay rendered theme tokens on its always-dark panel: on a light theme the score
  (2.6:1), collection years (3.2:1) and cast initials (1.05:1) were unreadable; onDark variants now.
- The lint-rule contract tests share one ESLint with a one-off warm-up (a cold first lint took 8 s,
  past vitest's 5 s).
2026-09-25 13:55:26 +02:00

31 lines
1.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from "vitest";
import { eslint } from "./lintProbe.testkit";
// `local/literal-a11y-text` (eslint.config.js): what a screen reader or a tooltip says is user-facing
// text, so it goes through t() like the rest. Five English `aria-label`s and a whole English panel
// survived every lane before it. A rule that matches nothing looks exactly like a clean codebase, so
// this asserts it fires.
const RULE = "local/literal-a11y-text";
async function hits(source: string): Promise<number> {
const [result] = await eslint.lintText(source, { filePath: "src/components/__probe.tsx" });
return (result?.messages ?? []).filter((m) => m.ruleId === RULE).length;
}
describe("a literal word in an attribute the user hears or sees", () => {
it("is refused, in every spelling of a string", async () => {
expect(await hits(`export const A = () => <button aria-label="remove" />;\n`)).toBe(1);
expect(await hits(`export const B = () => <button title={"Collapse"} />;\n`)).toBe(1);
expect(await hits("export const C = () => <input placeholder={`Search`} />;\n")).toBe(1);
expect(await hits(`export const D = () => <img alt="A poster" src="" />;\n`)).toBe(1);
});
it("lets through a translation, a decorative image, a symbol and a URL shape", async () => {
expect(await hits(`export const E = ({ t }) => <button aria-label={t("x.y")} />;\n`)).toBe(0);
expect(await hits(`export const F = () => <img alt="" src="" />;\n`)).toBe(0);
expect(await hits(`export const G = () => <span title="×" />;\n`)).toBe(0);
expect(await hits(`export const H = () => <input placeholder="https://…" />;\n`)).toBe(0);
});
});