lsa-planer

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

/ tests domain kennungMitTrennzeichen.test.ts

5,6 KB Rohdatei
tests/domain/kennungMitTrennzeichen.test.ts — 127 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { createConflict, createEmptyProject, createSignalGroup } from '@/domain/model/factory';
3 import type { Project } from '@/domain/model/project';
4 import { parseProject } from '@/domain/model/schema';
5 import { buildSignalPlan, intergreenKey } from '@/domain/plan/signalPlan';
6
7 /*
8 * Befund 61, behoben in Fassung 5.10.0 - zusammengesetzte Schluessel aus
9 * Signalgruppenkennungen waren mehrdeutig.
10 *
11 * `intergreenKey` verband zwei Kennungen mit "|", `parseConflicts` mit "->".
12 * Beim Einlesen wird eine Kennung nur auf leer und auf Doppelvergabe geprueft,
13 * nicht auf ein Zeichenrepertoire - sie kommt unveraendert aus der Datei. Fuer
14 * die Kennungen "a|b", "c", "a" und "b|c" lieferte intergreenKey("a|b","c")
15 * denselben Schluessel wie intergreenKey("a","b|c"): Die Karte behielt nur den
16 * zuletzt geschriebenen Eintrag, und die andere Beziehung erhielt die fremde
17 * Zwischenzeit. Ist sie kuerzer, wird der Phasenuebergang zu kurz bemessen -
18 * die gefaehrliche Richtung, und die Gegenprobe faellt aus, weil
19 * `checkIntergreenGaps` denselben Schluessel nachschlaegt.
20 *
21 * Mit "->" in einer Kennung traf es schon das Einlesen: Die zweite, sachlich
22 * andere Beziehung wurde mit "doppelt erfasst" verworfen.
23 *
24 * Aus der Anwendung heraus entsteht das nicht - `createId` vergibt nur
25 * Kleinbuchstaben, Ziffern und "-" -, ueber eine von Hand geschriebene
26 * Projektdatei sehr wohl; die ist im Projekt ausdruecklich als Einstiegspunkt
27 * anerkannt.
28 *
29 * GEGEN DEN ALTSTAND schlagen die ersten drei Faelle fehl. Die letzten beiden
30 * halten die Grenzen fest und bestehen auch dort.
31 *
32 * DIE QUELLE IST SEIT DER FASSUNG 5.12.0 GESCHLOSSEN: Das Einlesen prueft
33 * die Kennungen jetzt gegen ein Zeichenrepertoire (`ZULAESSIGE_KENNUNG` in
34 * src/domain/model/schema.ts) und ersetzt, was nicht hineinpasst - ein "|"
35 * kann also nicht mehr aus einer Datei kommen. Der erste Fall baut sein
36 * Projekt deshalb unmittelbar auf statt ueber `parseProject`, wie es die
37 * Nachbarwache `schluesselAusKennungen.test.ts` aus demselben Grund tut:
38 * Er bewacht die SCHLUESSELBILDUNG - die zweite Reihe, und die einzige, die
39 * noch traegt, wenn das Repertoire eines Tages weiter gefasst wird. Die
40 * Faelle darunter gehen weiter ueber das Einlesen; sie messen dessen eigene
41 * Zusagen und bestehen mit den ersetzten Kennungen unveraendert, weil die
42 * Verweise mitziehen.
43 */
44
45 /** Vier Kennungen, deren zusammengesetzte Schluessel bisher kollidierten. */
46 const GRUPPEN_MIT_STRICH = [
47 { id: 'a|b', name: 'K1', mode: 'kfz' },
48 { id: 'c', name: 'K2', mode: 'kfz' },
49 { id: 'a', name: 'K3', mode: 'kfz' },
50 { id: 'b|c', name: 'K4', mode: 'kfz' },
51 ];
52
53 /** Dieselben vier Gruppen, aber am Einlesen vorbei zusammengesetzt. */
54 function projektMitStrich(): Project {
55 const leer = createEmptyProject('Kennungen mit Strich', new Date('2026-01-01T00:00:00Z'));
56 return {
57 ...leer,
58 signalGroups: GRUPPEN_MIT_STRICH.map((g, i) => ({
59 ...createSignalGroup({ name: g.name, mode: 'kfz', index: i }),
60 id: g.id,
61 })),
62 conflicts: [
63 { ...createConflict('a|b', 'c'), id: 'cf-1', clearingDistance: 40 },
64 { ...createConflict('a', 'b|c'), id: 'cf-2', clearingDistance: 5 },
65 ],
66 };
67 }
68
69 describe('Befund 61 - Kennungen mit Trennzeichen', () => {
70 it('haelt zwei Beziehungen mit kollidierendem Schluessel auseinander', () => {
71 const project = projektMitStrich();
72 expect(project.conflicts).toHaveLength(2);
73
74 const plan = buildSignalPlan(project);
75 const lang = plan.intergreens.get(intergreenKey('a|b', 'c'));
76 const kurz = plan.intergreens.get(intergreenKey('a', 'b|c'));
77
78 expect(lang?.conflictId).toBe('cf-1');
79 expect(kurz?.conflictId).toBe('cf-2');
80 // Der lange Raeumweg ergibt die laengere Zwischenzeit; erbte eine
81 // Beziehung die fremde, waeren beide Werte gleich.
82 expect((lang?.value ?? 0) > (kurz?.value ?? 0)).toBe(true);
83 });
84
85 it('vergibt fuer jede Beziehung einen eigenen Schluessel', () => {
86 expect(intergreenKey('a|b', 'c')).not.toBe(intergreenKey('a', 'b|c'));
87 });
88
89 it('behaelt beim Einlesen eine Beziehung mit "->" in der Kennung', () => {
90 const { project, issues } = parseProject({
91 signalGroups: [
92 { id: 'x->y', name: 'K1', mode: 'kfz' },
93 { id: 'z', name: 'K2', mode: 'kfz' },
94 { id: 'x', name: 'K3', mode: 'kfz' },
95 { id: 'y->z', name: 'K4', mode: 'kfz' },
96 ],
97 conflicts: [
98 { id: 'cf-1', fromId: 'x->y', toId: 'z', clearingDistance: 20, enteringDistance: 0 },
99 { id: 'cf-2', fromId: 'x', toId: 'y->z', clearingDistance: 20, enteringDistance: 0 },
100 ],
101 });
102
103 expect(project.conflicts.map((c) => c.id)).toEqual(['cf-1', 'cf-2']);
104 expect(issues.filter((i) => i.path.startsWith('conflicts'))).toEqual([]);
105 });
106
107 it('meldet eine wirklich doppelt erfasste Beziehung weiterhin', () => {
108 const { project, issues } = parseProject({
109 signalGroups: GRUPPEN_MIT_STRICH,
110 conflicts: [
111 { id: 'cf-1', fromId: 'a|b', toId: 'c', clearingDistance: 20, enteringDistance: 0 },
112 { id: 'cf-2', fromId: 'a|b', toId: 'c', clearingDistance: 40, enteringDistance: 0 },
113 ],
114 });
115
116 expect(project.conflicts).toHaveLength(1);
117 expect(
118 issues.some((i) => i.path === 'conflicts[1]' && i.message.includes('doppelt erfasst')),
119 ).toBe(true);
120 });
121
122 it('laesst die Schreibweise gewoehnlicher Kennungen unveraendert', () => {
123 // Kennungen aus createId enthalten weder "|" noch das Fluchtzeichen; der
124 // Schluessel liest sich weiterhin als "fromId|toId".
125 expect(intergreenKey('sg-1-abc', 'sg-2-def')).toBe('sg-1-abc|sg-2-def');
126 });
127 });