lsa-planer

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

/ tests ui programmeinstellungen.test.ts

5,0 KB Rohdatei
tests/ui/programmeinstellungen.test.ts — 136 Zeilen
1 import { beforeAll, describe, expect, it, vi } from 'vitest';
2 import { ladeSitzung, loadAppSettings, saveSession } from '@/services/storage';
3
4 /**
5 * Programmkopf und Vorgabenansicht duerfen einander die Einstellungen nicht
6 * ueberschreiben (Befund 19).
7 *
8 * Was der Anwender sonst erlebt: Er traegt unter "Vorgaben" die Fundstelle aus
9 * seiner Regelwerksausgabe ein - "Abschnitt 3.4, Tabelle 6" -, drueckt danach
10 * in der Kopfzeile "Ansicht:" und die Angabe ist stumm verschwunden. Sie fehlt
11 * damit auch in der Planunterlage, die `exportView` aus
12 * `loadAppSettings().fundstellen` speist. Grund: `boot()` haelt eine Kopie der
13 * Programmeinstellungen vom Programmstart, die Vorgabenansicht eine zweite vom
14 * Zeichnen, und `saveAppSettings` schreibt das ganze Objekt, ohne zu mischen.
15 * Die Gegenrichtung gilt ebenso: Eine nach dem Zeichnen der Vorgabenansicht
16 * geaenderte Anzeigedauer wird von der naechsten Fundstelleneingabe
17 * zurueckgesetzt.
18 *
19 * Geprueft wird am echten Einstiegspunkt: `src/main.ts` wird eingelesen und
20 * startet die ganze Anwendung; nur der Sitzungsspeicher ist ersetzt.
21 */
22
23 vi.mock('@/services/storage', async (importOriginal) => {
24 const echt = await importOriginal<typeof import('@/services/storage')>();
25 return {
26 ...echt,
27 ladeSitzung: vi.fn(),
28 saveSession: vi.fn(),
29 addRecoveryPoint: vi.fn(),
30 };
31 });
32
33 async function warte(ms = 0): Promise<void> {
34 await new Promise((fertig) => setTimeout(fertig, ms));
35 }
36
37 function schaltflaeche(text: string): HTMLButtonElement {
38 const treffer = [...document.querySelectorAll('button')].find(
39 (b) => b.textContent?.trim() === text || b.getAttribute('aria-label') === text,
40 );
41 if (!treffer) throw new Error(`Schaltflaeche "${text}" nicht gefunden.`);
42 return treffer;
43 }
44
45 /** Die Kopfzeilenschaltflaeche fuer das Erscheinungsbild. */
46 function erscheinungsbildKnopf(): HTMLButtonElement {
47 const treffer = [...document.querySelectorAll('button')].find((b) =>
48 b.textContent?.startsWith('Ansicht:'),
49 );
50 if (!treffer) throw new Error('Schaltflaeche "Ansicht: ..." nicht gefunden.');
51 return treffer;
52 }
53
54 /** Wechselt ueber die Navigationsleiste in eine Ansicht. */
55 function gehZu(beschriftung: string): void {
56 const treffer = [...document.querySelectorAll<HTMLButtonElement>('.nav-knopf')].find(
57 (b) => b.querySelector('.nav-beschriftung')?.textContent === beschriftung,
58 );
59 if (!treffer) throw new Error(`Ansicht "${beschriftung}" nicht gefunden.`);
60 treffer.click();
61 }
62
63 /** Die Fundstellenfelder der Vorgabenansicht, an ihrem Platzhalter erkannt. */
64 function fundstellenfelder(): HTMLInputElement[] {
65 return [
66 ...document.querySelectorAll<HTMLInputElement>(
67 'input[placeholder="z. B. Abschnitt 3.4, Tabelle 6"]',
68 ),
69 ];
70 }
71
72 function trageEin(feld: HTMLInputElement, wert: string): void {
73 feld.value = wert;
74 feld.dispatchEvent(new Event('change'));
75 }
76
77 beforeAll(async () => {
78 vi.mocked(ladeSitzung).mockResolvedValue({ art: 'leer' });
79 vi.mocked(saveSession).mockResolvedValue({ ok: true, message: '' });
80
81 localStorage.clear();
82 document.body.replaceChildren();
83 const wurzel = document.createElement('div');
84 wurzel.id = 'anwendung';
85 document.body.append(wurzel);
86
87 await import('@/main');
88 await warte(20);
89 });
90
91 describe('Zwei Buchfuehrungen ueber die Programmeinstellungen', () => {
92 it('haelt eine eingetragene Fundstelle beim Umschalten des Erscheinungsbilds', async () => {
93 gehZu('Vorgaben');
94 await warte(0);
95
96 const felder = fundstellenfelder();
97 expect(felder.length).toBeGreaterThan(0);
98 trageEin(felder[0]!, 'Abschnitt 3.4, Tabelle 6');
99 await warte(0);
100 expect(Object.values(loadAppSettings().fundstellen)).toContain('Abschnitt 3.4, Tabelle 6');
101
102 erscheinungsbildKnopf().click();
103 await warte(0);
104
105 // Der Programmkopf schrieb hier seine Startkopie zurueck - ohne die
106 // Fundstelle. Sie fehlt danach auch in der Planunterlage.
107 expect(Object.values(loadAppSettings().fundstellen)).toContain('Abschnitt 3.4, Tabelle 6');
108 });
109
110 it('haelt eine geaenderte Anzeigedauer beim naechsten Eintrag einer Fundstelle', async () => {
111 gehZu('Vorgaben');
112 await warte(0);
113
114 // Die Vorgabenansicht steht gezeichnet da und haelt ihre Kopie. Jetzt
115 // aendert der Anwender im Fenster "Meldungen" die Anzeigedauer.
116 schaltflaeche('Meldungen').click();
117 await warte(0);
118 const dauer = document.querySelector<HTMLSelectElement>('.dialog-hintergrund select');
119 if (dauer === null) throw new Error('Auswahlliste der Anzeigedauer nicht gefunden.');
120 dauer.value = '20';
121 dauer.dispatchEvent(new Event('change'));
122 await warte(0);
123 expect(loadAppSettings().meldungsdauerSekunden).toBe(20);
124 schaltflaeche('Schließen').click();
125 await warte(0);
126
127 // Und traegt danach in der noch immer gezeichneten Vorgabenansicht eine
128 // zweite Fundstelle ein.
129 const felder = fundstellenfelder();
130 trageEin(felder[1] ?? felder[0]!, 'Bild 12');
131 await warte(0);
132
133 expect(loadAppSettings().meldungsdauerSekunden).toBe(20);
134 expect(Object.values(loadAppSettings().fundstellen)).toContain('Bild 12');
135 });
136 });