import { afterEach, describe, expect, it } from 'vitest'; import { ProjectStore } from '@/app/store'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import type { Project } from '@/domain/model/project'; import { mitBewahrtemFokus } from '@/ui/fokus'; import type { ViewContext } from '@/ui/shell'; import { conflictsView } from '@/ui/views/conflictsView'; /** * Befund 44 zur Fassung 5.10.0. * * `labelled()` im Zwischenzeitfenster haengte den Hilfeknopf als Kind IN das * `label`. Was im `label` liegt, ist der NAME des Feldes: Es hiess damit nicht * "Räumweg", sondern "Räumweg Hilfe zu Räumweg". Betroffen waren alle sechs * Zahlenfelder des Fensters. * * Die Regel steht im Programm bereits dreifach: `beschriftetesFeld` * (src/ui/dom.ts), `feld()` im Assistenten und `mitHilfe()` in der * Projektansicht stellen den Knopf NEBEN die Beschriftung, in eine * `feld-kopf`-Zeile; `tests/ui/hilfeAmFeld.test.ts` und * `tests/ui/anordnungsangaben.test.ts` bewachen sie. `labelled()` war die * vierte Stelle mit Hilfeknopf und die einzige, die es andersherum machte. * * Gerade hier wiegt der Name schwer: Raeumweg und Einfahrweg sind fachlich * vertauschbare Groessen mit derselben Einheit. Werden sie verwechselt, ist die * Zwischenzeit falsch, und nichts weist darauf hin. */ const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); let aufraeumen: (() => void) | null = null; afterEach(() => { aufraeumen?.(); aufraeumen = null; document.body.replaceChildren(); }); function zeichne(project: Project): HTMLElement { const wurzel = document.createElement('div'); document.body.append(wurzel); const store = new ProjectStore(project); let abmelden: () => void = () => {}; const context: ViewContext = { store, refresh: () => { mitBewahrtemFokus(wurzel, () => { abmelden(); wurzel.replaceChildren(); abmelden = conflictsView.render(wurzel, context); }); }, navigate: () => {}, target: null, }; abmelden = conflictsView.render(wurzel, context); aufraeumen = () => { abmelden(); wurzel.remove(); }; return wurzel; } /** Oeffnet das Bearbeitungsfenster ueber die erste besetzte Matrixzelle. */ function oeffneFenster(wurzel: HTMLElement): HTMLElement { const zelle = [...wurzel.querySelectorAll('button')].find((b) => (b.getAttribute('aria-label') ?? '').includes('fährt ein: Zwischenzeit'), ); if (zelle === undefined) throw new Error('Keine besetzte Matrixzelle'); zelle.click(); const fenster = document.querySelector('.dialog-hintergrund .dialog-inhalt'); if (fenster === null) throw new Error('Das Bearbeitungsfenster ist nicht offen'); return fenster; } /** Die sechs Zahlenfelder des Fensters tragen einen Hilfeknopf. */ const FELDER = [ 'Räumweg', 'Einfahrweg', 'Räumgeschwindigkeit', 'Einfahrgeschwindigkeit', 'Überfahrzeit', 'Zwischenzeit von Hand', ] as const; describe('Befund 44 - Hilfeknopf des Zwischenzeitfensters steht neben der Beschriftung', () => { it('haelt den Knopf aus dem Namen jedes Zahlenfeldes heraus', () => { const wurzel = zeichne(createStandardIntersectionProject('Hilfe', FESTES_DATUM)); const fenster = oeffneFenster(wurzel); const beschriftungen = [...fenster.querySelectorAll('label')]; expect(beschriftungen.length, 'Das Fenster zeigt keine beschrifteten Felder').toBeGreaterThan( FELDER.length, ); for (const label of beschriftungen) { const knopf = label.querySelector('.hilfe-knopf'); expect( knopf, `Der Hilfeknopf steht in der Beschriftung "${label.textContent ?? ''}" und geht damit in ` + 'den Namen des Feldes ein', ).toBeNull(); } // Der reine Name bleibt uebrig - "Räumweg", nicht "Räumweg?". const namen = beschriftungen.map((l) => (l.textContent ?? '').trim()); for (const feld of FELDER) { expect(namen, `Die Beschriftung "${feld}" fehlt oder trägt Beiwerk`).toContain(feld); } }); it('stellt Beschriftung und Knopf in dieselbe Kopfzeile wie beschriftetesFeld', () => { const wurzel = zeichne(createStandardIntersectionProject('Hilfe', FESTES_DATUM)); const fenster = oeffneFenster(wurzel); for (const feld of FELDER) { const label = [...fenster.querySelectorAll('label')].find( (l) => (l.textContent ?? '').trim() === feld, ); expect(label, `Kein Feld "${feld}"`).toBeDefined(); const kopf = label!.parentElement; expect(kopf?.classList.contains('feld-kopf'), `"${feld}" steht in keiner Kopfzeile`).toBe( true, ); expect( kopf?.querySelector('.hilfe-knopf'), `Neben "${feld}" fehlt der Hilfeknopf`, ).not.toBeNull(); // Die Verknuepfung bleibt: Die Beschriftung ist weiterhin anklickbar. expect(label!.htmlFor, `"${feld}" ist mit keinem Feld verknüpft`).not.toBe(''); expect(fenster.querySelector(`#${label!.htmlFor}`)).not.toBeNull(); } }); });