// @vitest-environment node /** * „Gesperrt sieht gesperrt aus“ – auch ohne Farbe. * * **Der Anlass.** Die Projektregel `aria-disabled` statt `disabled` ist * richtig: Ein natives `disabled` nähme dem Element den Fokus, und der * erklärende Satz daneben würde nie vorgelesen. Der Zustand wurde aber * **ausschließlich über Farbtoken** gezeichnet – heller Text auf versetztem * Grund. Im eigenen Hochkontrast-Thema ist `--farbe-text-leise` mit * `--farbe-text` identisch (beides `#ffffff`) und `--farbe-grund-versetzt` * mit dem Grund (`#000000`): Ein gesperrter Knopf sah dort **pixelgleich** * aus wie ein aktiver. Unter erzwungenen Farben fallen alle beteiligten Token * ohnehin auf `CanvasText` und `Canvas` zusammen. * * Wer Hochkontrast braucht, drückte also auf scheinbar bereite Knöpfe, ohne * dass etwas geschah – genau die Verwirrung, die der Kommentar in `lernen.css` * zu verhindern verspricht. * * Geprüft wird hier am Stilblatt, nicht im Browser: Ob eine gestrichelte * Linie im echten Fenster auffällt, sagt keine Messung – das ist Sichtprüfung * H8. Was sich hier feststellen lässt, ist, dass der Zustand überhaupt an * etwas anderem hängt als an einer Farbe, die in zwei von vier Darstellungen * mit ihrem Gegenstück zusammenfällt. */ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, expect, it } from 'vitest'; const wurzel = fileURLToPath(new URL('..', import.meta.url)); function stil(datei: string): string { return readFileSync(join(wurzel, 'src', 'renderer', 'src', 'styles', datei), 'utf8'); } const LERNEN_CSS = stil('lernen.css'); const TOKENS_CSS = stil('tokens.css'); /** Der Regelblock ab einem Selektor bis zur schließenden Klammer. */ function block(css: string, selektor: string): string { const anfang = css.indexOf(selektor); expect(anfang, `Selektor nicht gefunden: ${selektor}`).toBeGreaterThan(-1); const auf = css.indexOf('{', anfang); const zu = css.indexOf('}', auf); return css.slice(auf, zu); } describe('Der Gesperrt-Zustand hängt nicht allein an der Farbe', () => { it('gibt gesperrten Knöpfen ein farbunabhängiges Merkmal', () => { const regel = block(LERNEN_CSS, ".knopf[aria-disabled='true']"); expect(regel).toMatch(/border-style:\s*dashed/u); }); it('belegt, warum die Farbe allein nicht trägt', () => { /* Die Gegenprobe zur Behebung: Wären die beiden Token im Hochkontrast-Thema verschieden, bräuchte es das zusätzliche Merkmal nicht – und dieser Test dürfte gelöscht werden. Solange sie zusammenfallen, ist er der Beleg für den Befund. */ const hochkontrast = block(TOKENS_CSS, ":root[data-thema='hochkontrast']"); const werte = new Map( [...hochkontrast.matchAll(/--([a-z-]+):\s*(#[0-9a-f]{6})\b/gu)].map((t) => [t[1], t[2]]), ); expect(werte.get('farbe-text-leise')).toBe(werte.get('farbe-text')); expect(werte.get('farbe-grund-versetzt')).toBe(werte.get('farbe-grund')); }); }); describe('Erzwungene Farben (Windows-Kontrastdesign)', () => { it('kennzeichnet gesperrte Knöpfe mit der Systemfarbe GrayText', () => { /* Die einzige Farbe, die unter erzwungenen Farben einen Zustand ausdrücken darf – und die einzige, die Windows-Nutzer dafür kennen. Sie muss ausdrücklich gesetzt werden: Die Token fallen dort alle auf CanvasText zusammen. */ const bereich = LERNEN_CSS.slice(LERNEN_CSS.indexOf('@media (forced-colors: active)')); const regel = block(bereich, ".knopf[aria-disabled='true']"); expect(regel).toMatch(/color:\s*GrayText/u); expect(regel).toMatch(/border-color:\s*GrayText/u); }); it('kennzeichnet auch gesperrte Schalter', () => { const bereich = LERNEN_CSS.slice(LERNEN_CSS.indexOf('@media (forced-colors: active)')); const regel = block(bereich, ".schalter__feld[aria-disabled='true'] + .schalter__beschriftung"); expect(regel).toMatch(/color:\s*GrayText/u); }); });