lsa-planer

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

/ tests domain fahrbeziehung.test.ts

9,0 KB Rohdatei
tests/domain/fahrbeziehung.test.ts — 237 Zeilen
1 import { readFileSync } from 'node:fs';
2 import { describe, expect, it } from 'vitest';
3 import {
4 defaultMovement,
5 fahrbeziehungZurVerkehrsart,
6 zulaessigeFahrbeziehungen,
7 } from '@/domain/model/factory';
8 import { parseProject } from '@/domain/model/schema';
9 import { clearingSpeedFor } from '@/domain/rilsa/intergreen';
10 import type { Movement, TrafficMode } from '@/domain/rilsa/types';
11
12 /*
13 * Fassung 5.10.0 - die Zuordnung Fahrbeziehung zu Verkehrsart gehoert in den
14 * Fachkern.
15 *
16 * Befund 44 hat die Auswahlliste an die Verkehrsart gebunden, die Zuordnung
17 * dabei aber in der Oberflaeche abgelegt: Sie stand in
18 * `src/ui/views/signalGroupsView.ts`, und `lageplanView.ts` holte
19 * `movementOptions` und `fahrbeziehungZurVerkehrsart` von dort. Damit war sie
20 * fuer das Einlesen nicht erreichbar - `oneOf` in `model/schema.ts` prueft die
21 * Fahrbeziehung gegen die Werteliste, nicht gegen die Verkehrsart. Eine
22 * Projektdatei mit "querung" an einer Kfz-Gruppe oder "links" an einer Furt kam
23 * ohne ein Wort durch, waehrend die Oberflaeche denselben Eintrag abfing.
24 *
25 * WAS DIE ERSETZUNG AENDERT: die AUSGEWIESENE Fahrbeziehung, nicht die
26 * Zwischenzeit. `clearingSpeedFor` verzweigt ueber `mode`; eine Furt raeumt mit
27 * 1,2 m/s, auch wenn "geradeaus" daran steht. Der letzte Fall hier haelt das
28 * fest - die Behebung geht in keine der beiden Sicherheitsrichtungen.
29 */
30
31 const ALLE_ARTEN: readonly TrafficMode[] = ['kfz', 'rad', 'fuss', 'oepnv'];
32
33 /** Ein eingelesenes Projekt mit genau einer Signalgruppe. */
34 function gruppeEinlesen(mode: TrafficMode, movement: unknown) {
35 const { project, issues, repaired } = parseProject({
36 schemaVersion: 13,
37 signalGroups: [
38 {
39 id: 'sg-1',
40 name: 'K1',
41 mode,
42 movement,
43 armId: null,
44 vZul: 50,
45 lanes: 1,
46 vehicleClass: mode === 'fuss' ? 'keine' : 'pkw',
47 minGreenOverride: null,
48 maxGreenOverride: null,
49 reducedMobility: false,
50 color: '#2f6fd0',
51 },
52 ],
53 });
54 const meldungen = issues.filter((i) => i.path === 'signalGroups[0].movement');
55 return { movement: project.signalGroups[0]?.movement, meldungen, repaired };
56 }
57
58 /** Ein eingelesenes Projekt mit genau einer Fahrlinie im Lageplan. */
59 function linieEinlesen(mode: TrafficMode, movement: unknown) {
60 const { project, issues } = parseProject({
61 schemaVersion: 13,
62 lageplan: {
63 bild: null,
64 kalibrierung: null,
65 haltlinien: [],
66 linien: [
67 {
68 id: 'pl-1',
69 name: 'Linie 1',
70 mode,
71 movement,
72 breiteMeter: 3.25,
73 punkte: [
74 { x: 0, y: 0 },
75 { x: 100, y: 0 },
76 ],
77 haltlinieId: null,
78 startT: 0.5,
79 signalGroupId: null,
80 },
81 ],
82 },
83 });
84 const meldungen = issues.filter((i) => i.path === 'lageplan.linien[0].movement');
85 return { movement: project.lageplan.linien[0]?.movement, meldungen };
86 }
87
88 describe('Der Fachkern sagt, welche Fahrbeziehung zu welcher Verkehrsart passt', () => {
89 it('laesst nur die Furt queren', () => {
90 expect(zulaessigeFahrbeziehungen('fuss')).toEqual(['querung']);
91 });
92
93 it('gibt Kfz- und OePNV-Strömen drei Fahrbeziehungen', () => {
94 for (const mode of ['kfz', 'oepnv'] as const) {
95 expect(zulaessigeFahrbeziehungen(mode), mode).toEqual(['geradeaus', 'rechts', 'links']);
96 }
97 });
98
99 it('laesst den Radverkehr auch queren', () => {
100 /*
101 * NICHT wie vorgeschlagen dieselbe Liste wie beim Kraftfahrzeugverkehr:
102 * Die Radquerung ist ein gefuehrter Fall des Programms -
103 * `geometrie/lageplan.ts` nennt zur Haltlinienart 'querung' "Furt oder
104 * Radquerung", und `validation/engine.ts` verlangt von einer Anlage "nur
105 * aus Furten und Radfurten" keine Verkehrsstaerken. Auch die Hin- und
106 * Rueckwegprobe der Projektdatei fuehrt eine Radgruppe mit 'querung' und
107 * erwartet dazu keine Meldung.
108 */
109 expect(zulaessigeFahrbeziehungen('rad')).toEqual(['geradeaus', 'rechts', 'links', 'querung']);
110 // Die Vorbelegung bleibt trotzdem 'geradeaus' - so steht es in
111 // validation/rules.ts.
112 expect(defaultMovement('rad')).toBe('geradeaus');
113 });
114
115 it('nennt keine Verkehrsart ohne Fahrbeziehung', () => {
116 for (const mode of ALLE_ARTEN) {
117 expect(zulaessigeFahrbeziehungen(mode).length, mode).toBeGreaterThan(0);
118 }
119 });
120
121 it('setzt die Standardfahrbeziehung als erste der zulaessigen', () => {
122 // Ein zweiter, danebenstehender Rueckfallwert waere eine zweite Fassung
123 // derselben Zuordnung.
124 for (const mode of ALLE_ARTEN) {
125 expect(defaultMovement(mode), mode).toBe(zulaessigeFahrbeziehungen(mode)[0]);
126 }
127 });
128
129 it('behaelt beim Wechsel der Verkehrsart, was weiter passt', () => {
130 expect(fahrbeziehungZurVerkehrsart('kfz', 'links')).toBe('links');
131 expect(fahrbeziehungZurVerkehrsart('rad', 'rechts')).toBe('rechts');
132 });
133
134 it('ersetzt beim Wechsel der Verkehrsart, was nicht mehr passt', () => {
135 expect(fahrbeziehungZurVerkehrsart('kfz', 'querung')).toBe('geradeaus');
136 expect(fahrbeziehungZurVerkehrsart('fuss', 'links')).toBe('querung');
137 });
138
139 it('macht aus einer Furt beim Wechsel auf Radverkehr eine Radquerung', () => {
140 expect(fahrbeziehungZurVerkehrsart('rad', 'querung')).toBe('querung');
141 });
142 });
143
144 describe('Das Einlesen prueft die Fahrbeziehung gegen die Verkehrsart', () => {
145 it('ersetzt "querung" an der Kfz-Gruppe und meldet die Ersetzung', () => {
146 const { movement, meldungen, repaired } = gruppeEinlesen('kfz', 'querung');
147
148 expect(movement).toBe('geradeaus');
149 expect(meldungen).toHaveLength(1);
150 expect(meldungen[0]?.severity).toBe('warnung');
151 expect(meldungen[0]?.message).toContain('querung');
152 expect(meldungen[0]?.message).toContain('unzulässig');
153 expect(meldungen[0]?.message).toContain('Signalgruppe');
154 // Die Datei ist veraendert worden; der Anwender muss das beim Speichern
155 // wissen.
156 expect(repaired).toBe(true);
157 });
158
159 it('ersetzt "links" an der Fussgaengergruppe und meldet die Ersetzung', () => {
160 const { movement, meldungen } = gruppeEinlesen('fuss', 'links');
161
162 expect(movement).toBe('querung');
163 expect(meldungen).toHaveLength(1);
164 expect(meldungen[0]?.message).toContain('querung');
165 });
166
167 it('ersetzt "querung" an der Kfz-Fahrlinie des Lageplans und meldet es', () => {
168 const { movement, meldungen } = linieEinlesen('kfz', 'querung');
169
170 expect(movement).toBe('geradeaus');
171 expect(meldungen).toHaveLength(1);
172 expect(meldungen[0]?.message).toContain('Fahrlinie');
173 });
174
175 it('ersetzt "geradeaus" an der Fusslinie des Lageplans und meldet es', () => {
176 const { movement, meldungen } = linieEinlesen('fuss', 'geradeaus');
177
178 expect(movement).toBe('querung');
179 expect(meldungen).toHaveLength(1);
180 });
181
182 it('meldet eine unbekannte Fahrbeziehung weiterhin nur einmal', () => {
183 // `oneOf` ersetzt sie bereits durch die Standardfahrbeziehung. Zwei
184 // Meldungen zu einem Eintrag waeren eine zu viel.
185 const { movement, meldungen } = gruppeEinlesen('fuss', 'wenden');
186
187 expect(movement).toBe('querung');
188 expect(meldungen).toHaveLength(1);
189 expect(meldungen[0]?.message).toContain('unbekannt');
190 });
191 });
192
193 describe('Gegenproben - was unveraendert bleibt', () => {
194 it('laesst jede zulaessige Fahrbeziehung stehen und schweigt dazu', () => {
195 for (const mode of ALLE_ARTEN) {
196 for (const movement of zulaessigeFahrbeziehungen(mode)) {
197 const gelesen = gruppeEinlesen(mode, movement);
198 expect(gelesen.movement, `${mode}/${movement}`).toBe(movement);
199 expect(gelesen.meldungen, `${mode}/${movement}`).toHaveLength(0);
200 }
201 }
202 });
203
204 it('aendert an der Raeumgeschwindigkeit nichts - die entscheidet die Verkehrsart', () => {
205 // Beide Ersetzungen wiegen die Zwischenzeit nicht auf: Die Furt raeumt mit
206 // ihren 1,2 m/s, gleich welche Fahrbeziehung an ihr steht, und der
207 // querende Kfz-Strom raeumte schon vorher wie ein geradeausfahrender.
208 const furt = (movement: Movement) =>
209 clearingSpeedFor({ mode: 'fuss', movement, reducedMobility: false });
210 expect(furt('geradeaus')).toBe(furt('querung'));
211
212 const kfz = (movement: Movement) =>
213 clearingSpeedFor({ mode: 'kfz', movement, reducedMobility: false });
214 expect(kfz('querung')).toBe(kfz('geradeaus'));
215 });
216 });
217
218 describe('Die Zuordnung steht an einer Stelle', () => {
219 const SIGNALGRUPPEN = readFileSync('src/ui/views/signalGroupsView.ts', 'utf8');
220 const LAGEPLAN = readFileSync('src/ui/views/lageplanView.ts', 'utf8');
221
222 it('fuehrt in keiner der beiden Ansichten eine eigene Werteliste', () => {
223 // Gesucht wird die Liste selbst, nicht ihr Name: Eine zweite Fassung
224 // entstuende als Aufzaehlung im Quelltext.
225 for (const [name, quelle] of [
226 ['signalGroupsView.ts', SIGNALGRUPPEN],
227 ['lageplanView.ts', LAGEPLAN],
228 ] as const) {
229 expect(quelle, name).not.toMatch(/'geradeaus',\s*'rechts',\s*'links'/);
230 expect(quelle, name).not.toMatch(/'querung'\s*\]/);
231 }
232 });
233
234 it('bindet die Lageplanansicht die Signalgruppentabelle nicht mehr ein', () => {
235 expect(LAGEPLAN).not.toContain("from './signalGroupsView'");
236 });
237 });