lsa-planer

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

/ tests ui konfliktfensterUeberfahrzeit.test.ts

21,1 KB Rohdatei
tests/ui/konfliktfensterUeberfahrzeit.test.ts — 512 Zeilen
1 import { afterEach, describe, expect, it } from 'vitest';
2 import { ProjectStore } from '@/app/store';
3 import {
4 createConflict,
5 createEmptyProject,
6 createPhase,
7 createSignalGroup,
8 createStandardIntersectionProject,
9 } from '@/domain/model/factory';
10 import type { Project } from '@/domain/model/project';
11 import {
12 ENGER_RADIUS_LABEL,
13 RILSA_DEFAULTS,
14 RILSA_ENGSTELLE,
15 SAFETY_FLOORS,
16 UEBERFAHRZEIT_ANSATZ_LABELS,
17 } from '@/domain/rilsa/constants';
18 import { regelUeberfahrzeit } from '@/domain/rilsa/intergreen';
19 import { QUELLEN } from '@/domain/rilsa/quellen';
20 import { resolveRilsaSettings } from '@/domain/rilsa/settings';
21 import type { UeberfahrzeitAnsatz } from '@/domain/rilsa/types';
22 import { ueberfahrzeitAnsatzSatz } from '@/services/export/bewertung';
23 import type { ViewDefinition } from '@/ui/shell';
24 import { conflictsView } from '@/ui/views/conflictsView';
25
26 /**
27 * Was das Konfliktfenster ueber die beiden Ueberfahrzeit-Ansaetze und den
28 * engen Innenradius sagt (Fassung 5.5.0, Befunde F4, F5 und B2).
29 *
30 * Gegen den Stand vom 15.08.2026 schlugen fehl:
31 *
32 * F4 Das Fenster nannte die Ueberfahrzeit unbedingt "Regelwert" - auch beim
33 * Ansatz "feste Ueberfahrzeit", dessen Werte quellen.ts damals als
34 * "deshalb kein Regelwerkswert" fuehrte. Drei Felder darueber war genau
35 * das fuer die Raeumgeschwindigkeit bereits behoben.
36 * F4 Die Erlaeuterungsspalte der Zeile "Ueberfahrzeit tue" blieb leer,
37 * waehrend der Ausdruck an derselben Stelle eine Herleitung nennt.
38 * F5 Die Herleitung des Kfz-Ansatzes darf nicht neben einer Fussgaenger- oder
39 * OePNV-Ueberfahrzeit stehen; dort gelten andere Regeln.
40 * B2 Steht neben dem Merkmal "enger Innenradius" eine Raeumgeschwindigkeit
41 * von Hand, hat sie Vorrang. Das Fenster behauptete das Gegenteil.
42 *
43 * UMGEDREHT MIT FASSUNG 5.43.0 (Befund 17): Die Faelle zu F4
44 * schrieben bis dahin den Stand vor 5.27.0 fest - "Regelwert" beim
45 * Gelbzeitansatz, keiner beim festen. Seit dem Abgleich an der gekauften RiLSA
46 * 2015 ist es umgekehrt: Der feste Ansatz ist der des Regelwerks (Abschnitt
47 * 2.5.2, Faelle 1 und 2), die Kopplung an die Gelbzeit der Ansatz dieses
48 * Programms. Beim Tausch der Beschriftungen in constants.ts blieb das Fenster
49 * stehen und nannte beim Abbieger "Regelwert 3 s", wo die RiLSA 2 s setzt. Und
50 * die Beschriftung des Gelbzeitansatzes sagte "länger als der Regelwert",
51 * obwohl beide bei 50 km/h geradeaus 3 s ergeben.
52 */
53
54 const FESTES_DATUM = new Date('2026-01-01T00:00:00Z');
55
56 function zeichne(
57 view: ViewDefinition,
58 project: Project,
59 ): { wurzel: HTMLElement; aufraeumen: () => void } {
60 const wurzel = document.createElement('div');
61 document.body.append(wurzel);
62 const abmelden = view.render(wurzel, {
63 store: new ProjectStore(project),
64 refresh: () => {},
65 navigate: () => {},
66 target: null,
67 });
68 return {
69 wurzel,
70 aufraeumen: () => {
71 abmelden();
72 wurzel.remove();
73 },
74 };
75 }
76
77 /** Sichtbarer Text einer Beschriftung - ohne den Hilfeknopf. */
78 function beschriftungstext(label: HTMLLabelElement): string {
79 return [...label.childNodes]
80 .filter((knoten) => !(knoten instanceof Element && knoten.classList.contains('hilfe-knopf')))
81 .map((knoten) => knoten.textContent ?? '')
82 .join('')
83 .trim();
84 }
85
86 function feldZu(wurzel: ParentNode, beschriftung: string): HTMLElement | null {
87 const label = [...wurzel.querySelectorAll('label')].find(
88 (l) => beschriftungstext(l) === beschriftung,
89 );
90 const id = label?.getAttribute('for');
91 if (id === undefined || id === null || id === '') return null;
92 return wurzel.querySelector<HTMLElement>(`[id="${id}"]`);
93 }
94
95 function hinweisZu(wurzel: ParentNode, feld: HTMLElement): string {
96 const id = feld.getAttribute('aria-describedby');
97 if (id === null) return '';
98 return id
99 .split(' ')
100 .map((teil) => wurzel.querySelector(`[id="${teil}"]`)?.textContent ?? '')
101 .join(' ');
102 }
103
104 /** Erlaeuterungsspalte einer Zeile des Rechenwegs. */
105 function rechenwegZeile(fenster: ParentNode, bezeichnung: string): string {
106 const zeile = [...fenster.querySelectorAll('tbody tr')].find(
107 (tr) => tr.children[0]?.textContent?.trim() === bezeichnung,
108 );
109 if (!zeile) throw new Error(`Zeile "${bezeichnung}" fehlt im Rechenweg`);
110 return zeile.children[2]?.textContent?.trim() ?? '';
111 }
112
113 function oeffneBeziehung(wurzel: HTMLElement, project: Project, fromName: string): ParentNode {
114 const von = project.signalGroups.find((g) => g.name === fromName)!;
115 const konflikt = project.conflicts.find((c) => c.fromId === von.id)!;
116 const zeile = project.signalGroups.findIndex((g) => g.id === konflikt.fromId);
117 const spalte = project.signalGroups.findIndex((g) => g.id === konflikt.toId);
118 const zellen = [...wurzel.querySelectorAll<HTMLButtonElement>('.matrix-zelle')];
119 const n = project.signalGroups.length;
120 const versatz = spalte > zeile ? spalte - 1 : spalte;
121 zellen[zeile * (n - 1) + versatz]?.click();
122 const fenster = document.querySelector('.dialog');
123 expect(fenster, `Das Fenster zu ${fromName} ist nicht offen`).not.toBeNull();
124 return fenster as ParentNode;
125 }
126
127 /** Knotenpunkt, dessen erste Signalgruppe rechts abbiegt. */
128 function mitAbbieger(aenderung: Partial<Project['conflicts'][number]> = {}): Project {
129 const basis = createStandardIntersectionProject('Prüffall', FESTES_DATUM);
130 const erste = basis.signalGroups[0]!;
131 return {
132 ...basis,
133 signalGroups: basis.signalGroups.map((g) =>
134 g.id === erste.id ? { ...g, movement: 'rechts' as const } : g,
135 ),
136 conflicts: basis.conflicts.map((c) => (c.fromId === erste.id ? { ...c, ...aenderung } : c)),
137 };
138 }
139
140 /**
141 * Derselbe Abbieger mit gewaehltem Ansatz und - wo angegeben - eigenen
142 * Vorgaben fuer die festen Ueberfahrzeiten.
143 */
144 function mitAnsatz(
145 ansatz: UeberfahrzeitAnsatz,
146 aenderung: Partial<Project['conflicts'][number]> = {},
147 rilsa: Project['settings']['rilsa'] = {},
148 ): Project {
149 const basis = mitAbbieger(aenderung);
150 return { ...basis, settings: { ...basis.settings, ueberfahrzeitAnsatz: ansatz, rilsa } };
151 }
152
153 /** Zwei Kfz-Stroeme ueber eine einstreifige Engstelle (RiLSA 2015, Abschnitt 5.2). */
154 function baustelle(ansatz: UeberfahrzeitAnsatz): Project {
155 const basis = createEmptyProject('Engstelle', FESTES_DATUM, 'einstreifig');
156 const a = createSignalGroup({ name: 'K1', mode: 'kfz', vZul: 50, index: 0 });
157 const b = createSignalGroup({ name: 'K2', mode: 'kfz', vZul: 50, index: 1 });
158 const p1 = createPhase('Seite A', [a.id]);
159 const p2 = createPhase('Seite B', [b.id]);
160 return {
161 ...basis,
162 settings: { ...basis.settings, ueberfahrzeitAnsatz: ansatz },
163 signalGroups: [a, b],
164 conflicts: [
165 { ...createConflict(a.id, b.id), clearingDistance: 44, enteringDistance: 0 },
166 { ...createConflict(b.id, a.id), clearingDistance: 44, enteringDistance: 0 },
167 ],
168 phases: [p1, p2],
169 program: { ...basis.program, phaseOrder: [p1.id, p2.id] },
170 };
171 }
172
173 /** Hinweis unter dem Feld "Überfahrzeit" der Beziehung, die `fromName` raeumt. */
174 function ueberfahrzeitHinweis(project: Project, fromName: string): string {
175 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
176 try {
177 const fenster = oeffneBeziehung(wurzel, project, fromName);
178 const feld = feldZu(fenster, 'Überfahrzeit');
179 expect(feld, 'Das Feld "Überfahrzeit" fehlt im Fenster').not.toBeNull();
180 return hinweisZu(fenster, feld!);
181 } finally {
182 aufraeumen();
183 }
184 }
185
186 afterEach(() => {
187 document.body.replaceChildren();
188 });
189
190 describe('F4 - "Regelwert" nur, wo einer steht', () => {
191 it('nennt die feste Ueberfahrzeit Regelwert, solange sie der RiLSA entspricht', () => {
192 const hinweis = ueberfahrzeitHinweis(mitAnsatz('fest'), 'K1');
193 expect(hinweis).toContain(`Regelwert ${RILSA_DEFAULTS.crossingTime.kfzAbbiegend} s`);
194 expect(hinweis).not.toContain('Praxisansatz');
195 });
196
197 it('nennt die Gelbzeit beim Abbieger nicht Regelwert', () => {
198 // Der Fall aus dem Befund: Die Gelbzeit ist 3 s, die RiLSA setzt 2 s.
199 const hinweis = ueberfahrzeitHinweis(mitAnsatz('gelbzeit'), 'K1');
200 expect(hinweis).toContain('Angesetzt 3 s – Gelbzeit (Ansatz dieses Programms)');
201 expect(hinweis).not.toContain('Regelwert');
202 });
203
204 it('nennt eine abweichende feste Vorgabe nicht Regelwert', () => {
205 const hinweis = ueberfahrzeitHinweis(
206 mitAnsatz('fest', {}, { crossingTimeKfzAbbiegend: 4 }),
207 'K1',
208 );
209 expect(hinweis).toContain('Angesetzt 4 s – Vorgabe dieses Plans');
210 expect(hinweis).not.toContain('Regelwert');
211 });
212
213 it('nennt bei Handeingabe im Gelbzeitansatz die Gelbzeit nicht Regelwert', () => {
214 const hinweis = ueberfahrzeitHinweis(mitAnsatz('gelbzeit', { crossingTimeOverride: 6 }), 'K1');
215 expect(hinweis).toContain('Angesetzt 6 s – von Hand eingetragen; ohne diesen Eintrag 3 s');
216 expect(hinweis).not.toContain('Regelwert');
217 });
218
219 it('nennt bei Handeingabe im festen Ansatz den Regelwert der RiLSA', () => {
220 const hinweis = ueberfahrzeitHinweis(mitAnsatz('fest', { crossingTimeOverride: 4 }), 'K1');
221 expect(hinweis).toContain(
222 `Angesetzt 4 s – von Hand eingetragen; Regelwert ${RILSA_DEFAULTS.crossingTime.kfzAbbiegend} s`,
223 );
224 });
225
226 it('nennt bei Handeingabe und abweichender fester Vorgabe keinen Regelwert', () => {
227 // Gegenprobe zum Fall darueber: Die Vorgabe dieses Plans ist kein Regelwert.
228 const hinweis = ueberfahrzeitHinweis(
229 mitAnsatz('fest', { crossingTimeOverride: 6 }, { crossingTimeKfzAbbiegend: 4 }),
230 'K1',
231 );
232 expect(hinweis).toContain('ohne diesen Eintrag 4 s');
233 expect(hinweis).not.toContain('Regelwert');
234 });
235
236 it('nennt die Ueberfahrzeit der Engstelle beim festen Ansatz Regelwert', () => {
237 // Abschnitt 5.2.2 setzt 4 s unabhaengig vom Ansatz; das Fenster nannte sie
238 // beim festen Ansatz "feste Überfahrzeit (Praxisansatz ...)".
239 const project = baustelle('fest');
240 const hinweis = ueberfahrzeitHinweis(project, 'K1');
241 expect(hinweis).toContain(`Regelwert ${RILSA_ENGSTELLE.ueberfahrzeit} s`);
242 expect(hinweis).not.toContain('Überfahrzeit');
243 });
244
245 it('nennt die Ueberfahrzeit der Engstelle beim Gelbzeitansatz Regelwert', () => {
246 // Gegenprobe: Der Tausch darf die 4 s der Engstelle nicht zur Gelbzeit machen.
247 const hinweis = ueberfahrzeitHinweis(baustelle('gelbzeit'), 'K1');
248 expect(hinweis).toContain(`Regelwert ${RILSA_ENGSTELLE.ueberfahrzeit} s`);
249 expect(hinweis).not.toContain('Gelbzeit');
250 });
251
252 it('nennt die Ueberfahrzeit der Fussgaenger bei beiden Ansaetzen Regelwert', () => {
253 // Gegenprobe: Der Ansatz erreicht nur den Kraftfahrzeugverkehr.
254 for (const ansatz of ['gelbzeit', 'fest'] as const) {
255 const basis = createStandardIntersectionProject('Fußgänger', FESTES_DATUM);
256 const project: Project = {
257 ...basis,
258 settings: { ...basis.settings, ueberfahrzeitAnsatz: ansatz },
259 };
260 const fuss = project.signalGroups.find((g) => g.mode === 'fuss');
261 expect(fuss, 'Die Vorlage führt keine Fußgängergruppe').toBeDefined();
262 expect(ueberfahrzeitHinweis(project, fuss!.name), ansatz).toContain(
263 `Regelwert ${RILSA_DEFAULTS.crossingTime.fuss} s`,
264 );
265 }
266 });
267
268 it('erklaert die Zeile "Ueberfahrzeit tue" im Rechenweg', () => {
269 const basis = mitAbbieger();
270 const project: Project = {
271 ...basis,
272 settings: { ...basis.settings, ueberfahrzeitAnsatz: 'fest' },
273 };
274 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
275 try {
276 const fenster = oeffneBeziehung(wurzel, project, 'K1');
277 expect(rechenwegZeile(fenster, 'Überfahrzeit tü')).toBe(UEBERFAHRZEIT_ANSATZ_LABELS.fest);
278 } finally {
279 aufraeumen();
280 }
281 });
282
283 it('nennt eine von Hand gesetzte Ueberfahrzeit als solche', () => {
284 const project = mitAbbieger({ crossingTimeOverride: 4 });
285 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
286 try {
287 const fenster = oeffneBeziehung(wurzel, project, 'K1');
288 expect(rechenwegZeile(fenster, 'Überfahrzeit tü')).toContain('von Hand');
289 } finally {
290 aufraeumen();
291 }
292 });
293 });
294
295 describe('F5 - die Herleitung der Ueberfahrzeit gilt je Verkehrsart', () => {
296 it('nennt bei Fussgaengern nicht den Kfz-Ansatz', () => {
297 const basis = createStandardIntersectionProject('Fußgänger', FESTES_DATUM);
298 const project: Project = {
299 ...basis,
300 settings: { ...basis.settings, ueberfahrzeitAnsatz: 'fest' },
301 };
302 const fuss = project.signalGroups.find((g) => g.mode === 'fuss');
303 expect(fuss, 'Die Vorlage führt keine Fußgängergruppe').toBeDefined();
304 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
305 try {
306 const fenster = oeffneBeziehung(wurzel, project, fuss!.name);
307 const erlaeuterung = rechenwegZeile(fenster, 'Überfahrzeit tü');
308 expect(erlaeuterung).not.toContain('feste Überfahrzeit');
309 expect(erlaeuterung).toContain('Fußgänger');
310 } finally {
311 aufraeumen();
312 }
313 });
314 });
315
316 describe('B2 - das Fenster zeigt den Vorrang der Handeingabe', () => {
317 it('beschriftet die Raeumgeschwindigkeit nicht mehr mit dem Merkmal', () => {
318 const project = mitAbbieger({ engerRadius: true, clearingSpeedOverride: 9 });
319 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
320 try {
321 const fenster = oeffneBeziehung(wurzel, project, 'K1');
322 const hinweis = hinweisZu(fenster, feldZu(fenster, 'Räumgeschwindigkeit')!);
323 expect(hinweis).not.toContain('enger Innenradius');
324 } finally {
325 aufraeumen();
326 }
327 });
328
329 it('sagt am Kaestchen, dass es ohne Wirkung bleibt', () => {
330 const project = mitAbbieger({ engerRadius: true, clearingSpeedOverride: 9 });
331 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
332 try {
333 const fenster = oeffneBeziehung(wurzel, project, 'K1');
334 const hinweis = hinweisZu(fenster, feldZu(fenster, ENGER_RADIUS_LABEL)!);
335 expect(hinweis).toContain('Ohne Wirkung');
336 expect(hinweis).toContain('Vorrang');
337 expect(hinweis).not.toContain('die Zwischenzeit fällt länger aus');
338 } finally {
339 aufraeumen();
340 }
341 });
342
343 it('zeigt die Warnung im Rechenweg der Beziehung', () => {
344 const project = mitAbbieger({ engerRadius: true, clearingSpeedOverride: 9 });
345 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
346 try {
347 const fenster = oeffneBeziehung(wurzel, project, 'K1');
348 const balken = [...fenster.querySelectorAll('.hinweisbalken-warnung')].map(
349 (b) => b.textContent ?? '',
350 );
351 expect(balken.some((t) => t.includes('Vorrang'))).toBe(true);
352 } finally {
353 aufraeumen();
354 }
355 });
356 });
357
358 describe('Befund 17 - der Gelbzeitansatz heisst nur, was bei jeder Vorgabe stimmt', () => {
359 /*
360 * "Länger als der Regelwert" stimmte nicht: Bei 50 km/h geradeaus ergeben
361 * beide Ansaetze 3 s, und die Gelbzeitstaffel ist einstellbar. Immer wahr ist
362 * "nie kürzer", weil keine Gelbzeit unter SAFETY_FLOORS.yellowKfz liegt. Eine
363 * Tempogrenze ("oberhalb von 50 km/h länger") gehoert deshalb nicht in die
364 * Beschriftung.
365 */
366 it('beschriftet den Gelbzeitansatz mit "nie kürzer als der Regelwert"', () => {
367 expect(UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit).toContain('nie kürzer als der Regelwert');
368 expect(UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit).not.toContain('länger als der Regelwert');
369 });
370
371 it('haelt "nie kürzer" auch an der kleinsten zulaessigen Gelbzeitstaffel', () => {
372 // Die Grundlage der Beschriftung, nicht ihr Wortlaut: Faellt die
373 // Sicherheitsschranke unter einen Regelwert, ist der Satz falsch.
374 const { defaults } = resolveRilsaSettings({
375 yellowKfzUpTo50: 0,
376 yellowKfzUpTo60: 0,
377 yellowKfzAbove60: 0,
378 });
379 expect(defaults.yellowBySpeed.map((stufe) => stufe.yellow)).toEqual([
380 SAFETY_FLOORS.yellowKfz,
381 SAFETY_FLOORS.yellowKfz,
382 SAFETY_FLOORS.yellowKfz,
383 ]);
384 for (const vZul of [30, 50, 60, 70, 100]) {
385 for (const movement of ['geradeaus', 'rechts', 'links'] as const) {
386 const gelbzeit = regelUeberfahrzeit(
387 { mode: 'kfz', movement, vZul, ueberfahrzeitAnsatz: 'gelbzeit' },
388 defaults,
389 );
390 const regelwert = regelUeberfahrzeit({
391 mode: 'kfz',
392 movement,
393 vZul,
394 ueberfahrzeitAnsatz: 'fest',
395 });
396 expect(gelbzeit, `${movement}, ${vZul} km/h`).toBeGreaterThanOrEqual(regelwert);
397 }
398 }
399 });
400
401 it('sagt dasselbe im Fundstellenverzeichnis', () => {
402 const hinweis = QUELLEN['rilsa-zwischenzeiten'].hinweis ?? '';
403 expect(hinweis).toContain('„Überfahrzeit = Gelbzeit" ist nie kürzer als der Regelwert');
404 expect(hinweis).not.toContain('ist länger als der Regelwert');
405 });
406
407 it('sagt es beim festen Ansatz in der Planunterlage ebenso', () => {
408 const satz = ueberfahrzeitAnsatzSatz('fest');
409 expect(satz).toContain('die Kopplung an die Gelbzeit – rechnet nie kürzer');
410 expect(satz).not.toContain('rechnet länger');
411 });
412 });
413
414 describe('Nachtrag zu Befund 17 - "nie kürzer" gilt gegen die RiLSA, nicht gegen jede Vorgabe', () => {
415 /*
416 * Die Planunterlage sagte beim festen Ansatz unbedingt "Die Werte sind die
417 * Regelwerte der RiLSA 2015 ...; der Regelansatz dieses Programms ... rechnet
418 * nie kürzer als diese" - auch dort, wo der Plan andere feste Werte traegt.
419 * Mit 4 s abbiegend ist die Gelbzeit bei 50 km/h 3 s und damit KUERZER als
420 * "diese". Und das Feld "Überfahrzeit" zeigte leer "Regelwert", wo der
421 * Hinweis darunter gerade keinen mehr nennt.
422 */
423 const rilsaFeste =
424 `${RILSA_DEFAULTS.crossingTime.kfzGeradeaus} s geradeaus und ` +
425 `${RILSA_DEFAULTS.crossingTime.kfzAbbiegend} s abbiegend`;
426
427 it('schreibt abweichende feste Werte in der Planunterlage nicht der RiLSA zu', () => {
428 const { defaults } = resolveRilsaSettings({ crossingTimeKfzAbbiegend: 4 });
429 // Der Grund des Befunds, nicht sein Wortlaut: Gegen DIESE Vorgabe faellt
430 // die Gelbzeitkopplung kuerzer aus.
431 expect(
432 regelUeberfahrzeit(
433 { mode: 'kfz', movement: 'rechts', vZul: 50, ueberfahrzeitAnsatz: 'gelbzeit' },
434 defaults,
435 ),
436 ).toBeLessThan(defaults.crossingTime.kfzAbbiegend);
437
438 const satz = ueberfahrzeitAnsatzSatz('fest', defaults);
439 expect(satz).not.toContain('Die Werte sind die Regelwerte der RiLSA 2015');
440 expect(satz).not.toContain('nie kürzer als diese');
441 expect(satz).toContain('rechnet nie kürzer als die Regelwerte der RiLSA 2015');
442 expect(satz).toContain(`nicht die ${rilsaFeste} der RiLSA 2015`);
443 });
444
445 it('widerspricht sich bei abweichenden festen Werten nicht im selben Absatz', () => {
446 const { defaults } = resolveRilsaSettings({
447 crossingTimeKfzGeradeaus: 6,
448 crossingTimeKfzAbbiegend: 6,
449 });
450 const satz = ueberfahrzeitAnsatzSatz('fest', defaults);
451 expect(satz).toContain('weichen dann allerdings von den Regelwerten der RiLSA 2015 ab');
452 expect(satz).not.toContain('Die Werte sind die Regelwerte der RiLSA 2015');
453 });
454
455 it('laesst den Satz bei den Werten der RiLSA stehen', () => {
456 // Gegenprobe: Dort ist "diese" richtig.
457 const satz = ueberfahrzeitAnsatzSatz('fest', resolveRilsaSettings({}).defaults);
458 expect(satz).toContain('Die Werte sind die Regelwerte der RiLSA 2015');
459 expect(satz).toContain('die Kopplung an die Gelbzeit – rechnet nie kürzer als diese.');
460 });
461
462 /** Platzhalter und Hinweis des Feldes "Überfahrzeit" der Beziehung, die `fromName` raeumt. */
463 function ueberfahrzeitFeld(
464 project: Project,
465 fromName: string,
466 ): { platzhalter: string; hinweis: string } {
467 const { wurzel, aufraeumen } = zeichne(conflictsView, project);
468 try {
469 const fenster = oeffneBeziehung(wurzel, project, fromName);
470 const feld = feldZu(fenster, 'Überfahrzeit');
471 expect(feld, 'Das Feld "Überfahrzeit" fehlt im Fenster').toBeInstanceOf(HTMLInputElement);
472 return {
473 platzhalter: (feld as HTMLInputElement).placeholder,
474 hinweis: hinweisZu(fenster, feld!),
475 };
476 } finally {
477 aufraeumen();
478 }
479 }
480
481 it('schreibt beim Gelbzeitansatz nicht "Regelwert" in das leere Feld', () => {
482 expect(ueberfahrzeitFeld(mitAnsatz('gelbzeit'), 'K1').platzhalter).toBe('Gelbzeit');
483 });
484
485 it('schreibt bei abweichender fester Vorgabe nicht "Regelwert" in das leere Feld', () => {
486 const project = mitAnsatz('fest', {}, { crossingTimeKfzAbbiegend: 4 });
487 expect(ueberfahrzeitFeld(project, 'K1').platzhalter).toBe('Vorgabe dieses Plans');
488 });
489
490 it('laesst "Regelwert" im Feld, wo einer angesetzt ist', () => {
491 // Gegenproben: fester Ansatz mit den Werten der RiLSA, Engstelle bei beiden
492 // Ansaetzen.
493 expect(ueberfahrzeitFeld(mitAnsatz('fest'), 'K1').platzhalter).toBe('Regelwert');
494 for (const ansatz of ['gelbzeit', 'fest'] as const) {
495 expect(ueberfahrzeitFeld(baustelle(ansatz), 'K1').platzhalter, ansatz).toBe('Regelwert');
496 }
497 });
498
499 it('nennt im leeren Feld dasselbe wie im Hinweis darunter', () => {
500 const faelle: readonly [string, Project][] = [
501 ['Gelbzeit', mitAnsatz('gelbzeit')],
502 ['fest', mitAnsatz('fest')],
503 ['fest, abweichend', mitAnsatz('fest', {}, { crossingTimeKfzAbbiegend: 4 })],
504 ['Engstelle, Gelbzeit', baustelle('gelbzeit')],
505 ['Engstelle, fest', baustelle('fest')],
506 ];
507 for (const [name, project] of faelle) {
508 const { platzhalter, hinweis } = ueberfahrzeitFeld(project, 'K1');
509 expect(hinweis, name).toContain(platzhalter);
510 }
511 });
512 });