lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain kennungMitTrennzeichen.test.ts
| 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 | }); |