import { readFileSync } from 'node:fs'; import { afterEach, describe, expect, it } from 'vitest'; import { ProjectStore } from '@/app/store'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { exportView, RECHENWEG_SCHALTER } from '@/ui/views/exportView'; /** * Was `hidden` traegt, ist auch verborgen. * * BEFUND AUS DEM BETRIEB (11.09.2026, mit Bildschirmfoto): In der Ansicht * "Ausgabe" stand der Warnbalken "Rechenweg abgewaehlt" dauerhaft da - auch bei * gesetztem Haekchen, also waehrend der Rechenweg gedruckt wurde. Die Ansicht * blendet ihn ueber das HTML-Merkmal `hidden` aus, und ihre Logik dabei ist * richtig. Nur wirkt das Merkmal allein ueber die Regel `[hidden] { display: * none }` des Browsers, und `.hinweisbalken { display: flex }` schlaegt sie: * Eine Klassenregel hat hoehere Spezifitaet als die Regel eines * Merkmalsselektors aus der Vorgabestilvorlage. * * WARUM DIE STILVORLAGE HIER GEPRUEFT WIRD UND NICHT DIE ANZEIGE. Unter jsdom * gibt es kein Stylesheet - `getComputedStyle` liefert dort nichts, woran sich * dieser Fehler zeigen liesse. Nachweisbar ist er nur an den beiden Tatsachen, * aus denen er folgt: Der Balken traegt eine Klasse mit eigenem `display`, und * die Stilvorlage stellt `[hidden]` darueber. Faellt eine der beiden weg, ist * der Fehler zurueck. */ const STIL = readFileSync('src/styles/app.css', 'utf8'); const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); let aufraeumen: () => void = () => {}; function zeichne() { const wurzel = document.createElement('div'); document.body.append(wurzel); const abmelden = exportView.render(wurzel, { store: new ProjectStore(createStandardIntersectionProject('Ausgabe', FESTES_DATUM)), refresh: () => {}, navigate: () => {}, target: null, }); aufraeumen = () => { abmelden(); wurzel.remove(); }; return wurzel; } afterEach(() => { aufraeumen(); document.body.replaceChildren(); }); /** Die Regel eines Selektors, roh aus der Stilvorlage. */ function regel(selektor: string): string | null { const treffer = new RegExp( `(^|\\})\\s*${selektor.replace(/[.[\]*]/g, (z) => `\\${z}`)}\\s*\\{([^}]*)\\}`, 'm', ).exec(STIL); return treffer?.[2] ?? null; } describe('Die Stilvorlage stellt hidden ueber jede Klassenregel', () => { it('gibt es die Regel ueberhaupt', () => { expect(regel('[hidden]'), 'keine Regel fuer [hidden] in app.css').not.toBeNull(); }); it('setzt sie display auf none', () => { expect(regel('[hidden]')).toMatch(/display:\s*none/); }); it('setzt sie es mit !important', () => { // Ohne das gewinnt jede Klassenregel mit eigenem `display`, und genau daran // scheiterte der Warnbalken der Ausgabe. expect(regel('[hidden]')).toMatch(/display:\s*none\s*!important/); }); }); describe('Warum es diese Regel braucht', () => { it('setzt der Hinweisbalken ein eigenes display', () => { // Die Tatsache, aus der der Fehler folgte. Faellt sie eines Tages weg, ist // die Regel oben trotzdem richtig - aber dieser Fall sagt dann, dass der // Anlass ein anderer geworden ist. expect(regel('.hinweisbalken')).toMatch(/display:\s*flex/); }); it('traegt der Warnbalken der Ausgabe genau diese Klasse', () => { const wurzel = zeichne(); const balken = [...wurzel.querySelectorAll('.hinweisbalken')].find((k) => (k.textContent ?? '').includes('Rechenweg abgewählt'), ); expect(balken, 'Der Warnbalken fehlt in der Ansicht').not.toBeUndefined(); }); }); describe('Der Warnbalken folgt dem Haekchen', () => { function balkenUndSchalter(wurzel: ParentNode) { const balken = [...wurzel.querySelectorAll('.hinweisbalken')].find((k) => (k.textContent ?? '').includes('Rechenweg abgewählt'), ); // Die Beschriftung ist ein Geschwister des Feldes, kein Elternteil - der // Weg fuehrt deshalb ueber `for` und die Kennung. const beschriftung = [...wurzel.querySelectorAll('label')].find((l) => (l.textContent ?? '').includes(RECHENWEG_SCHALTER), ); const schalter = wurzel.querySelector( `input[id="${beschriftung?.getAttribute('for') ?? ''}"]`, ); return { balken, schalter }; } it('ist er verborgen, solange der Rechenweg ausgewaehlt ist', () => { // Der Regelfall: Das Haekchen steht, der Rechenweg wird gedruckt, und die // Warnung davor hat nichts zu suchen. const { balken, schalter } = balkenUndSchalter(zeichne()); expect(schalter?.checked, 'Der Rechenweg ist nicht vorausgewaehlt').toBe(true); expect(balken?.hidden).toBe(true); }); it('erscheint er beim Abwaehlen und verschwindet beim Wiederanwaehlen', () => { const wurzel = zeichne(); const { balken, schalter } = balkenUndSchalter(wurzel); expect(schalter, 'Kein Schalter fuer den Rechenweg').not.toBeNull(); schalter!.checked = false; schalter!.dispatchEvent(new Event('change', { bubbles: true })); expect(balken?.hidden).toBe(false); schalter!.checked = true; schalter!.dispatchEvent(new Event('change', { bubbles: true })); expect(balken?.hidden).toBe(true); }); });