Files
siftlode/frontend/src/lib/literalA11yText.contract.test.ts
T
peter 4f72b193aa fix(i18n): every label a screen reader or tooltip says is translated
- The Plex playlist rows' "reorder"/"remove", the Plex filter chip's "remove" and the player
  slider's "reset" were English literals; each now names its row or setting in HU/EN.
- The playlist show header's chevron had no accessible name; its decorative thumbnail button left
  the tab order.
- The opt-in Glass tuner panel was English only: every word is translated, and each slider's label
  now names its number box and range.
- New `local/literal-a11y-text` lint rule (with its contract test): a literal word in aria-label,
  title, placeholder, alt and the other spoken/shown attributes fails lint.
2026-09-19 17:12:47 +02:00

34 lines
1.8 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 { fileURLToPath } from "node:url";
import { ESLint } from "eslint";
import { describe, expect, it } from "vitest";
// `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";
const FRONTEND = fileURLToPath(new URL("../../", import.meta.url));
async function hits(source: string): Promise<number> {
const eslint = new ESLint({ cwd: FRONTEND });
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);
});
});