lsa-planer

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

/ tests ui speichernOhneNachweis.test.ts

5,8 KB Rohdatei
tests/ui/speichernOhneNachweis.test.ts — 157 Zeilen
1 import { beforeAll, describe, expect, it, vi } from 'vitest';
2 import { ladeSitzung, saveProjectToFile, saveSession } from '@/services/storage';
3 import { createEmptyProject } from '@/domain/model/factory';
4
5 /**
6 * Befund 23 zur Fassung 5.9.0.
7 *
8 * Ohne Desktop-Bruecke - `npm run dev` im Browser, und im Auslieferungsstand
9 * dann, wenn `preload.js` unter `sandbox: true` stumm scheitert - faellt
10 * `saveProjectToFile` auf `downloadInBrowser` zurueck. Diese Funktion ist
11 * `void` und klickt nur ein `<a download>` an; danach wurde fest
12 * `{ ok: true, ... }` zurueckgegeben. Ob die Ablage zustande kam - der Anwender
13 * bricht den Speichern-unter-Kasten des Browsers ab, eine Unternehmensrichtlinie
14 * sperrt den Download -, laesst sich von aussen nicht feststellen.
15 *
16 * In `src/main.ts` folgte daraus `store.markSaved(...)`: Die Aenderungsmarke
17 * fiel, Kopfzeile und Statusleiste sagten "gespeichert", und der naechste
18 * Griff zu "Neu" oder "Öffnen" verwarf die Arbeit ohne Rueckfrage - genau die
19 * Lage, die der Kommentar bei `speichereEinmal` fuer den Wettlauffall bereits
20 * als unzulaessig beschreibt.
21 *
22 * Geprueft wird dreifach: die Auskunft der Speicherschicht selbst, was der
23 * Programmstart daraus macht - und dass der zweite Weg durch denselben
24 * Rueckfall, `saveDataToFile` fuer PDF, CSV und die ausgegebene Projektdatei,
25 * dieselbe Auskunft gibt. Er meldete zuvor "Heruntergeladen: ..." und damit
26 * denselben Vorgang in derselben Datei anders als 30 Zeilen darueber.
27 */
28
29 vi.mock('@/services/storage', async (importOriginal) => {
30 const echt = await importOriginal<typeof import('@/services/storage')>();
31 return {
32 ...echt,
33 ladeSitzung: vi.fn(),
34 saveSession: vi.fn(),
35 addRecoveryPoint: vi.fn(),
36 saveProjectToFile: vi.fn(),
37 };
38 });
39
40 async function warte(ms = 0): Promise<void> {
41 await new Promise((fertig) => setTimeout(fertig, ms));
42 }
43
44 function offeneFenster(): string[] {
45 return [...document.querySelectorAll('.dialog-hintergrund h2')].map(
46 (h) => h.textContent?.trim() ?? '',
47 );
48 }
49
50 function feld(beschriftung: string): HTMLInputElement {
51 const label = [...document.querySelectorAll('label')].find(
52 (l) => l.textContent?.trim() === beschriftung,
53 );
54 const id = label?.getAttribute('for');
55 const eingabe = id === null || id === undefined ? null : document.getElementById(id);
56 if (!(eingabe instanceof HTMLInputElement)) {
57 throw new Error(`Eingabefeld "${beschriftung}" nicht gefunden.`);
58 }
59 return eingabe;
60 }
61
62 function druecke(taste: string): void {
63 window.dispatchEvent(
64 new KeyboardEvent('keydown', { key: taste, ctrlKey: true, bubbles: true, cancelable: true }),
65 );
66 }
67
68 beforeAll(async () => {
69 // jsdom kennt weder createObjectURL noch revokeObjectURL; der Rueckfallweg
70 // braucht beide, um ueberhaupt bis zu seiner Rueckgabe zu kommen.
71 Object.defineProperty(URL, 'createObjectURL', {
72 value: () => 'blob:pruefung',
73 configurable: true,
74 });
75 Object.defineProperty(URL, 'revokeObjectURL', { value: () => undefined, configurable: true });
76
77 vi.mocked(ladeSitzung).mockResolvedValue({ art: 'leer' });
78 vi.mocked(saveSession).mockResolvedValue({ ok: true, message: '' });
79 vi.mocked(saveProjectToFile).mockResolvedValue({
80 ok: true,
81 canceled: false,
82 filePath: null,
83 unbestaetigt: true,
84 message: 'Herunterladen angestoßen: Projekt.lsap',
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('Speichern ohne Desktop-Brücke', () => {
97 it('behauptet keinen bestätigten Schreibvorgang', async () => {
98 // Die echte Funktion, nicht die Attrappe: Ohne `window.lsaPlaner` faellt sie
99 // auf das Herunterladen im Browser zurueck.
100 const { saveProjectToFile: echtesSpeichern } =
101 await vi.importActual<typeof import('@/services/storage')>('@/services/storage');
102
103 const ergebnis = await echtesSpeichern(createEmptyProject('Bahnhofstraße'), null);
104
105 expect(
106 ergebnis.unbestaetigt,
107 'Der Rueckfallweg gibt einen Schreibvorgang als bestaetigt aus, den niemand bestaetigt hat',
108 ).toBe(true);
109 expect(ergebnis.filePath).toBeNull();
110 expect(ergebnis.message).not.toContain('Gespeichert');
111 });
112
113 it('lässt die Änderungsmarke stehen, statt "gespeichert" zu melden', async () => {
114 const name = feld('Projektbezeichnung');
115 name.value = 'Bahnhofstraße / Ringstraße';
116 name.dispatchEvent(new Event('change'));
117 await warte(0);
118
119 druecke('s');
120 await warte(20);
121 expect(saveProjectToFile).toHaveBeenCalledTimes(1);
122
123 // Die Probe: Steht der Stand in keiner nachweislich geschriebenen Datei,
124 // muss "Neu" zurueckfragen.
125 druecke('n');
126 await warte(10);
127 expect(offeneFenster(), 'Der Knotenpunkt liess sich ohne Rueckfrage verwerfen').toEqual([
128 'Ungespeicherte Änderungen',
129 ]);
130
131 document.querySelector<HTMLButtonElement>('.dialog-hintergrund button')?.click();
132 await warte(10);
133 });
134 });
135
136 describe('Ausgabe von PDF, CSV und Projektdatei ohne Desktop-Brücke', () => {
137 it('gibt denselben Vorbehalt aus wie das Speichern', async () => {
138 const { saveDataToFile } =
139 await vi.importActual<typeof import('@/services/storage')>('@/services/storage');
140
141 const ergebnis = await saveDataToFile(
142 'Signalgruppe;Räumweg\nK1;12,0\n',
143 'Bahnhofstraße - Zwischenzeiten.csv',
144 [{ name: 'CSV-Datei', extensions: ['csv'] }],
145 'text/csv',
146 );
147
148 expect(
149 ergebnis.unbestaetigt,
150 'Der Ausgabeweg gibt einen Schreibvorgang als bestaetigt aus, den niemand bestaetigt hat',
151 ).toBe(true);
152 expect(
153 ergebnis.message,
154 'Derselbe Vorgang wird in derselben Datei zweimal verschieden beschrieben',
155 ).toBe('Herunterladen angestoßen: Bahnhofstraße - Zwischenzeiten.csv');
156 });
157 });