lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests ui zwischenzeitfensterHilfe.test.ts

4,9 KB Rohdatei
tests/ui/zwischenzeitfensterHilfe.test.ts — 134 Zeilen
1 import { afterEach, describe, expect, it } from 'vitest';
2 import { ProjectStore } from '@/app/store';
3 import { createStandardIntersectionProject } from '@/domain/model/factory';
4 import type { Project } from '@/domain/model/project';
5 import { mitBewahrtemFokus } from '@/ui/fokus';
6 import type { ViewContext } from '@/ui/shell';
7 import { conflictsView } from '@/ui/views/conflictsView';
8
9 /**
10 * Befund 44 zur Fassung 5.10.0.
11 *
12 * `labelled()` im Zwischenzeitfenster haengte den Hilfeknopf als Kind IN das
13 * `label`. Was im `label` liegt, ist der NAME des Feldes: Es hiess damit nicht
14 * "Räumweg", sondern "Räumweg Hilfe zu Räumweg". Betroffen waren alle sechs
15 * Zahlenfelder des Fensters.
16 *
17 * Die Regel steht im Programm bereits dreifach: `beschriftetesFeld`
18 * (src/ui/dom.ts), `feld()` im Assistenten und `mitHilfe()` in der
19 * Projektansicht stellen den Knopf NEBEN die Beschriftung, in eine
20 * `feld-kopf`-Zeile; `tests/ui/hilfeAmFeld.test.ts` und
21 * `tests/ui/anordnungsangaben.test.ts` bewachen sie. `labelled()` war die
22 * vierte Stelle mit Hilfeknopf und die einzige, die es andersherum machte.
23 *
24 * Gerade hier wiegt der Name schwer: Raeumweg und Einfahrweg sind fachlich
25 * vertauschbare Groessen mit derselben Einheit. Werden sie verwechselt, ist die
26 * Zwischenzeit falsch, und nichts weist darauf hin.
27 */
28
29 const FESTES_DATUM = new Date('2026-01-01T00:00:00Z');
30
31 let aufraeumen: (() => void) | null = null;
32 afterEach(() => {
33 aufraeumen?.();
34 aufraeumen = null;
35 document.body.replaceChildren();
36 });
37
38 function zeichne(project: Project): HTMLElement {
39 const wurzel = document.createElement('div');
40 document.body.append(wurzel);
41 const store = new ProjectStore(project);
42 let abmelden: () => void = () => {};
43 const context: ViewContext = {
44 store,
45 refresh: () => {
46 mitBewahrtemFokus(wurzel, () => {
47 abmelden();
48 wurzel.replaceChildren();
49 abmelden = conflictsView.render(wurzel, context);
50 });
51 },
52 navigate: () => {},
53 target: null,
54 };
55 abmelden = conflictsView.render(wurzel, context);
56 aufraeumen = () => {
57 abmelden();
58 wurzel.remove();
59 };
60 return wurzel;
61 }
62
63 /** Oeffnet das Bearbeitungsfenster ueber die erste besetzte Matrixzelle. */
64 function oeffneFenster(wurzel: HTMLElement): HTMLElement {
65 const zelle = [...wurzel.querySelectorAll('button')].find((b) =>
66 (b.getAttribute('aria-label') ?? '').includes('fährt ein: Zwischenzeit'),
67 );
68 if (zelle === undefined) throw new Error('Keine besetzte Matrixzelle');
69 zelle.click();
70 const fenster = document.querySelector<HTMLElement>('.dialog-hintergrund .dialog-inhalt');
71 if (fenster === null) throw new Error('Das Bearbeitungsfenster ist nicht offen');
72 return fenster;
73 }
74
75 /** Die sechs Zahlenfelder des Fensters tragen einen Hilfeknopf. */
76 const FELDER = [
77 'Räumweg',
78 'Einfahrweg',
79 'Räumgeschwindigkeit',
80 'Einfahrgeschwindigkeit',
81 'Überfahrzeit',
82 'Zwischenzeit von Hand',
83 ] as const;
84
85 describe('Befund 44 - Hilfeknopf des Zwischenzeitfensters steht neben der Beschriftung', () => {
86 it('haelt den Knopf aus dem Namen jedes Zahlenfeldes heraus', () => {
87 const wurzel = zeichne(createStandardIntersectionProject('Hilfe', FESTES_DATUM));
88 const fenster = oeffneFenster(wurzel);
89
90 const beschriftungen = [...fenster.querySelectorAll('label')];
91 expect(beschriftungen.length, 'Das Fenster zeigt keine beschrifteten Felder').toBeGreaterThan(
92 FELDER.length,
93 );
94
95 for (const label of beschriftungen) {
96 const knopf = label.querySelector('.hilfe-knopf');
97 expect(
98 knopf,
99 `Der Hilfeknopf steht in der Beschriftung "${label.textContent ?? ''}" und geht damit in ` +
100 'den Namen des Feldes ein',
101 ).toBeNull();
102 }
103
104 // Der reine Name bleibt uebrig - "Räumweg", nicht "Räumweg?".
105 const namen = beschriftungen.map((l) => (l.textContent ?? '').trim());
106 for (const feld of FELDER) {
107 expect(namen, `Die Beschriftung "${feld}" fehlt oder trägt Beiwerk`).toContain(feld);
108 }
109 });
110
111 it('stellt Beschriftung und Knopf in dieselbe Kopfzeile wie beschriftetesFeld', () => {
112 const wurzel = zeichne(createStandardIntersectionProject('Hilfe', FESTES_DATUM));
113 const fenster = oeffneFenster(wurzel);
114
115 for (const feld of FELDER) {
116 const label = [...fenster.querySelectorAll('label')].find(
117 (l) => (l.textContent ?? '').trim() === feld,
118 );
119 expect(label, `Kein Feld "${feld}"`).toBeDefined();
120
121 const kopf = label!.parentElement;
122 expect(kopf?.classList.contains('feld-kopf'), `"${feld}" steht in keiner Kopfzeile`).toBe(
123 true,
124 );
125 expect(
126 kopf?.querySelector('.hilfe-knopf'),
127 `Neben "${feld}" fehlt der Hilfeknopf`,
128 ).not.toBeNull();
129 // Die Verknuepfung bleibt: Die Beschriftung ist weiterhin anklickbar.
130 expect(label!.htmlFor, `"${feld}" ist mit keinem Feld verknüpft`).not.toBe('');
131 expect(fenster.querySelector(`#${label!.htmlFor}`)).not.toBeNull();
132 }
133 });
134 });