lsa-planer

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

/ tests ui ausgabeOhneNachweis.test.ts

5,1 KB Rohdatei
tests/ui/ausgabeOhneNachweis.test.ts — 140 Zeilen
1 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2 import { ProjectStore } from '@/app/store';
3 import { createStandardIntersectionProject } from '@/domain/model/factory';
4 import type { Project } from '@/domain/model/project';
5 import { saveDataToFile } from '@/services/storage';
6 import type { ViewContext } from '@/ui/shell';
7 import { exportView } from '@/ui/views/exportView';
8
9 /**
10 * Befund 23 zur Fassung 5.9.0, Folge in der Ausgabeansicht.
11 *
12 * Ohne Desktop-Bruecke - `npm run dev` im Browser, und im Auslieferungsstand
13 * dann, wenn `preload.js` unter `sandbox: true` stumm scheitert - faellt
14 * `saveDataToFile` auf das Herunterladen im Browser zurueck. Seit Befund 23
15 * traegt das Ergebnis dafuer `unbestaetigt: true` und die Meldung
16 * "Herunterladen angestoßen"; ob die Datei wirklich abgelegt wurde, erfaehrt
17 * die Anwendung nicht (der Anwender bricht den Speichern-unter-Kasten ab, eine
18 * Unternehmensrichtlinie sperrt den Download).
19 *
20 * Alle drei Ausgabewege dieser Ansicht meldeten das Ergebnis dennoch als
21 * 'erfolg' - in Gruen, wie einen abgelegten Ausdruck. `speichereEinmal` in
22 * src/main.ts behandelt denselben Fall laengst als Warnung und sagt dazu, dass
23 * sich die Ablage nicht feststellen laesst. Wer die Planunterlage einer
24 * Behoerde vorlegen soll, darf eine gruene Meldung nicht als Nachweis lesen.
25 */
26
27 vi.mock('@/services/storage', async (importOriginal) => {
28 const echt = await importOriginal<typeof import('@/services/storage')>();
29 return { ...echt, saveDataToFile: vi.fn() };
30 });
31
32 const FESTES_DATUM = new Date('2026-01-01T00:00:00Z');
33
34 let aufraeumen: (() => void) | null = null;
35
36 beforeEach(() => {
37 vi.mocked(saveDataToFile).mockReset();
38 });
39
40 afterEach(() => {
41 aufraeumen?.();
42 aufraeumen = null;
43 document.body.replaceChildren();
44 });
45
46 function zeichne(project: Project): HTMLElement {
47 const wurzel = document.createElement('div');
48 document.body.append(wurzel);
49 const store = new ProjectStore(project);
50 const context: ViewContext = {
51 store,
52 refresh: () => {},
53 navigate: () => {},
54 target: null,
55 };
56 const abmelden = exportView.render(wurzel, context);
57 aufraeumen = () => {
58 abmelden();
59 wurzel.remove();
60 };
61 return wurzel;
62 }
63
64 function knopf(wurzel: ParentNode, beschriftung: string): HTMLButtonElement {
65 const gefunden = [...wurzel.querySelectorAll<HTMLButtonElement>('button')].find(
66 (b) => b.textContent?.trim() === beschriftung,
67 );
68 if (gefunden === undefined) throw new Error(`Kein Knopf "${beschriftung}"`);
69 return gefunden;
70 }
71
72 async function warte(ms = 0): Promise<void> {
73 await new Promise((fertig) => setTimeout(fertig, ms));
74 }
75
76 /** Die zuletzt angezeigte Meldung: Art und Text. */
77 function letzteMeldung(): { readonly art: string; readonly text: string } {
78 const meldungen = [...document.querySelectorAll('.meldung')];
79 const letzte = meldungen[meldungen.length - 1];
80 if (letzte === undefined) throw new Error('Keine Meldung angezeigt');
81 const art = [...letzte.classList].find((k) => k.startsWith('meldung-')) ?? '';
82 return { art: art.replace('meldung-', ''), text: letzte.textContent ?? '' };
83 }
84
85 const OHNE_NACHWEIS = {
86 ok: true,
87 canceled: false,
88 filePath: null,
89 unbestaetigt: true,
90 message: 'Herunterladen angestoßen: Knotenpunkt - Prüfbericht.csv',
91 } as const;
92
93 const ABGELEGT = {
94 ok: true,
95 canceled: false,
96 filePath: 'C:/Plaene/Knotenpunkt - Prüfbericht.csv',
97 message: 'Gespeichert: C:/Plaene/Knotenpunkt - Prüfbericht.csv',
98 } as const;
99
100 describe('Ausgabe ohne Desktop-Brücke: die Meldung behauptet keine Ablage', () => {
101 it('meldet eine Tabelle als Warnung mit dem Vorbehalt', async () => {
102 vi.mocked(saveDataToFile).mockResolvedValue(OHNE_NACHWEIS);
103 const wurzel = zeichne(createStandardIntersectionProject('Knotenpunkt', FESTES_DATUM));
104
105 knopf(wurzel, 'Prüfbericht').click();
106 await warte(10);
107
108 const meldung = letzteMeldung();
109 expect(meldung.text).toContain('Herunterladen angestoßen');
110 expect(meldung.text, 'Der Vorbehalt aus speichereEinmal fehlt').toContain('nicht feststellen');
111 expect(meldung.art, 'Ein unbestätigter Vorgang wird in Grün gemeldet').toBe('warnung');
112 });
113
114 it('meldet die Projektdatei ebenso', async () => {
115 vi.mocked(saveDataToFile).mockResolvedValue(OHNE_NACHWEIS);
116 const wurzel = zeichne(createStandardIntersectionProject('Knotenpunkt', FESTES_DATUM));
117
118 knopf(wurzel, 'Projektdatei ausgeben').click();
119 await warte(10);
120
121 const meldung = letzteMeldung();
122 expect(meldung.text).toContain('nicht feststellen');
123 expect(meldung.art).toBe('warnung');
124 });
125
126 it('meldet einen nachgewiesenen Schreibvorgang weiterhin als Erfolg', async () => {
127 // Grenzwache: Der Regelweg ueber die Desktop-Bruecke meldet einen Pfad
128 // zurueck; dort ist die Ablage belegt und der Vorbehalt waere falsch.
129 vi.mocked(saveDataToFile).mockResolvedValue(ABGELEGT);
130 const wurzel = zeichne(createStandardIntersectionProject('Knotenpunkt', FESTES_DATUM));
131
132 knopf(wurzel, 'Prüfbericht').click();
133 await warte(10);
134
135 const meldung = letzteMeldung();
136 expect(meldung.text).toContain('Gespeichert');
137 expect(meldung.text).not.toContain('nicht feststellen');
138 expect(meldung.art).toBe('erfolg');
139 });
140 });