lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain zeichenrepertoireKennungen.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { istZulaessigeKennung, parseProject } from '@/domain/model/schema'; |
| 3 | import { CURRENT_SCHEMA_VERSION } from '@/domain/model/project'; |
| 4 | |
| 5 | /* |
| 6 | * Zeichenrepertoire fuer Kennungen (Fassung 5.12.0). |
| 7 | * |
| 8 | * Das Einlesen prueft an einer Kennung bis dahin nur zweierlei: leer und |
| 9 | * doppelt vergeben. Sonst kommt sie mit beliebigem Inhalt aus der Datei. Drei |
| 10 | * Befunde sind daraus geworden - 61 und seine Folge (Paarschluessel |
| 11 | * aus Signalgruppenkennungen) und 43 (Wegschluessel aus Planlinienkennungen); |
| 12 | * behoben war jedes Mal die Schluesselbildung, nie die Kennung. |
| 13 | * |
| 14 | * WAS DIESE DATEI BEWACHT: dass die Quelle geschlossen ist. Eine Kennung |
| 15 | * ausserhalb des Repertoires wird ERSETZT und GEMELDET, und jeder Verweis |
| 16 | * darauf zieht mit - Konfliktbeziehungen, Phasen, Verkehrsstaerken, Fahrlinien, |
| 17 | * Haltlinien und die Zuordnung zur Zufahrt. Aufgeloest wird nichts: Die Kennung |
| 18 | * gehoert genau einem Datensatz, es ist nichts zu raten, und ein Verlust der |
| 19 | * Konfliktbeziehungen waere die gefaehrliche Richtung (`intergreenBetween` |
| 20 | * liefert fuer eine fehlende Beziehung 0). |
| 21 | * |
| 22 | * GEGEN DEN ALTSTAND (Fassung 5.11.0) sind 11 der 15 Faelle |
| 23 | * rot - gemessen, indem die HEAD-Fassung von schema.ts ueber ein Vite-Plugin |
| 24 | * untergeschoben wurde; der Arbeitsbaum blieb unangetastet. Was dabei |
| 25 | * herauskommt, ist nicht einerlei: |
| 26 | * |
| 27 | * - Rot ueber das VERHALTEN sind neun Faelle - jeder, der eine Ersetzung, |
| 28 | * eine Meldung oder deren Wortlaut erwartet. Dazu gehoert auch der Fall |
| 29 | * zur Doppelvergabe: Im Altstand bleibt die ERSTE, unzulaessige Kennung |
| 30 | * stehen. Er prueft das unmittelbar am Trennzeichen und nicht ueber |
| 31 | * `istZulaessigeKennung`, damit er dort nicht schon ueber die fehlende |
| 32 | * Ausfuhr faellt. |
| 33 | * - Rot ueber die fehlende AUSFUHR sind die beiden Faelle, die |
| 34 | * `istZulaessigeKennung` selbst festhalten - die Funktion gibt es im |
| 35 | * Altstand nicht. Sie bewachen das Repertoire, nicht den Einleseweg. |
| 36 | * - GRUEN bleiben dort vier: die Gegenprobe mit gewoehnlichen Kennungen; der |
| 37 | * Fall zum Nachziehen der Verweise (ohne Ersetzung faellt die alte Kennung |
| 38 | * mit der neuen zusammen, die Erwartung trifft dann von selbst zu); und die |
| 39 | * beiden Faelle zum Zusammentreffen von unzulaessig und doppelt, die das |
| 40 | * Aufloesen der Verweise und das Fehlen einer beschoenigenden Meldung |
| 41 | * festhalten - beides tut der Altstand ebenso, er meldet dort ueberhaupt |
| 42 | * nichts. |
| 43 | * |
| 44 | * GEGEN DEN STAND VOR DEM NACHTRAG (Ersatzmeldung sofort abgesetzt, |
| 45 | * `kennungImKlartext` nur ueber \p{Cc}) sind drei der vier nachgereichten |
| 46 | * Faelle rot: die beiden zur zurueckgenommenen Umschluesselung und der zu den |
| 47 | * unsichtbaren Zeichen. Gruen bleiben dort die Gegenprobe zum gewoehnlichen |
| 48 | * Leerzeichen und der Fall, der das Aufloesen der Verweise festhaelt - am |
| 49 | * Verhalten hat der Nachtrag nichts geaendert, nur an den Meldungen. |
| 50 | */ |
| 51 | |
| 52 | const DATUM = new Date('2026-01-01T00:00:00Z'); |
| 53 | |
| 54 | /** Meldungen zu einem Pfad, damit sich die Erwartung ablesen laesst. */ |
| 55 | function meldungen( |
| 56 | issues: readonly { path: string; message: string }[], |
| 57 | path: string, |
| 58 | ): readonly string[] { |
| 59 | return issues.filter((i) => i.path === path).map((i) => i.message); |
| 60 | } |
| 61 | |
| 62 | describe('E.1 - Zeichenrepertoire der Kennungen', () => { |
| 63 | it('nimmt an, was das Programm selbst schreibt', () => { |
| 64 | // createId: "<Praefix>-<Zaehler zur Basis 36>-<8 Stellen hexadezimal>". |
| 65 | expect(istZulaessigeKennung('sg-1z-4bb787ea')).toBe(true); |
| 66 | // tools/vierarmigerKnotenpunkt.ts und die eingefrorene Beispieldatei. |
| 67 | expect(istZulaessigeKennung('arm-nord')).toBe(true); |
| 68 | expect(istZulaessigeKennung('arm-nord-geradeaus')).toBe(true); |
| 69 | expect(istZulaessigeKennung('hl-arm-nord')).toBe(true); |
| 70 | expect(istZulaessigeKennung('ph-1')).toBe(true); |
| 71 | // Von Hand oder aus einem Fremdwerkzeug: erlaubt, weil sie nichts brechen. |
| 72 | expect(istZulaessigeKennung('SG_1')).toBe(true); |
| 73 | expect(istZulaessigeKennung('sg.1')).toBe(true); |
| 74 | }); |
| 75 | |
| 76 | it('weist die Zeichen ab, die belegt etwas brechen', () => { |
| 77 | // Die Trennzeichen der Paarschluessel (intergreenKey, maskiereKennung). |
| 78 | expect(istZulaessigeKennung('a|b')).toBe(false); |
| 79 | expect(istZulaessigeKennung('a~b')).toBe(false); |
| 80 | // Das Anfuehrungszeichen bricht [data-hervorheben="<Kennung>"]. |
| 81 | expect(istZulaessigeKennung('a"b')).toBe(false); |
| 82 | // Steuerzeichen und Leerraum zerreissen jede zeilenweise Ausgabe. |
| 83 | expect(istZulaessigeKennung('a b')).toBe(false); |
| 84 | expect(istZulaessigeKennung('a\nb')).toBe(false); |
| 85 | // Der alte Trennstrich aus parseConflicts und wegSchluessel. |
| 86 | expect(istZulaessigeKennung('a->b')).toBe(false); |
| 87 | // Die Laenge gehoert zum Repertoire: 64 ja, 65 nein. |
| 88 | expect(istZulaessigeKennung('a'.repeat(64))).toBe(true); |
| 89 | expect(istZulaessigeKennung('a'.repeat(65))).toBe(false); |
| 90 | // Die leere Kennung ist nicht Sache des Repertoires, sondern der |
| 91 | // Neuvergabe an der Fundstelle - dort ohne Meldung. |
| 92 | expect(istZulaessigeKennung('')).toBe(false); |
| 93 | }); |
| 94 | }); |
| 95 | |
| 96 | describe('E.1 - Signalgruppenkennung ausserhalb des Repertoires', () => { |
| 97 | /** Ein Projekt, in dem alles auf die Signalgruppe "sg|a" verweist. */ |
| 98 | const datei = { |
| 99 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 100 | intersection: { arms: [{ id: 'arm-nord', name: 'Nord' }] }, |
| 101 | signalGroups: [ |
| 102 | { id: 'sg|a', name: 'K1', mode: 'kfz', movement: 'geradeaus', armId: 'arm-nord' }, |
| 103 | { id: 'sg-b', name: 'F1', mode: 'fuss', movement: 'querung', vehicleClass: 'keine' }, |
| 104 | ], |
| 105 | conflicts: [ |
| 106 | { id: 'cf-1', fromId: 'sg|a', toId: 'sg-b', clearingDistance: 12, enteringDistance: 4 }, |
| 107 | ], |
| 108 | phases: [{ id: 'ph-1', name: 'Phase 1', signalGroupIds: ['sg|a'] }], |
| 109 | demands: [{ signalGroupId: 'sg|a', volume: 600, heavyVehicleShare: 0.05 }], |
| 110 | lageplan: { |
| 111 | haltlinien: [ |
| 112 | { |
| 113 | id: 'hl-1', |
| 114 | name: 'Zufahrt Nord', |
| 115 | art: 'zufahrt', |
| 116 | armId: 'arm-nord', |
| 117 | von: { x: 0, y: 0 }, |
| 118 | bis: { x: 20, y: 0 }, |
| 119 | }, |
| 120 | ], |
| 121 | linien: [ |
| 122 | { |
| 123 | id: 'pl-1', |
| 124 | name: 'L1', |
| 125 | mode: 'kfz', |
| 126 | movement: 'geradeaus', |
| 127 | punkte: [ |
| 128 | { x: 10, y: 0 }, |
| 129 | { x: 10, y: 40 }, |
| 130 | ], |
| 131 | haltlinieId: 'hl-1', |
| 132 | startT: 0.5, |
| 133 | signalGroupId: 'sg|a', |
| 134 | }, |
| 135 | ], |
| 136 | }, |
| 137 | program: { phaseOrder: ['ph-1'] }, |
| 138 | }; |
| 139 | |
| 140 | it('ersetzt die Kennung und meldet es', () => { |
| 141 | const { project, issues, repaired } = parseProject(datei, DATUM); |
| 142 | |
| 143 | const ersetzt = project.signalGroups[0]!; |
| 144 | expect(ersetzt.name).toBe('K1'); |
| 145 | expect(ersetzt.id).not.toBe('sg|a'); |
| 146 | expect(istZulaessigeKennung(ersetzt.id)).toBe(true); |
| 147 | expect(repaired).toBe(true); |
| 148 | expect(meldungen(issues, 'signalGroups[0]')).toHaveLength(1); |
| 149 | expect(meldungen(issues, 'signalGroups[0]')[0]).toContain('sg|a'); |
| 150 | }); |
| 151 | |
| 152 | it('zieht alle Verweise auf die neue Kennung nach, statt sie aufzuloesen', () => { |
| 153 | const { project, issues } = parseProject(datei, DATUM); |
| 154 | const neu = project.signalGroups[0]!.id; |
| 155 | |
| 156 | // Die feindliche Beziehung bleibt - ihr Verlust waere die gefaehrliche |
| 157 | // Richtung: intergreenBetween liefert fuer eine fehlende Beziehung 0. |
| 158 | expect(project.conflicts).toHaveLength(1); |
| 159 | expect(project.conflicts[0]!.fromId).toBe(neu); |
| 160 | expect(project.conflicts[0]!.toId).toBe('sg-b'); |
| 161 | expect(project.conflicts[0]!.clearingDistance).toBe(12); |
| 162 | |
| 163 | expect(project.phases[0]!.signalGroupIds).toEqual([neu]); |
| 164 | expect(project.demands).toHaveLength(1); |
| 165 | expect(project.demands[0]!.signalGroupId).toBe(neu); |
| 166 | expect(project.demands[0]!.volume).toBe(600); |
| 167 | expect(project.lageplan.linien[0]!.signalGroupId).toBe(neu); |
| 168 | |
| 169 | // Und kein Verweis meldet einen Verlust: Gemeldet wird einmal, an der |
| 170 | // Kennung selbst. |
| 171 | const verluste = issues.filter((i) => /nicht vorhandene|aufgehoben|entfernt/.test(i.message)); |
| 172 | expect(verluste).toEqual([]); |
| 173 | }); |
| 174 | }); |
| 175 | |
| 176 | describe('E.1 - die uebrigen Kennungen', () => { |
| 177 | it('ersetzt eine Zufahrtskennung und zieht Signalgruppe und Haltlinie nach', () => { |
| 178 | const { project, issues } = parseProject( |
| 179 | { |
| 180 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 181 | intersection: { arms: [{ id: 'arm nord', name: 'Nord' }] }, |
| 182 | signalGroups: [{ id: 'sg-1', name: 'K1', armId: 'arm nord' }], |
| 183 | lageplan: { |
| 184 | haltlinien: [ |
| 185 | { |
| 186 | id: 'hl-1', |
| 187 | name: 'Zufahrt', |
| 188 | art: 'zufahrt', |
| 189 | armId: 'arm nord', |
| 190 | von: { x: 0, y: 0 }, |
| 191 | bis: { x: 20, y: 0 }, |
| 192 | }, |
| 193 | ], |
| 194 | linien: [], |
| 195 | }, |
| 196 | }, |
| 197 | DATUM, |
| 198 | ); |
| 199 | |
| 200 | const neu = project.intersection.arms[0]!.id; |
| 201 | expect(neu).not.toBe('arm nord'); |
| 202 | expect(istZulaessigeKennung(neu)).toBe(true); |
| 203 | expect(project.signalGroups[0]!.armId).toBe(neu); |
| 204 | expect(project.lageplan.haltlinien[0]!.armId).toBe(neu); |
| 205 | expect(meldungen(issues, 'intersection.arms[0]')).toHaveLength(1); |
| 206 | }); |
| 207 | |
| 208 | it('ersetzt eine Phasenkennung und zieht die Phasenfolge nach', () => { |
| 209 | const { project, issues } = parseProject( |
| 210 | { |
| 211 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 212 | signalGroups: [{ id: 'sg-1', name: 'K1' }], |
| 213 | phases: [ |
| 214 | // Ein Steuerzeichen in der Kennung - in jeder Ansicht unsichtbar. |
| 215 | { id: 'ph\u0001', name: 'Phase 1', signalGroupIds: ['sg-1'] }, |
| 216 | { id: 'ph-2', name: 'Phase 2', signalGroupIds: [] }, |
| 217 | ], |
| 218 | program: { phaseOrder: ['ph-2', 'ph\u0001'] }, |
| 219 | }, |
| 220 | DATUM, |
| 221 | ); |
| 222 | |
| 223 | const neu = project.phases[0]!.id; |
| 224 | expect(neu).not.toBe('ph\u0001'); |
| 225 | expect(project.program.phaseOrder).toEqual(['ph-2', neu]); |
| 226 | // Das Steuerzeichen steht sichtbar in der Meldung, nicht als Zeilenbruch. |
| 227 | expect(meldungen(issues, 'phases[0]')[0]).toContain('\\u0001'); |
| 228 | }); |
| 229 | |
| 230 | it('ersetzt eine Haltlinienkennung und zieht die Fahrlinie nach', () => { |
| 231 | const { project, issues } = parseProject( |
| 232 | { |
| 233 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 234 | signalGroups: [], |
| 235 | lageplan: { |
| 236 | haltlinien: [ |
| 237 | { |
| 238 | id: 'hl->1', |
| 239 | name: 'Zufahrt', |
| 240 | art: 'zufahrt', |
| 241 | von: { x: 0, y: 0 }, |
| 242 | bis: { x: 20, y: 0 }, |
| 243 | }, |
| 244 | ], |
| 245 | linien: [ |
| 246 | { |
| 247 | id: 'pl-1', |
| 248 | name: 'L1', |
| 249 | mode: 'kfz', |
| 250 | movement: 'geradeaus', |
| 251 | punkte: [ |
| 252 | { x: 10, y: 0 }, |
| 253 | { x: 10, y: 40 }, |
| 254 | ], |
| 255 | haltlinieId: 'hl->1', |
| 256 | startT: 0.5, |
| 257 | }, |
| 258 | ], |
| 259 | }, |
| 260 | }, |
| 261 | DATUM, |
| 262 | ); |
| 263 | |
| 264 | const neu = project.lageplan.haltlinien[0]!.id; |
| 265 | expect(neu).not.toBe('hl->1'); |
| 266 | expect(project.lageplan.linien[0]!.haltlinieId).toBe(neu); |
| 267 | expect(meldungen(issues, 'lageplan.haltlinien[0]')).toHaveLength(1); |
| 268 | }); |
| 269 | |
| 270 | it('ersetzt Konflikt- und Fahrlinienkennung, auf die in der Datei nichts verweist', () => { |
| 271 | const { project, issues } = parseProject( |
| 272 | { |
| 273 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 274 | signalGroups: [ |
| 275 | { id: 'sg-1', name: 'K1' }, |
| 276 | { id: 'sg-2', name: 'K2' }, |
| 277 | ], |
| 278 | conflicts: [{ id: 'cf|1', fromId: 'sg-1', toId: 'sg-2', clearingDistance: 9 }], |
| 279 | lageplan: { |
| 280 | haltlinien: [], |
| 281 | linien: [ |
| 282 | { |
| 283 | id: 'pl|1', |
| 284 | name: 'L1', |
| 285 | mode: 'kfz', |
| 286 | movement: 'geradeaus', |
| 287 | punkte: [ |
| 288 | { x: 0, y: 0 }, |
| 289 | { x: 0, y: 40 }, |
| 290 | ], |
| 291 | }, |
| 292 | ], |
| 293 | }, |
| 294 | }, |
| 295 | DATUM, |
| 296 | ); |
| 297 | |
| 298 | expect(project.conflicts).toHaveLength(1); |
| 299 | expect(project.conflicts[0]!.id).not.toBe('cf|1'); |
| 300 | expect(project.conflicts[0]!.clearingDistance).toBe(9); |
| 301 | expect(istZulaessigeKennung(project.conflicts[0]!.id)).toBe(true); |
| 302 | expect(meldungen(issues, 'conflicts[0]')).toHaveLength(1); |
| 303 | |
| 304 | expect(project.lageplan.linien[0]!.id).not.toBe('pl|1'); |
| 305 | expect(istZulaessigeKennung(project.lageplan.linien[0]!.id)).toBe(true); |
| 306 | expect(meldungen(issues, 'lageplan.linien[0]')).toHaveLength(1); |
| 307 | }); |
| 308 | }); |
| 309 | |
| 310 | describe('E.1 - unzulaessig UND doppelt vergeben', () => { |
| 311 | it('behandelt die zweite Vergabe als mehrdeutig und loest die Verweise auf', () => { |
| 312 | // Jetzt ist wieder offen, welche der beiden Gruppen gemeint ist - hier |
| 313 | // wird aufgeloest statt umgeschluesselt. |
| 314 | const { project, issues } = parseProject( |
| 315 | { |
| 316 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 317 | signalGroups: [ |
| 318 | { id: 'sg|a', name: 'K1' }, |
| 319 | { id: 'sg|a', name: 'K2' }, |
| 320 | { id: 'sg-b', name: 'F1', mode: 'fuss', movement: 'querung', vehicleClass: 'keine' }, |
| 321 | ], |
| 322 | conflicts: [{ id: 'cf-1', fromId: 'sg|a', toId: 'sg-b', clearingDistance: 12 }], |
| 323 | demands: [{ signalGroupId: 'sg|a', volume: 600 }], |
| 324 | }, |
| 325 | DATUM, |
| 326 | ); |
| 327 | |
| 328 | expect(project.signalGroups.map((g) => g.name)).toEqual(['K1', 'K2', 'F1']); |
| 329 | expect(new Set(project.signalGroups.map((g) => g.id)).size).toBe(3); |
| 330 | // Nicht ueber `istZulaessigeKennung` geprueft, sondern unmittelbar am |
| 331 | // Trennzeichen: Im Altstand fehlt die Ausfuhr, und der Fall fiele dort |
| 332 | // ueber einen TypeError statt ueber das, was er bewacht - dass auch die |
| 333 | // erste, unzulaessige Kennung ersetzt ist und nicht bloss die zweite. |
| 334 | for (const gruppe of project.signalGroups) { |
| 335 | expect(gruppe.id).not.toContain('|'); |
| 336 | } |
| 337 | expect(project.conflicts).toEqual([]); |
| 338 | expect(project.demands).toEqual([]); |
| 339 | expect(issues.some((i) => i.message.includes('kommt mehrfach vor'))).toBe(true); |
| 340 | expect( |
| 341 | issues.some((i) => i.path === 'demands[0]' && i.message.includes('mehrfach vergebene')), |
| 342 | ).toBe(true); |
| 343 | }); |
| 344 | |
| 345 | /** |
| 346 | * Alle vier Kennungsarten mit Umschluesselungskarte, jede zweimal mit |
| 347 | * derselben unzulaessigen Kennung: Zufahrt, Signalgruppe, Phase, Haltlinie. |
| 348 | * Konfliktbeziehung und Fahrlinie fuehren keine Karte - auf ihre Kennungen |
| 349 | * verweist in der Datei nichts, dort ist nichts zurueckzunehmen. |
| 350 | */ |
| 351 | const doppeltUndUnzulaessig = { |
| 352 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 353 | intersection: { |
| 354 | arms: [ |
| 355 | { id: 'arm|n', name: 'Nord' }, |
| 356 | { id: 'arm|n', name: 'Nord zwei' }, |
| 357 | ], |
| 358 | }, |
| 359 | signalGroups: [ |
| 360 | { id: 'sg|a', name: 'K1', mode: 'kfz', movement: 'geradeaus', armId: 'arm|n' }, |
| 361 | { id: 'sg|a', name: 'K2', mode: 'kfz', movement: 'geradeaus' }, |
| 362 | { id: 'sg-b', name: 'F1', mode: 'fuss', movement: 'querung', vehicleClass: 'keine' }, |
| 363 | ], |
| 364 | conflicts: [ |
| 365 | { id: 'cf-1', fromId: 'sg|a', toId: 'sg-b', clearingDistance: 12, enteringDistance: 4 }, |
| 366 | ], |
| 367 | phases: [ |
| 368 | { id: 'ph|1', name: 'Phase 1', signalGroupIds: ['sg|a'] }, |
| 369 | { id: 'ph|1', name: 'Phase 2', signalGroupIds: [] }, |
| 370 | ], |
| 371 | demands: [{ signalGroupId: 'sg|a', volume: 600 }], |
| 372 | lageplan: { |
| 373 | haltlinien: [ |
| 374 | { |
| 375 | id: 'hl|1', |
| 376 | name: 'Zufahrt Nord', |
| 377 | art: 'zufahrt', |
| 378 | armId: 'arm|n', |
| 379 | von: { x: 0, y: 0 }, |
| 380 | bis: { x: 20, y: 0 }, |
| 381 | }, |
| 382 | { |
| 383 | id: 'hl|1', |
| 384 | name: 'Zufahrt Nord zwei', |
| 385 | art: 'zufahrt', |
| 386 | von: { x: 0, y: 30 }, |
| 387 | bis: { x: 20, y: 30 }, |
| 388 | }, |
| 389 | ], |
| 390 | linien: [ |
| 391 | { |
| 392 | id: 'pl-1', |
| 393 | name: 'L1', |
| 394 | mode: 'kfz', |
| 395 | movement: 'geradeaus', |
| 396 | punkte: [ |
| 397 | { x: 10, y: 0 }, |
| 398 | { x: 10, y: 40 }, |
| 399 | ], |
| 400 | haltlinieId: 'hl|1', |
| 401 | startT: 0.5, |
| 402 | signalGroupId: 'sg|a', |
| 403 | }, |
| 404 | ], |
| 405 | }, |
| 406 | program: { phaseOrder: ['ph|1'] }, |
| 407 | }; |
| 408 | |
| 409 | it('behauptet nicht mehr, an den Verweisen aendere sich nichts', () => { |
| 410 | // Der Einlesebericht sagte dem Planer an genau der Stelle, an der eine |
| 411 | // Konfliktbeziehung verlorengeht, dass nichts verlorengeht: Die |
| 412 | // Ersatzmeldung war schon abgesetzt, als die zweite Vergabe die |
| 413 | // Umschluesselung wieder zuruecknahm. |
| 414 | const { issues } = parseProject(doppeltUndUnzulaessig, DATUM); |
| 415 | |
| 416 | expect(issues.filter((i) => i.message.includes('ändert sich nichts'))).toEqual([]); |
| 417 | }); |
| 418 | |
| 419 | it('sagt an jeder der vier Kennungen, dass die Umschluesselung zurueckgenommen ist', () => { |
| 420 | const { issues } = parseProject(doppeltUndUnzulaessig, DATUM); |
| 421 | |
| 422 | for (const path of [ |
| 423 | 'intersection.arms[0]', |
| 424 | 'signalGroups[0]', |
| 425 | 'phases[0]', |
| 426 | 'lageplan.haltlinien[0]', |
| 427 | ]) { |
| 428 | // An "phases[0]" steht daneben noch die aufgeloeste Zuordnung zur |
| 429 | // mehrfach vergebenen Signalgruppe; gemeint ist hier die Ersatzmeldung. |
| 430 | const zurKennung = meldungen(issues, path).filter((m) => m.includes('Sie wurde durch')); |
| 431 | expect(zurKennung).toHaveLength(1); |
| 432 | expect(zurKennung[0]).toContain('zurückgenommen'); |
| 433 | } |
| 434 | }); |
| 435 | |
| 436 | it('loest die Verweise auf alle vier Kennungen tatsaechlich auf', () => { |
| 437 | // Die Meldung und das, was geschieht, muessen dasselbe sagen - deshalb |
| 438 | // steht hier neben dem Wortlaut auch das Ergebnis. |
| 439 | const { project } = parseProject(doppeltUndUnzulaessig, DATUM); |
| 440 | |
| 441 | expect(project.signalGroups[0]!.armId).toBeNull(); |
| 442 | expect(project.lageplan.haltlinien[0]!.armId).toBeNull(); |
| 443 | expect(project.conflicts).toEqual([]); |
| 444 | expect(project.demands).toEqual([]); |
| 445 | expect(project.phases[0]!.signalGroupIds).toEqual([]); |
| 446 | expect(project.lageplan.linien[0]!.haltlinieId).toBeNull(); |
| 447 | expect(project.lageplan.linien[0]!.signalGroupId).toBeNull(); |
| 448 | }); |
| 449 | }); |
| 450 | |
| 451 | describe('E.1 - die Meldung macht unsichtbare Zeichen sichtbar', () => { |
| 452 | // Ueber die Zeichennummer gebildet, damit im Quelltext dieser Datei kein |
| 453 | // unsichtbares Zeichen steht - man saehe hier so wenig wie in der Meldung. |
| 454 | /** U+200B, das Zeichen ohne Breite. */ |
| 455 | const OHNE_BREITE = String.fromCharCode(0x200b); |
| 456 | /** U+00A0, das geschuetzte Leerzeichen. */ |
| 457 | const GESCHUETZT = String.fromCharCode(0x00a0); |
| 458 | |
| 459 | /** Die Meldung, die das Einlesen zur Signalgruppenkennung `roh` absetzt. */ |
| 460 | function meldungZurKennung(roh: string): string { |
| 461 | const { issues } = parseProject( |
| 462 | { schemaVersion: CURRENT_SCHEMA_VERSION, signalGroups: [{ id: roh, name: 'K1' }] }, |
| 463 | DATUM, |
| 464 | ); |
| 465 | return meldungen(issues, 'signalGroups[0]')[0] ?? ''; |
| 466 | } |
| 467 | |
| 468 | it('schreibt Zeichen aus, die man sonst nicht saehe', () => { |
| 469 | // Ohne die Ersetzung stuende auf dem Schirm "sga" - eine Kennung, die |
| 470 | // aussieht wie eine zulaessige, und der Anwender staende vor einer |
| 471 | // Beanstandung, die er nicht nachvollziehen kann. |
| 472 | expect(meldungZurKennung(`sg${OHNE_BREITE}a`)).toContain('sg\\u200ba'); |
| 473 | // Das geschuetzte Leerzeichen saehe wie ein gewoehnliches aus. |
| 474 | expect(meldungZurKennung(`sg${GESCHUETZT}a`)).toContain('sg\\u00a0a'); |
| 475 | // Der Zeilenumbruch zerrisse die Meldung. |
| 476 | expect(meldungZurKennung('sg\na')).toContain('sg\\u000aa'); |
| 477 | }); |
| 478 | |
| 479 | it('laesst das gewoehnliche Leerzeichen stehen', () => { |
| 480 | // Zwischen den Anfuehrungszeichen ist es zu erkennen; eine Zahlenfolge |
| 481 | // waere hier schwerer zu lesen als die Kennung selbst. |
| 482 | expect(meldungZurKennung('arm nord')).toContain('"arm nord"'); |
| 483 | }); |
| 484 | }); |
| 485 | |
| 486 | describe('E.1 - Gegenprobe', () => { |
| 487 | it('laesst eine Datei mit gewoehnlichen Kennungen unveraendert und ohne Meldung', () => { |
| 488 | const { project, issues, repaired } = parseProject( |
| 489 | { |
| 490 | schemaVersion: CURRENT_SCHEMA_VERSION, |
| 491 | intersection: { arms: [{ id: 'arm-nord', name: 'Nord' }] }, |
| 492 | signalGroups: [ |
| 493 | { id: 'sg-1z-4bb787ea', name: 'K1', armId: 'arm-nord' }, |
| 494 | { id: 'arm-nord-geradeaus', name: 'K2' }, |
| 495 | ], |
| 496 | conflicts: [ |
| 497 | { |
| 498 | id: 'cf-10-fa4aa6f5', |
| 499 | fromId: 'sg-1z-4bb787ea', |
| 500 | toId: 'arm-nord-geradeaus', |
| 501 | clearingDistance: 12, |
| 502 | enteringDistance: 4, |
| 503 | }, |
| 504 | ], |
| 505 | phases: [{ id: 'ph-1', name: 'Phase 1', signalGroupIds: ['sg-1z-4bb787ea'] }], |
| 506 | program: { phaseOrder: ['ph-1'] }, |
| 507 | }, |
| 508 | DATUM, |
| 509 | ); |
| 510 | |
| 511 | expect(project.signalGroups.map((g) => g.id)).toEqual(['sg-1z-4bb787ea', 'arm-nord-geradeaus']); |
| 512 | expect(project.signalGroups[0]!.armId).toBe('arm-nord'); |
| 513 | expect(project.conflicts[0]!.id).toBe('cf-10-fa4aa6f5'); |
| 514 | expect(project.phases[0]!.id).toBe('ph-1'); |
| 515 | expect(project.program.phaseOrder).toEqual(['ph-1']); |
| 516 | expect(issues).toEqual([]); |
| 517 | expect(repaired).toBe(false); |
| 518 | }); |
| 519 | }); |