lsa-planer

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

/ tests ui vergleichAnsicht.test.ts

5,8 KB Rohdatei
tests/ui/vergleichAnsicht.test.ts — 175 Zeilen
1 import { afterEach, 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 { vergessVergleichsprojekt, vergleichView } from '@/ui/views/vergleichView';
6 import { openProjectFromFile } from '@/services/storage';
7
8 /**
9 * Die Ansicht "Vergleich".
10 *
11 * DIE BEDINGUNG DES BEFUNDS: Die Ansicht bleibt LESEND. Kein Knopf, der einen
12 * Wert von der anderen Seite uebernimmt. Eine Zwischenzeit gehoert zu den
13 * Wegen, aus denen sie entstanden ist; herueberkopiert waere sie eine Zahl
14 * ohne Rechenweg - und der Ausdruck fuehrte sie als nachgewiesen.
15 */
16
17 vi.mock('@/services/storage', async (importOriginal) => {
18 const echt = await importOriginal<typeof import('@/services/storage')>();
19 return { ...echt, openProjectFromFile: vi.fn() };
20 });
21
22 const STICHTAG = new Date('2026-01-01T12:00:00Z');
23
24 function basis(): Project {
25 return createStandardIntersectionProject('Hauptstraße / Bahnhofstraße', STICHTAG);
26 }
27
28 function zeichne(project: Project): {
29 wurzel: HTMLElement;
30 store: ProjectStore;
31 aufraeumen: () => void;
32 } {
33 const wurzel = document.createElement('div');
34 document.body.append(wurzel);
35 const store = new ProjectStore(project);
36 const ende = vergleichView.render(wurzel, {
37 store,
38 refresh: () => {},
39 navigate: () => {},
40 target: null,
41 });
42 return {
43 wurzel,
44 store,
45 aufraeumen: () => {
46 ende();
47 wurzel.remove();
48 },
49 };
50 }
51
52 function knopf(wurzel: ParentNode, text: string): HTMLButtonElement {
53 const treffer = [...wurzel.querySelectorAll('button')].find(
54 (b) => b.textContent?.trim() === text,
55 );
56 if (!treffer) throw new Error(`Schaltfläche "${text}" nicht gefunden.`);
57 return treffer;
58 }
59
60 /** Laesst den naechsten "Öffnen"-Klick dieses Projekt liefern. */
61 function liefere(project: Project, optionen: { migriert?: boolean; fassung?: string } = {}): void {
62 vi.mocked(openProjectFromFile).mockResolvedValue({
63 ok: true,
64 canceled: false,
65 filePath: 'C:/Plaene/Bestand.lsap',
66 message: '',
67 result: {
68 project,
69 issues: [],
70 migrated: optionen.migriert ?? false,
71 repaired: false,
72 sourceVersion: optionen.fassung ?? '16',
73 intergreenComparison: [],
74 },
75 });
76 }
77
78 async function warte(): Promise<void> {
79 await new Promise((fertig) => setTimeout(fertig, 0));
80 }
81
82 afterEach(() => {
83 vergessVergleichsprojekt();
84 vi.mocked(openProjectFromFile).mockReset();
85 });
86
87 describe('Ansicht Vergleich', () => {
88 it('zeigt ohne Vergleichsprojekt einen Leerhinweis und keine Zahlen', () => {
89 const { wurzel, aufraeumen } = zeichne(basis());
90 try {
91 expect(wurzel.textContent).toContain('Noch kein Vergleichsprojekt geladen');
92 expect(wurzel.querySelectorAll('table')).toHaveLength(0);
93 expect(knopf(wurzel, 'Vergleichsprojekt öffnen')).toBeTruthy();
94 // Die Zusage steht auf der Seite und nicht nur im Quelltext.
95 expect(wurzel.textContent).toContain('Die Datei wird nur gelesen');
96 } finally {
97 aufraeumen();
98 }
99 });
100
101 it('stellt nach dem Öffnen beide Planfälle gegenüber', async () => {
102 const roh = basis();
103 const kuerzer: Project = {
104 ...roh,
105 conflicts: roh.conflicts.map((c) => ({
106 ...c,
107 clearingDistance: Math.max(0, c.clearingDistance - 5),
108 })),
109 };
110 // Geladen wird der Bestand mit den LAENGEREN Wegen; das offene Projekt hat
111 // die kuerzeren - also muss die Ansicht Verkuerzungen melden.
112 liefere(roh);
113 const { wurzel, aufraeumen } = zeichne(kuerzer);
114 try {
115 knopf(wurzel, 'Vergleichsprojekt öffnen').click();
116 await warte();
117 expect(wurzel.textContent).toContain('KÜRZER als im Vergleichsprojekt');
118 expect(wurzel.querySelectorAll('table').length).toBeGreaterThanOrEqual(2);
119 expect(wurzel.textContent).toContain('Bestand.lsap');
120 } finally {
121 aufraeumen();
122 }
123 });
124
125 it('bietet keinen Weg, einen Wert zu übernehmen', async () => {
126 /*
127 * DER WICHTIGSTE FALL DIESER DATEI. Geprueft wird nicht das Fehlen eines
128 * bestimmten Knopfes, sondern dass die Ansicht ueberhaupt nur zwei
129 * Schaltflaechen hat - und beide betreffen das Laden der Vergleichsdatei.
130 * Ein neuer Knopf, der etwas mit dem Projekt tut, macht diesen Fall rot.
131 */
132 liefere(basis());
133 const roh = basis();
134 const { wurzel, store, aufraeumen } = zeichne(roh);
135 try {
136 knopf(wurzel, 'Vergleichsprojekt öffnen').click();
137 await warte();
138 const beschriftungen = [...wurzel.querySelectorAll('button')].map((b) =>
139 (b.textContent ?? '').trim(),
140 );
141 expect(beschriftungen).toEqual(['Anderes Projekt öffnen', 'Vergleich schließen']);
142 expect(wurzel.querySelectorAll('input, select, textarea')).toHaveLength(0);
143 // Und das Projekt selbst ist unberuehrt geblieben.
144 expect(store.getProject()).toBe(roh);
145 } finally {
146 aufraeumen();
147 }
148 });
149
150 it('nennt den Vorbehalt zu einem hochgezogenen Altprojekt', async () => {
151 liefere(basis(), { migriert: true, fassung: '4.3.3' });
152 const { wurzel, aufraeumen } = zeichne(basis());
153 try {
154 knopf(wurzel, 'Vergleichsprojekt öffnen').click();
155 await warte();
156 expect(wurzel.textContent).toContain('NEU GERECHNET');
157 } finally {
158 aufraeumen();
159 }
160 });
161
162 it('lässt den Vergleich wieder schließen', async () => {
163 liefere(basis());
164 const { wurzel, aufraeumen } = zeichne(basis());
165 try {
166 knopf(wurzel, 'Vergleichsprojekt öffnen').click();
167 await warte();
168 knopf(wurzel, 'Vergleich schließen').click();
169 expect(wurzel.textContent).toContain('Noch kein Vergleichsprojekt geladen');
170 expect(wurzel.querySelectorAll('table')).toHaveLength(0);
171 } finally {
172 aufraeumen();
173 }
174 });
175 });