- globals.css: eine Basisregel :where(:focus-visible) mit --focus-width und --focus-offset (Sonne 3 px), Firefox-:-moz-focusring gleichgezogen; --text-xs 0.8125rem. - Fokus-Codemod (scripts/codemod-t5-focus.mjs): eigene Ringe und outline-none raus, sm:h-/sm:size- zu pointer-fine:, Label-Zeilen mit focus-ring-within; Skript-Fokus (Lücken-Liste, „Gespeichert“) bleibt. - Button/Input 44 px, kompakt nur mit Maus; Icon-Knöpfe mit unsichtbarer 44-px-Trefferfläche bei Touch, Nachbarn mit genug Abstand. - 16 px in Feldern, Labels, Auswahl, Leerzuständen, Listentiteln. - .holo-stage auf allen Beute-Bühnen, .stage-card holt den Seitenring. - Baukasten-Wächter (src/styles/baukasten-guard.spec.ts) mit den Regeln aus A3–A5, Ausnahmen mit Grund; Kalender-Auswahl als Inset-Ring. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const SRC = join(__dirname, "..");
|
|
|
|
function sourceFiles(dir: string): string[] {
|
|
return readdirSync(dir).flatMap((name) => {
|
|
const path = join(dir, name);
|
|
if (statSync(path).isDirectory()) {
|
|
return sourceFiles(path);
|
|
}
|
|
return /\.(tsx?|css)$/.test(name) && !/\.spec\./.test(name) ? [path] : [];
|
|
});
|
|
}
|
|
|
|
function read(relative: string): string {
|
|
return readFileSync(join(SRC, relative), "utf8");
|
|
}
|
|
|
|
describe("area migration", () => {
|
|
// The gradient and white-text rules live in src/styles/baukasten-guard.spec.ts (task A5).
|
|
|
|
it("harvest detail uses Scissors, extraction create FlaskConical and storage only Warehouse", () => {
|
|
expect(read("app/(app)/harvests/[id]/page.tsx")).toContain('area="harvests"');
|
|
expect(read("app/(app)/extractions/new/page.tsx")).toContain('area="extractions"');
|
|
const storage = read("app/(app)/storage/page.tsx");
|
|
expect(storage).toContain('area="storage"');
|
|
expect(storage).not.toMatch(/icon=\{Package\}/);
|
|
});
|
|
|
|
it("medicine packs, hive systems and voice settings use their own area icon", () => {
|
|
expect(read("app/(app)/medicines/[id]/page.tsx")).not.toMatch(/icon=\{Package\}/);
|
|
expect(read("app/(app)/medicines/lots/[id]/page.tsx")).toContain('area="medicines"');
|
|
expect(read("app/(app)/settings/hive-systems/page.tsx")).toContain('area="hive-systems"');
|
|
expect(read("app/(app)/settings/voice/page.tsx")).toContain('area="voice"');
|
|
});
|
|
|
|
it("no component passes a raw icon to the headers any more", () => {
|
|
const offenders = sourceFiles(SRC).filter((file) => {
|
|
const text = readFileSync(file, "utf8");
|
|
return /<(PageHeader|DetailHeader|EntityCard)\b[^>]*\bicon=/.test(text);
|
|
});
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
});
|