lsa-planer

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

/ tests domain signalgeber.test.ts

6,7 KB Rohdatei
tests/domain/signalgeber.test.ts — 144 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { createEmptyProject } from '@/domain/model/factory';
3 import * as actions from '@/app/actions';
4 import { parseProject } from '@/domain/model/schema';
5 import { serializeProject } from '@/services/storage';
6 import { CURRENT_SCHEMA_VERSION, type Project } from '@/domain/model/project';
7 import { fahrlinienVon } from '@/domain/geometrie/lageplan';
8 import { buildSignalPlan } from '@/domain/plan/signalPlan';
9 import type { Signalgeber } from '@/domain/geometrie/lageplan';
10
11 /*
12 * STUFE 3 DES SIGNALLAGEPLAN-ENTWURFS: DER SIGNALGEBER WIRD GEZEICHNET UND
13 * RECHNET NICHT MIT.
14 *
15 * Der zweite Teil des Satzes ist der wichtigere, und er ist die Bedingung,
16 * unter der es das Objekt ueberhaupt gibt. Aus einem Signalgeberstandort
17 * LIESSE sich rechnen - der Abstand zur Haltlinie, eine Sichtweite, ein
18 * Raeumweganfang -, und jede dieser Groessen beruehrt die Zwischenzeit. Was
19 * Zwischenzeiten verkuerzt, ist die gefaehrliche Richtung; solange die
20 * Regelwerke zu Aufstellung und Abstaenden nicht im Original vorliegen, wird
21 * gezeichnet und nicht gerechnet.
22 *
23 * Diese Datei haelt das fest, damit es nicht spaeter still in eine Ableitung
24 * wandert. Dasselbe tut `tests/domain/arbeitsbereich.test.ts` fuer das Band -
25 * und dort war es die richtige Entscheidung, wie der Vergleich zeigt.
26 */
27
28 const DATUM = new Date('2026-09-12T10:00:00Z');
29
30 const GEBER: Signalgeber = {
31 id: 'sg-1',
32 name: 'K1',
33 punkt: { x: 120, y: 80 },
34 ausrichtung: 90,
35 signalGroupId: null,
36 };
37
38 function projektMitGeber(geber: Signalgeber = GEBER): Project {
39 const basis = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig');
40 return actions.addSignalgeber(basis, geber);
41 }
42
43 describe('Signalgeber im Lageplan', () => {
44 it('legt an, ändert und löscht über reine Funktionen', () => {
45 const mit = projektMitGeber();
46 expect(mit.lageplan.signalgeber).toHaveLength(1);
47
48 const umbenannt = actions.setSignalgeber(mit, 'sg-1', { name: 'K2' });
49 expect(umbenannt.lageplan.signalgeber[0]!.name).toBe('K2');
50 // Nichts wird an Ort und Stelle veraendert.
51 expect(mit.lageplan.signalgeber[0]!.name).toBe('K1');
52
53 expect(actions.removeSignalgeber(mit, 'sg-1').lageplan.signalgeber).toHaveLength(0);
54 });
55
56 it('erscheint in keiner Fahrlinie und erzeugt keine Konfliktbeziehung', () => {
57 /*
58 * DIE ENTSCHEIDENDE PRUEFUNG. `fahrlinienVon` ist die Stelle, an der aus
59 * der Zeichnung Vermessung wird - Kreuzungspunkte, Konfliktbereiche,
60 * Raeum- und Einfahrwege. Was dort auftaucht, rechnet mit.
61 */
62 const projekt = projektMitGeber();
63 expect(fahrlinienVon(projekt.lageplan)).toHaveLength(0);
64 expect(projekt.conflicts).toHaveLength(0);
65 });
66
67 it('ändert keine einzige Zahl des Signalzeitenplans', () => {
68 const ohne = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig');
69 const mit = actions.addSignalgeber(ohne, GEBER);
70 const planOhne = buildSignalPlan(ohne);
71 const planMit = buildSignalPlan(mit);
72 expect(planMit.cycleTime).toBe(planOhne.cycleTime);
73 expect(planMit.notes.map((n) => n.code)).toEqual(planOhne.notes.map((n) => n.code));
74 });
75
76 it('überlebt Speichern und Einlesen unverändert', () => {
77 const projekt = projektMitGeber({ ...GEBER, ausrichtung: 217 });
78 const gelesen = parseProject(JSON.parse(serializeProject(projekt)));
79 expect(gelesen.project.schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
80 expect(gelesen.project.lageplan.signalgeber).toEqual(projekt.lageplan.signalgeber);
81 expect(gelesen.issues).toEqual([]);
82 });
83
84 it('zieht die Ausrichtung beim Einlesen auf 0 bis unter 360 Grad', () => {
85 // Ein Wert von 400 oder -20 aus fremder Datei ist kein Fehler, sondern
86 // dieselbe Richtung anders geschrieben. Eine Meldung darueber waere Laerm.
87 const roh = JSON.parse(serializeProject(projektMitGeber())) as Record<string, unknown>;
88 const lageplan = (roh['lageplan'] as Record<string, unknown>) ?? {};
89 (lageplan['signalgeber'] as Record<string, unknown>[])[0]!['ausrichtung'] = 400;
90 const gelesen = parseProject(roh);
91 expect(gelesen.project.lageplan.signalgeber[0]!.ausrichtung).toBe(40);
92 });
93
94 it('behält den Standort, wenn die Signalgruppe gelöscht wird', () => {
95 /*
96 * Der Standort IST die Aussage des Objekts, die Verknuepfung nur seine
97 * Beschriftung. Faellt die Gruppe fort, faellt der Verweis - nicht der
98 * gezeichnete Punkt, den jemand vermessen hat.
99 */
100 const basis = createEmptyProject('Knoten', DATUM);
101 const mitGruppe = actions.addSignalGroup(basis, 'kfz');
102 const gruppe = mitGruppe.signalGroups[0]!;
103 const mitGeber = actions.addSignalgeber(mitGruppe, { ...GEBER, signalGroupId: gruppe.id });
104
105 const ohneGruppe = actions.removeSignalGroup(mitGeber, gruppe.id);
106 expect(ohneGruppe.lageplan.signalgeber).toHaveLength(1);
107 expect(ohneGruppe.lageplan.signalgeber[0]!.punkt).toEqual(GEBER.punkt);
108 expect(ohneGruppe.lageplan.signalgeber[0]!.signalGroupId).toBeNull();
109 });
110
111 it('hebt einen Verweis auf eine nicht vorhandene Signalgruppe beim Einlesen auf', () => {
112 const roh = JSON.parse(serializeProject(projektMitGeber())) as Record<string, unknown>;
113 const lageplan = (roh['lageplan'] as Record<string, unknown>) ?? {};
114 (lageplan['signalgeber'] as Record<string, unknown>[])[0]!['signalGroupId'] = 'gibt-es-nicht';
115 const gelesen = parseProject(roh);
116 expect(gelesen.project.lageplan.signalgeber[0]!.signalGroupId).toBeNull();
117 expect(gelesen.project.lageplan.signalgeber[0]!.punkt).toEqual(GEBER.punkt);
118 expect(gelesen.issues.some((i) => i.path.includes('signalgeber'))).toBe(true);
119 });
120
121 it('lässt einen Signalgeber ohne brauchbaren Standort aus und sagt es', () => {
122 // Ein Punkt ohne Koordinaten ist kein Standort. Ihn auf 0,0 zu setzen
123 // hiesse, eine Aufstellung an der Bildecke zu behaupten.
124 const roh = JSON.parse(serializeProject(projektMitGeber())) as Record<string, unknown>;
125 const lageplan = (roh['lageplan'] as Record<string, unknown>) ?? {};
126 (lageplan['signalgeber'] as Record<string, unknown>[])[0]!['punkt'] = { x: 'weit', y: null };
127 const gelesen = parseProject(roh);
128 expect(gelesen.project.lageplan.signalgeber).toHaveLength(0);
129 expect(gelesen.issues.some((i) => i.message.includes('Standort'))).toBe(true);
130 });
131
132 it('gibt einer Altdatei aus Schema 18 eine leere Liste', () => {
133 // Das ist die Wahrheit ueber sie: Dort war nichts erfasst. Ein Nachtrag
134 // waere eine erfundene Aufstellung.
135 const roh = JSON.parse(serializeProject(createEmptyProject('Alt', DATUM))) as Record<
136 string,
137 unknown
138 >;
139 roh['schemaVersion'] = 18;
140 delete (roh['lageplan'] as Record<string, unknown>)['signalgeber'];
141 const gelesen = parseProject(roh);
142 expect(gelesen.project.lageplan.signalgeber).toEqual([]);
143 });
144 });