lsa-planer

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

/ tests ui menuewegBeiOffenemFenster.test.ts

4,6 KB Rohdatei
tests/ui/menuewegBeiOffenemFenster.test.ts — 133 Zeilen
1 import { beforeAll, describe, expect, it, vi } from 'vitest';
2 import { ladeSitzung, saveProjectToFile, saveSession } from '@/services/storage';
3 import type { MenuCommand } from '@shared/ipc';
4
5 /**
6 * Befund 14 zur Fassung 5.9.0 - der Teil, der den Menueweg betrifft.
7 *
8 * Der Tastenempfaenger bricht bei offenem modalem Fenster ab (`src/main.ts`,
9 * "Nichts auswerten, solange ein modales Fenster offensteht"), und "Neu" wie
10 * "Öffnen" tragen denselben Waechter in sich - ausdruecklich deshalb, weil das
11 * Anwendungsmenue von der Stilllegung des Hintergrunds nicht erfasst wird.
12 * "Speichern" und "Speichern unter" hatten ihn nicht: Der Menuebefehl loeste
13 * einen Speichervorgang aus, waehrend die Vorlagenauswahl oder die Rueckfrage
14 * "Ungespeicherte Änderungen" offenstand. Der Riegel um `saveProject`
15 * verhindert nur den ZWEITEN Lauf daneben, nicht diesen ersten.
16 *
17 * Geprueft wird am echten Einstiegspunkt: `src/main.ts` startet die ganze
18 * Anwendung; ersetzt sind nur Sitzungsspeicher, Dateizugriff und die
19 * Desktop-Bruecke, ueber die der Menuebefehl hereinkommt.
20 */
21
22 vi.mock('@/services/storage', async (importOriginal) => {
23 const echt = await importOriginal<typeof import('@/services/storage')>();
24 return {
25 ...echt,
26 ladeSitzung: vi.fn(),
27 saveSession: vi.fn(),
28 addRecoveryPoint: vi.fn(),
29 saveProjectToFile: 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 offeneFenster(): string[] {
38 return [...document.querySelectorAll('.dialog-hintergrund h2')].map(
39 (h) => h.textContent?.trim() ?? '',
40 );
41 }
42
43 function feld(beschriftung: string): HTMLInputElement {
44 const label = [...document.querySelectorAll('label')].find(
45 (l) => l.textContent?.trim() === beschriftung,
46 );
47 const id = label?.getAttribute('for');
48 const eingabe = id === null || id === undefined ? null : document.getElementById(id);
49 if (!(eingabe instanceof HTMLInputElement)) {
50 throw new Error(`Eingabefeld "${beschriftung}" nicht gefunden.`);
51 }
52 return eingabe;
53 }
54
55 function druecke(taste: string): void {
56 window.dispatchEvent(
57 new KeyboardEvent('keydown', { key: taste, ctrlKey: true, bubbles: true, cancelable: true }),
58 );
59 }
60
61 /** Der Menuebefehl, so wie ihn der Hauptprozess schickt. */
62 let sendeMenuebefehl: (befehl: MenuCommand) => void = () => undefined;
63
64 beforeAll(async () => {
65 vi.mocked(ladeSitzung).mockResolvedValue({ art: 'leer' });
66 vi.mocked(saveSession).mockResolvedValue({ ok: true, message: '' });
67 vi.mocked(saveProjectToFile).mockResolvedValue({
68 ok: true,
69 canceled: false,
70 filePath: 'C:/Plaene/knoten.lsap',
71 message: 'Gespeichert: C:/Plaene/knoten.lsap',
72 });
73
74 (window as unknown as { lsaPlaner: unknown }).lsaPlaner = {
75 platform: 'electron',
76 appVersion: 'pruefung',
77 setDirty: () => undefined,
78 reportError: () => undefined,
79 logPath: () => Promise.resolve('C:/Protokoll/lsa-planer.log'),
80 onMenuCommand: (behandler: (befehl: MenuCommand) => void) => {
81 sendeMenuebefehl = behandler;
82 return () => undefined;
83 },
84 onBeforeClose: () => undefined,
85 };
86
87 document.body.replaceChildren();
88 const wurzel = document.createElement('div');
89 wurzel.id = 'anwendung';
90 document.body.append(wurzel);
91
92 await import('@/main');
93 await warte(20);
94 });
95
96 describe('Datei > Speichern aus dem Anwendungsmenü', () => {
97 it('löst hinter einem offenen Fenster keinen Speichervorgang aus', async () => {
98 const name = feld('Projektbezeichnung');
99 name.value = 'Bahnhofstraße';
100 name.dispatchEvent(new Event('change'));
101 await warte(0);
102
103 // Der Anwender greift zu "Neu"; wegen der ungespeicherten Aenderung steht
104 // die Rueckfrage offen. Das Anwendungsmenue bleibt daneben bedienbar - es
105 // wird von der Stilllegung des Hintergrunds nicht erfasst.
106 druecke('n');
107 await warte(10);
108 expect(offeneFenster()).toEqual(['Ungespeicherte Änderungen']);
109
110 sendeMenuebefehl('speichern');
111 sendeMenuebefehl('speichern-unter');
112 await warte(10);
113
114 expect(
115 saveProjectToFile,
116 'Der Menueweg speichert hinter der offenen Rueckfrage',
117 ).not.toHaveBeenCalled();
118
119 // Die Rueckfrage abbrechen; das Fenster ist danach vom Stapel.
120 document.querySelector<HTMLButtonElement>('.dialog-hintergrund button')?.click();
121 await warte(10);
122 expect(offeneFenster()).toEqual([]);
123 });
124
125 it('speichert ohne offenes Fenster weiterhin', async () => {
126 // Grenzwache zur vorigen Pruefung: Der Waechter darf den Regelweg nicht
127 // mitsperren.
128 sendeMenuebefehl('speichern');
129 await warte(10);
130
131 expect(saveProjectToFile).toHaveBeenCalledTimes(1);
132 });
133 });