lsa-planer

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

/ tests ui programmauswahl.test.ts

4,7 KB Rohdatei
tests/ui/programmauswahl.test.ts — 139 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { ProjectStore } from '@/app/store';
3 import { createStandardIntersectionProject } from '@/domain/model/factory';
4 import { legeProgrammAn, programmanzahl, wechsleProgramm } from '@/domain/model/programme';
5 import { ERSTES_PROGRAMM } from '@/domain/model/programme';
6 import type { Project } from '@/domain/model/project';
7 import { phasesView } from '@/ui/views/phasesView';
8
9 /**
10 * Die Auswahl der Tagesprogramme.
11 *
12 * WARUM DIE AUSWAHL UEBER DEM PROGRAMMKOPF STEHT: Jede Angabe darunter -
13 * Bezeichnung, Verfahren, Umlaufzeit, Versatz - gilt dem hier gewaehlten
14 * Programm. Stuende die Auswahl anderswo, traege der Bearbeiter Zahlen in ein
15 * Programm ein, von dem er nicht weiss, dass es offen ist.
16 */
17
18 const STICHTAG = new Date('2026-01-01T12:00:00Z');
19
20 function zeichne(project: Project): {
21 wurzel: HTMLElement;
22 store: ProjectStore;
23 aufraeumen: () => void;
24 } {
25 const wurzel = document.createElement('div');
26 document.body.append(wurzel);
27 const store = new ProjectStore(project);
28 const ende = phasesView.render(wurzel, {
29 store,
30 refresh: () => {},
31 navigate: () => {},
32 target: null,
33 });
34 return {
35 wurzel,
36 store,
37 aufraeumen: () => {
38 ende();
39 wurzel.remove();
40 },
41 };
42 }
43
44 /** Die Auswahlliste der Tagesprogramme, an ihrer Beschriftung erkannt. */
45 function auswahl(wurzel: ParentNode): HTMLSelectElement {
46 const treffer = [...wurzel.querySelectorAll('label')].find((l) =>
47 l.textContent?.includes('Bearbeitetes Tagesprogramm'),
48 );
49 const feld = treffer?.querySelector('select') ?? treffer?.parentElement?.querySelector('select');
50 if (!feld) throw new Error('Auswahlliste der Tagesprogramme nicht gefunden.');
51 return feld;
52 }
53
54 function knopf(wurzel: ParentNode, text: string): HTMLButtonElement {
55 const treffer = [...wurzel.querySelectorAll('button')].find(
56 (b) => b.textContent?.trim() === text,
57 );
58 if (!treffer) throw new Error(`Schaltfläche "${text}" nicht gefunden.`);
59 return treffer;
60 }
61
62 function basis(): Project {
63 return createStandardIntersectionProject('Hauptstraße / Bahnhofstraße', STICHTAG);
64 }
65
66 describe('Phasenansicht - Auswahl der Tagesprogramme', () => {
67 it('führt jedes Programm in der Auswahl und schaltet um', () => {
68 const zwei = wechsleProgramm(legeProgrammAn(basis(), 'Nacht'), ERSTES_PROGRAMM);
69 const { wurzel, store, aufraeumen } = zeichne(zwei);
70 try {
71 const feld = auswahl(wurzel);
72 expect([...feld.options].map((o) => o.textContent)).toEqual(['Programm 1', 'Nacht']);
73 expect(feld.value).toBe(ERSTES_PROGRAMM);
74
75 const nachtId = zwei.programmbibliothek[0]!.programm.id;
76 feld.value = nachtId;
77 feld.dispatchEvent(new Event('change'));
78 expect(store.getProject().program.id, 'nach dem Umschalten').toBe(nachtId);
79 expect(store.getProject().program.name).toBe('Nacht');
80 } finally {
81 aufraeumen();
82 }
83 });
84
85 it('legt ein weiteres Programm an und öffnet es', () => {
86 const { wurzel, store, aufraeumen } = zeichne(basis());
87 try {
88 knopf(wurzel, 'Weiteres Tagesprogramm').click();
89 expect(programmanzahl(store.getProject())).toBe(2);
90 expect(store.getProject().program.name).toBe('Programm 2');
91 // Die Zaehlung beginnt bei null: Ein uebernommener Wert waere eine
92 // Behauptung ueber eine Zaehlung, die fuer diese Tageszeit niemand
93 // vorgenommen hat.
94 expect(store.getProject().demands).toEqual([]);
95 } finally {
96 aufraeumen();
97 }
98 });
99
100 it('bietet das Löschen erst ab dem zweiten Programm an', () => {
101 // Ein Projekt ohne Programm hat keinen Signalzeitenplan. Die Schaltflaeche
102 // gar nicht erst zu zeigen ist ehrlicher als eine, die nichts tut.
103 const eines = zeichne(basis());
104 try {
105 expect(() => knopf(eines.wurzel, 'Dieses Tagesprogramm löschen')).toThrow();
106 } finally {
107 eines.aufraeumen();
108 }
109
110 const zwei = zeichne(legeProgrammAn(basis(), 'Nacht'));
111 try {
112 expect(knopf(zwei.wurzel, 'Dieses Tagesprogramm löschen')).toBeTruthy();
113 } finally {
114 zwei.aufraeumen();
115 }
116 });
117
118 it('sagt bei mehreren Programmen, dass alle geprüft werden', () => {
119 /*
120 * Der Satz ist die Bedienoberflaeche zur Ausgabesperre: Wer einen Fehler
121 * im Pruefbericht sieht, den er im sichtbaren Programm nicht findet, muss
122 * hier lesen koennen, woher er kommt.
123 */
124 const zwei = zeichne(legeProgrammAn(basis(), 'Nacht'));
125 try {
126 expect(zwei.wurzel.textContent).toContain('Geprüft werden alle');
127 expect(zwei.wurzel.textContent).toContain('sperrt die Ausgabe');
128 } finally {
129 zwei.aufraeumen();
130 }
131
132 const eines = zeichne(basis());
133 try {
134 expect(eines.wurzel.textContent).toContain('führt ein Tagesprogramm');
135 } finally {
136 eines.aufraeumen();
137 }
138 });
139 });