lsa-planer

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

/ tests ui speichernWettlauf.test.ts

3,8 KB Rohdatei
tests/ui/speichernWettlauf.test.ts — 111 Zeilen
1 import { beforeAll, describe, expect, it, vi } from 'vitest';
2 import { ladeSitzung, saveProjectToFile, saveSession } from '@/services/storage';
3 import type { FileOperationResult } from '@/services/storage';
4
5 /**
6 * Aenderungen waehrend des Dateischreibens duerfen nicht als gespeichert gelten
7 * (Befund 16).
8 *
9 * Was der Anwender sonst erlebt: Ein Projekt mit eingebettetem Luftbild liegt
10 * auf einem Netzlaufwerk. Er drueckt Strg+S und arbeitet waehrend des
11 * Schreibens weiter - eine Fahrlinie ziehen, in ein Textfeld tippen. Der
12 * Dateiinhalt steht seit dem Aufruf fest (`serializeProject` laeuft vor dem
13 * `await`), die Aenderung ist also nicht darin. Danach loescht
14 * `store.markSaved()` die Aenderungsmarke trotzdem: Die Kopfzeile meldet
15 * "gespeichert", "Neu" und "Öffnen" verwerfen ohne Rueckfrage, und die Datei -
16 * die angeordnete Unterlage - traegt still den aelteren Stand.
17 *
18 * Geprueft wird am echten Einstiegspunkt: `src/main.ts` wird eingelesen und
19 * startet die ganze Anwendung; ersetzt sind nur Sitzungsspeicher und
20 * Dateizugriff.
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 saveProjectToFile: vi.fn(),
31 };
32 });
33
34 async function warte(ms = 0): Promise<void> {
35 await new Promise((fertig) => setTimeout(fertig, ms));
36 }
37
38 function offeneFenster(): string[] {
39 return [...document.querySelectorAll('.dialog-hintergrund h2')].map(
40 (h) => h.textContent?.trim() ?? '',
41 );
42 }
43
44 /** Das Eingabefeld zu einer sichtbaren Beschriftung. */
45 function feld(beschriftung: string): HTMLInputElement {
46 const label = [...document.querySelectorAll('label')].find(
47 (l) => l.textContent?.trim() === beschriftung,
48 );
49 const id = label?.getAttribute('for');
50 const eingabe = id === null || id === undefined ? null : document.getElementById(id);
51 if (!(eingabe instanceof HTMLInputElement)) {
52 throw new Error(`Eingabefeld "${beschriftung}" nicht gefunden.`);
53 }
54 return eingabe;
55 }
56
57 function druecke(taste: string): void {
58 window.dispatchEvent(
59 new KeyboardEvent('keydown', { key: taste, ctrlKey: true, bubbles: true, cancelable: true }),
60 );
61 }
62
63 /** Der Dateischreibvorgang, der erst auf Zuruf fertig wird. */
64 let fertigstellen: ((ergebnis: FileOperationResult) => void) | null = null;
65
66 beforeAll(async () => {
67 vi.mocked(ladeSitzung).mockResolvedValue({ art: 'leer' });
68 vi.mocked(saveSession).mockResolvedValue({ ok: true, message: '' });
69 vi.mocked(saveProjectToFile).mockReturnValue(
70 new Promise<FileOperationResult>((erfuellen) => {
71 fertigstellen = erfuellen;
72 }),
73 );
74
75 document.body.replaceChildren();
76 const wurzel = document.createElement('div');
77 wurzel.id = 'anwendung';
78 document.body.append(wurzel);
79
80 await import('@/main');
81 await warte(20);
82 });
83
84 describe('Aenderung waehrend des Dateischreibens', () => {
85 it('behaelt die Aenderungsmarke, wenn sich das Projekt inzwischen geaendert hat', async () => {
86 druecke('s');
87 await warte(0);
88 expect(saveProjectToFile).toHaveBeenCalledTimes(1);
89
90 // Der Schreibvorgang laeuft noch - der Anwender tippt weiter.
91 const name = feld('Projektbezeichnung');
92 name.value = 'Bahnhofstraße / Ringstraße';
93 name.dispatchEvent(new Event('change'));
94 await warte(0);
95
96 fertigstellen?.({
97 ok: true,
98 canceled: false,
99 filePath: 'C:/Plaene/knoten.lsap',
100 message: 'Projekt gespeichert.',
101 });
102 await warte(20);
103
104 // Die Probe: Steht der Stand noch aus, muss "Neu" zurueckfragen.
105 druecke('n');
106 await warte(10);
107 expect(offeneFenster()).toEqual(['Ungespeicherte Änderungen']);
108
109 document.querySelector<HTMLButtonElement>('.dialog-hintergrund button')?.click();
110 });
111 });