import { readFileSync, readdirSync, statSync } from 'node:fs'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; /** * Zusagen, die im Stilblatt stehen und die jsdom nicht nachrechnet. * * `getComputedStyle` liefert unter jsdom keine Kaskade: Weder die Stapelfolge * noch der Vorrang zweier Regeln laesst sich dort messen. Beide Aussagen sind * aber Zusagen der Barrierefreiheit, und beide sind still zerfallen, als * jemand an einer der beiden Regeln arbeitete. Deshalb werden sie hier am * Quelltext des Stilblatts festgehalten - grob, aber genau an der Stelle, an * der sie brechen. */ const WURZEL = path.resolve(__dirname, '..', '..'); const STILBLATT = path.join(WURZEL, 'src', 'styles', 'app.css'); function stilblatt(): string { return readFileSync(STILBLATT, 'utf8'); } /** Wert von `z-index` im Block der genannten Auswahlregel. */ function stapelwert(css: string, auswahl: string): number { const start = css.indexOf(`\n${auswahl} {`); expect(start, `Regel "${auswahl}" fehlt`).toBeGreaterThanOrEqual(0); const block = css.slice(start, css.indexOf('\n}', start)); const treffer = /z-index:\s*(\d+)/.exec(block); expect(treffer, `"${auswahl}" ohne z-index`).not.toBeNull(); return Number(treffer![1]); } describe('Meldungsstapel liegt über dem Fensterhintergrund (Befunde M4, L7)', () => { it('gibt ihm eine höhere Stapelfolge als dem Fensterhintergrund', () => { /* * Der Meldungsstapel ist der einzige Teil des Hintergrunds, den ein * geoeffnetes Fenster NICHT stilllegt: Seine Live-Bereiche sollen weiter * ansagen, und eine stehengebliebene Warnung soll schliessbar bleiben * (legeHintergrundStill in src/ui/feedback.ts). * * Bei z-index 200 gegen 300 traf beides nicht zu: Der halb deckende * Fensterhintergrund lag darueber, die Meldung war abgedunkelt, und ein * Klick auf ihren Schliessen-Knopf landete auf dem Hintergrund - der * schliesst das FENSTER. Wer im Zwischenzeitenfenster eine Zahl vertippt, * bekam so eine Warnung, die er nicht wegbekommt, ohne das Fenster zu * verlieren. */ const css = stilblatt(); expect(stapelwert(css, '.meldungen')).toBeGreaterThan(stapelwert(css, '.dialog-hintergrund')); }); }); describe('Zeilenzustände schlagen die Überfahr-Einfärbung', () => { it('gibt den Zustandsregeln denselben Aufbau wie der Hover-Regel und stellt sie dahinter', () => { /* * `tbody tr:hover td` hat drei Typen und eine Pseudoklasse, also (0,1,3); * `.lageplan-aktiv td` kam auf (0,1,1) und verlor. Sobald der Zeiger auf * der Zeile stand, verschwand damit genau die Auskunft, die sie traegt - * welche Haltlinie hervorgehoben ist und welcher Weg vom gemessenen Wert * abweicht. * * Gleicher Aufbau UND spaeter im Blatt: Bei gleichem Gewicht entscheidet * die Reihenfolge. */ const css = stilblatt(); const hover = css.indexOf('tbody tr:hover td {'); expect(hover, 'Hover-Regel fehlt').toBeGreaterThanOrEqual(0); for (const klasse of ['lageplan-aktiv', 'lageplan-abweichung']) { const zustand = css.indexOf(`tbody tr.${klasse} td {`); expect(zustand, `Zustandsregel "${klasse}" ohne tbody-tr-Aufbau`).toBeGreaterThanOrEqual(0); expect(zustand, `"${klasse}" steht vor der Hover-Regel`).toBeGreaterThan(hover); } }); }); describe('prefers-reduced-motion: der Kommentar beschreibt keinen Code, den es nicht gibt', () => { it('wird ausschließlich im Stilblatt ausgewertet', () => { /* * Im Stilblatt stand: "Die Simulation laeuft weiter, aber sie startet dann * nicht von selbst; das steuert die Ansicht". Die Simulationsansicht wertet * die Einstellung nicht aus - sie muss es auch nicht, ihre Bildschleife * laeuft nur nach einem Druck auf "Starten". * * Sollte je eine zweite Stelle hinzukommen, faellt dieser Fall und der * Kommentar ist nachzuziehen. Das ist sein Zweck: Ein Kommentar, der eine * erfundene Steuerung beschreibt, verleitet dazu, sie vorauszusetzen. */ const treffer: string[] = []; const durchlaufe = (ordner: string): void => { for (const eintrag of readdirSync(ordner)) { const voll = path.join(ordner, eintrag); if (statSync(voll).isDirectory()) { durchlaufe(voll); } else if (/\.(ts|css|html)$/.test(eintrag)) { if (readFileSync(voll, 'utf8').includes('prefers-reduced-motion')) treffer.push(voll); } } }; durchlaufe(path.join(WURZEL, 'src')); expect(treffer).toEqual([STILBLATT]); }); });