waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
/ app tests gesperrt-sichtbar.test.ts
| 1 | // @vitest-environment node |
| 2 | /** |
| 3 | * „Gesperrt sieht gesperrt aus“ – auch ohne Farbe. |
| 4 | * |
| 5 | * **Der Anlass.** Die Projektregel `aria-disabled` statt `disabled` ist |
| 6 | * richtig: Ein natives `disabled` nähme dem Element den Fokus, und der |
| 7 | * erklärende Satz daneben würde nie vorgelesen. Der Zustand wurde aber |
| 8 | * **ausschließlich über Farbtoken** gezeichnet – heller Text auf versetztem |
| 9 | * Grund. Im eigenen Hochkontrast-Thema ist `--farbe-text-leise` mit |
| 10 | * `--farbe-text` identisch (beides `#ffffff`) und `--farbe-grund-versetzt` |
| 11 | * mit dem Grund (`#000000`): Ein gesperrter Knopf sah dort **pixelgleich** |
| 12 | * aus wie ein aktiver. Unter erzwungenen Farben fallen alle beteiligten Token |
| 13 | * ohnehin auf `CanvasText` und `Canvas` zusammen. |
| 14 | * |
| 15 | * Wer Hochkontrast braucht, drückte also auf scheinbar bereite Knöpfe, ohne |
| 16 | * dass etwas geschah – genau die Verwirrung, die der Kommentar in `lernen.css` |
| 17 | * zu verhindern verspricht. |
| 18 | * |
| 19 | * Geprüft wird hier am Stilblatt, nicht im Browser: Ob eine gestrichelte |
| 20 | * Linie im echten Fenster auffällt, sagt keine Messung – das ist Sichtprüfung |
| 21 | * H8. Was sich hier feststellen lässt, ist, dass der Zustand überhaupt an |
| 22 | * etwas anderem hängt als an einer Farbe, die in zwei von vier Darstellungen |
| 23 | * mit ihrem Gegenstück zusammenfällt. |
| 24 | */ |
| 25 | |
| 26 | import { readFileSync } from 'node:fs'; |
| 27 | import { join } from 'node:path'; |
| 28 | import { fileURLToPath } from 'node:url'; |
| 29 | |
| 30 | import { describe, expect, it } from 'vitest'; |
| 31 | |
| 32 | const wurzel = fileURLToPath(new URL('..', import.meta.url)); |
| 33 | |
| 34 | function stil(datei: string): string { |
| 35 | return readFileSync(join(wurzel, 'src', 'renderer', 'src', 'styles', datei), 'utf8'); |
| 36 | } |
| 37 | |
| 38 | const LERNEN_CSS = stil('lernen.css'); |
| 39 | const TOKENS_CSS = stil('tokens.css'); |
| 40 | |
| 41 | /** Der Regelblock ab einem Selektor bis zur schließenden Klammer. */ |
| 42 | function block(css: string, selektor: string): string { |
| 43 | const anfang = css.indexOf(selektor); |
| 44 | expect(anfang, `Selektor nicht gefunden: ${selektor}`).toBeGreaterThan(-1); |
| 45 | const auf = css.indexOf('{', anfang); |
| 46 | const zu = css.indexOf('}', auf); |
| 47 | return css.slice(auf, zu); |
| 48 | } |
| 49 | |
| 50 | describe('Der Gesperrt-Zustand hängt nicht allein an der Farbe', () => { |
| 51 | it('gibt gesperrten Knöpfen ein farbunabhängiges Merkmal', () => { |
| 52 | const regel = block(LERNEN_CSS, ".knopf[aria-disabled='true']"); |
| 53 | |
| 54 | expect(regel).toMatch(/border-style:\s*dashed/u); |
| 55 | }); |
| 56 | |
| 57 | it('belegt, warum die Farbe allein nicht trägt', () => { |
| 58 | /* Die Gegenprobe zur Behebung: Wären die beiden Token im |
| 59 | Hochkontrast-Thema verschieden, bräuchte es das zusätzliche Merkmal |
| 60 | nicht – und dieser Test dürfte gelöscht werden. Solange sie |
| 61 | zusammenfallen, ist er der Beleg für den Befund. */ |
| 62 | const hochkontrast = block(TOKENS_CSS, ":root[data-thema='hochkontrast']"); |
| 63 | const werte = new Map( |
| 64 | [...hochkontrast.matchAll(/--([a-z-]+):\s*(#[0-9a-f]{6})\b/gu)].map((t) => [t[1], t[2]]), |
| 65 | ); |
| 66 | |
| 67 | expect(werte.get('farbe-text-leise')).toBe(werte.get('farbe-text')); |
| 68 | expect(werte.get('farbe-grund-versetzt')).toBe(werte.get('farbe-grund')); |
| 69 | }); |
| 70 | }); |
| 71 | |
| 72 | describe('Erzwungene Farben (Windows-Kontrastdesign)', () => { |
| 73 | it('kennzeichnet gesperrte Knöpfe mit der Systemfarbe GrayText', () => { |
| 74 | /* Die einzige Farbe, die unter erzwungenen Farben einen Zustand |
| 75 | ausdrücken darf – und die einzige, die Windows-Nutzer dafür kennen. |
| 76 | Sie muss ausdrücklich gesetzt werden: Die Token fallen dort alle auf |
| 77 | CanvasText zusammen. */ |
| 78 | const bereich = LERNEN_CSS.slice(LERNEN_CSS.indexOf('@media (forced-colors: active)')); |
| 79 | const regel = block(bereich, ".knopf[aria-disabled='true']"); |
| 80 | |
| 81 | expect(regel).toMatch(/color:\s*GrayText/u); |
| 82 | expect(regel).toMatch(/border-color:\s*GrayText/u); |
| 83 | }); |
| 84 | |
| 85 | it('kennzeichnet auch gesperrte Schalter', () => { |
| 86 | const bereich = LERNEN_CSS.slice(LERNEN_CSS.indexOf('@media (forced-colors: active)')); |
| 87 | const regel = block(bereich, ".schalter__feld[aria-disabled='true'] + .schalter__beschriftung"); |
| 88 | |
| 89 | expect(regel).toMatch(/color:\s*GrayText/u); |
| 90 | }); |
| 91 | }); |