lsa-planer

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

/ tests ui reihenfolge.test.ts

6,2 KB Rohdatei
tests/ui/reihenfolge.test.ts — 161 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { projectView } from '@/ui/views/projectView';
3 import { signalGroupsView } from '@/ui/views/signalGroupsView';
4 import { compatibilityView, zaehleFeindlichePaare } from '@/ui/views/compatibilityView';
5 import { phasesView } from '@/ui/views/phasesView';
6 import { conflictsView } from '@/ui/views/conflictsView';
7 import { planView } from '@/ui/views/planView';
8 import { simulationView } from '@/ui/views/simulationView';
9 import { reportView } from '@/ui/views/reportView';
10 import { exportView } from '@/ui/views/exportView';
11 import { settingsView } from '@/ui/views/settingsView';
12 import { vergleichView } from '@/ui/views/vergleichView';
13 import { koordinierungView } from '@/ui/views/koordinierungView';
14 import { naechsterSchritt } from '@/ui/naechsterSchritt';
15 import { buildSignalPlan } from '@/domain/plan/signalPlan';
16 import { validateProject } from '@/domain/validation';
17 import * as actions from '@/app/actions';
18 import { createEmptyProject } from '@/domain/model/factory';
19 import { ASSISTENT_VORGABE, projektAusAngaben } from '@/ui/assistent';
20 import type { Project } from '@/domain/model/project';
21 import type { AppState } from '@/app/store';
22 import type { ViewDefinition } from '@/ui/shell';
23
24 const FIXED_DATE = new Date('2026-01-01T00:00:00Z');
25
26 /** Dieselbe Reihenfolge, in der main.ts die Ansichten anmeldet. */
27 const REIHENFOLGE: readonly ViewDefinition[] = [
28 projectView,
29 signalGroupsView,
30 compatibilityView,
31 phasesView,
32 conflictsView,
33 planView,
34 simulationView,
35 koordinierungView,
36 vergleichView,
37 reportView,
38 exportView,
39 settingsView,
40 ];
41
42 function zustand(project: Project): AppState {
43 const plan = buildSignalPlan(project);
44 return {
45 project,
46 plan,
47 report: validateProject(project, plan, FIXED_DATE),
48 dirty: false,
49 filePath: null,
50 canUndo: false,
51 canRedo: false,
52 undoLabel: null,
53 redoLabel: null,
54 };
55 }
56
57 describe('Reihenfolge der Ansichten', () => {
58 it('folgt der fachlichen Abhaengigkeit', () => {
59 const ids = REIHENFOLGE.map((v) => v.id);
60
61 // Signalgruppen vor allem, was auf ihnen aufbaut.
62 expect(ids.indexOf('signalgruppen')).toBeLessThan(ids.indexOf('vertraeglichkeit'));
63 // Vertraeglichkeit VOR den Phasen: sonst kann nicht geprueft werden, ob
64 // zwei feindliche Gruppen in derselben Phase liegen.
65 expect(ids.indexOf('vertraeglichkeit')).toBeLessThan(ids.indexOf('phasen'));
66 // Vermassen ist Detailarbeit und folgt der Struktur.
67 expect(ids.indexOf('phasen')).toBeLessThan(ids.indexOf('zwischenzeiten'));
68 // Ergebnisse nach allen Eingaben.
69 expect(ids.indexOf('zwischenzeiten')).toBeLessThan(ids.indexOf('signalplan'));
70 expect(ids.indexOf('signalplan')).toBeLessThan(ids.indexOf('pruefung'));
71 expect(ids.indexOf('pruefung')).toBeLessThan(ids.indexOf('export'));
72 });
73
74 it('gliedert die Navigation in zusammenhaengende Bloecke', () => {
75 const gruppen = REIHENFOLGE.map((v) => v.group);
76 expect(new Set(gruppen)).toEqual(
77 new Set(['Erfassung', 'Ergebnis', 'Abschluss', 'Einstellungen']),
78 );
79
80 // Eine Gruppe darf nicht unterbrochen werden, sonst zerfaellt die
81 // Ueberschrift in der Navigation in mehrere Bloecke.
82 const bloecke: string[] = [];
83 for (const gruppe of gruppen) {
84 if (bloecke[bloecke.length - 1] !== gruppe) bloecke.push(gruppe);
85 }
86 expect(bloecke).toEqual(['Erfassung', 'Ergebnis', 'Abschluss', 'Einstellungen']);
87 });
88
89 it('vergibt eindeutige Kennungen und Beschriftungen', () => {
90 const ids = REIHENFOLGE.map((v) => v.id);
91 const labels = REIHENFOLGE.map((v) => v.label);
92 expect(new Set(ids).size).toBe(ids.length);
93 expect(new Set(labels).size).toBe(labels.length);
94 for (const view of REIHENFOLGE) {
95 expect(view.description.length, view.id).toBeGreaterThan(20);
96 }
97 });
98
99 it('fuehrt der naechste Schritt durch dieselbe Reihenfolge', () => {
100 const ids = REIHENFOLGE.map((v) => v.id);
101 let project = createEmptyProject('Ablauf', FIXED_DATE);
102 const besucht: string[] = [];
103
104 // Leeres Projekt -> Signalgruppen
105 besucht.push(naechsterSchritt(zustand(project)).ansicht);
106
107 // Zwei Signalgruppen -> Vertraeglichkeit
108 project = actions.addSignalGroup(actions.addSignalGroup(project, 'kfz'), 'kfz');
109 besucht.push(naechsterSchritt(zustand(project)).ansicht);
110
111 // Konflikt erfasst, aber keine Phase -> Phasen
112 const [a, b] = project.signalGroups;
113 project = actions.toggleConflictPair(project, a!.id, b!.id);
114 project = {
115 ...project,
116 conflicts: project.conflicts.map((c) => ({
117 ...c,
118 clearingDistance: 20,
119 enteringDistance: 8,
120 })),
121 };
122 besucht.push(naechsterSchritt(zustand(project)).ansicht);
123
124 expect(besucht).toEqual(['signalgruppen', 'vertraeglichkeit', 'phasen']);
125 // Die Fuehrung darf nie rueckwaerts durch die Navigation springen.
126 const positionen = besucht.map((id) => ids.indexOf(id));
127 for (let i = 1; i < positionen.length; i += 1) {
128 expect(positionen[i]!).toBeGreaterThan(positionen[i - 1]!);
129 }
130 });
131 });
132
133 describe('Vertraeglichkeit', () => {
134 it('zaehlt Paare ungerichtet', () => {
135 let project = createEmptyProject('Paare', FIXED_DATE);
136 project = actions.addSignalGroup(actions.addSignalGroup(project, 'kfz'), 'kfz');
137 const [a, b] = project.signalGroups;
138
139 expect(zaehleFeindlichePaare(project)).toBe(0);
140 project = actions.toggleConflictPair(project, a!.id, b!.id);
141 // Zwei gerichtete Beziehungen, aber nur ein Paar.
142 expect(project.conflicts).toHaveLength(2);
143 expect(zaehleFeindlichePaare(project)).toBe(1);
144
145 project = actions.toggleConflictPair(project, a!.id, b!.id);
146 expect(zaehleFeindlichePaare(project)).toBe(0);
147 });
148
149 it('stimmt mit dem Ergebnis des Assistenten ueberein', () => {
150 const projekt = projektAusAngaben(ASSISTENT_VORGABE, 'x', FIXED_DATE);
151 // Jede erfasste Beziehung muss eine Gegenrichtung haben - sonst waere die
152 // paarweise Darstellung nicht deckungsgleich mit den Daten.
153 for (const c of projekt.conflicts) {
154 expect(
155 projekt.conflicts.some((g) => g.fromId === c.toId && g.toId === c.fromId),
156 `${c.fromId} -> ${c.toId} ohne Gegenrichtung`,
157 ).toBe(true);
158 }
159 expect(zaehleFeindlichePaare(projekt) * 2).toBe(projekt.conflicts.length);
160 });
161 });