import { afterEach, describe, expect, it } from 'vitest'; import { ProjectStore } from '@/app/store'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { festeWerteZuschreibungSatz } from '@/domain/rilsa/ansaetze'; import { RILSA_DEFAULTS, UEBERFAHRZEIT_ANSATZ_LABELS } from '@/domain/rilsa/constants'; import type { Project } from '@/domain/model/project'; import type { ViewContext } from '@/ui/shell'; import { projectView } from '@/ui/views/projectView'; /** * Befund 6 zur Fassung 5.9.0, Rest in der Projektansicht. * * Der Feldhinweis am Ueberfahrzeit-Ansatz holt die RICHTUNG des Vergleichs * inzwischen aus dem Fachkern (`ueberfahrzeitAnsatzRichtungSatz`), schrieb * danach aber unbedingt: "Die beiden Werte stammen aus behördlichen * Leitfäden." Beide Werte sind ueber die Vorgabenverwaltung einstellbar * (`crossingTimeKfzGeradeaus`, `crossingTimeKfzAbbiegend`, 1 bis 10 s). Wer * 6 s eintraegt, bekam seine eigene Eingabe als Leitfadenwert zurueck - in * derselben Zeile, die ihn davor warnen soll, einen Praxisansatz fuer einen * Regelwerkswert zu halten. * * Im Ausdruck ist dieselbe Zuschreibung behoben (`festeWerteHerkunftSatz` in * services/export/bewertung.ts); hier ist sie nachgezogen. */ const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); const BESCHRIFTUNG = 'Überfahrzeit des Kraftfahrzeugverkehrs'; let aufraeumen: (() => void) | null = null; afterEach(() => { aufraeumen?.(); aufraeumen = null; document.body.replaceChildren(); }); function zeichne(project: Project): HTMLElement { const wurzel = document.createElement('div'); document.body.append(wurzel); const store = new ProjectStore(project); const context: ViewContext = { store, refresh: () => {}, navigate: () => {}, target: null, }; const abmelden = projectView.render(wurzel, context); aufraeumen = () => { abmelden(); wurzel.remove(); }; return wurzel; } /** Sichtbarer Text einer Beschriftung - ohne den Hilfeknopf. */ function beschriftungstext(label: HTMLLabelElement): string { return [...label.childNodes] .filter((knoten) => !(knoten instanceof Element && knoten.classList.contains('hilfe-knopf'))) .map((knoten) => knoten.textContent ?? '') .join('') .trim(); } /** Der Feldhinweis, der zum Auswahlfeld des Ansatzes vorgelesen wird. */ function feldhinweis(wurzel: HTMLElement): string { const label = [...wurzel.querySelectorAll('label')].find( (l) => beschriftungstext(l) === BESCHRIFTUNG, ); const id = label?.getAttribute('for') ?? ''; const feld = wurzel.querySelector(`[id="${id}"]`); const beschreibung = feld?.getAttribute('aria-describedby') ?? ''; return beschreibung .split(' ') .map((teil) => wurzel.querySelector(`[id="${teil}"]`)?.textContent ?? '') .join(' '); } function mitVorgaben(rilsa: Record): Project { const basis = createStandardIntersectionProject('Überfahrzeit', FESTES_DATUM); return { ...basis, settings: { ...basis.settings, rilsa } }; } describe('Projekt: die Herkunft der festen Überfahrzeiten steht nur, wo sie zutrifft', () => { it('nennt bei unveränderten Vorgaben die behördlichen Leitfäden', () => { // Grenzwache: Solange die Regelwerte gelten, ist die Zuschreibung richtig // und muss stehen bleiben. // // MIT DER SATZFUGE: Der Herkunftssatz steht zwischen zwei anderen Saetzen, // und der folgende beginnt mit "Der Wortlaut der RiLSA 2015". Schliesst der // Zweig mit einem Strichpunkt, liest sich der Hinweis "... Leitfäden; Der // Wortlaut ..." - nach einem Strichpunkt steht kein grossgeschriebener // Artikel. Ein blosses toContain('behördlichen Leitfäden') bewacht die // Stelle nicht, deshalb die Fuge im Text. const wurzel = zeichne(createStandardIntersectionProject('Überfahrzeit', FESTES_DATUM)); const hinweis = feldhinweis(wurzel); expect(hinweis, 'Vorbedingung: der Hinweis am Ansatzfeld wurde gefunden').toContain( UEBERFAHRZEIT_ANSATZ_LABELS.gelbzeit, ); // Seit 5.27.0 nennt der Satz die Fundstelle: Die beiden Zahlen stehen in // der RiLSA 2015 und nicht nur in behoerdlichen Leitfaeden. expect(hinweis).toContain('Die beiden Werte sind die Regelwerte der RiLSA 2015'); }); it('schreibt eingetragene Vorgabewerte nicht behördlichen Leitfäden zu', () => { const wurzel = zeichne( mitVorgaben({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 }), ); const hinweis = feldhinweis(wurzel); expect(hinweis, 'Vorbedingung: die eingetragenen Werte stehen im Hinweis').toContain('6 s'); expect( hinweis, 'Der Hinweis gibt eingetragene Vorgabewerte als Leitfadenwerte aus', ).not.toContain('sind die Regelwerte der RiLSA 2015 (Abschnitt 2.5.2).'); // Die belegten Werte gehoeren daneben - sonst weiss der Leser nicht, wovon // sein Plan abweicht. expect(hinweis).toContain( `${RILSA_DEFAULTS.crossingTime.kfzGeradeaus} s geradeaus und ` + `${RILSA_DEFAULTS.crossingTime.kfzAbbiegend} s abbiegend`, ); }); it('nennt das Regelwerk in beiden Fällen', () => { /* * UMGESTELLT MIT 5.27.0: Hier stand der Vorbehalt "der Wortlaut der * RiLSA 2015 zu den Zwischenzeitfällen 1 und 2 ist öffentlich nicht * abgleichbar". Er ist entfallen; geblieben ist, dass der Hinweis das * Regelwerk auch dann nennt, wenn eigene Werte eingetragen sind - dann * eben als das, wovon sie abweichen. */ const wurzel = zeichne(mitVorgaben({ crossingTimeKfzGeradeaus: 6 })); const hinweis = feldhinweis(wurzel); expect(hinweis).not.toContain('nicht abgleichbar'); expect(hinweis).toContain('RiLSA 2015'); }); }); /* * NACHTRAG (Fassung 5.43.0): Derselbe Befund eine Zeile tiefer. * * Der Feldhinweis oben unterscheidet seit 5.9.0 zwischen Regelwerten und * Vorgaben dieses Plans. Der WARNBALKEN darunter - er erscheint, sobald der * feste Ansatz gewaehlt ist - tat es nicht: Er sagte unbedingt "Das sind die * Regelwerte der RiLSA 2015 (Abschnitt 2.5.2, Fälle 1 und 2)" und nannte die * Gelbzeitkopplung "den längeren Ansatz dieses Programms". Beides war zu stark: * Bei 50 km/h geradeaus ergeben beide Ansaetze 3 s, und bei 6 s festen Vorgaben * ist die Gelbzeit die kuerzere. */ describe('Projekt: der Warnbalken zum festen Ansatz sagt, woher die Zahlen stammen', () => { function mitFestemAnsatz(rilsa: Record = {}): Project { const basis = createStandardIntersectionProject('Überfahrzeit', FESTES_DATUM); return { ...basis, settings: { ...basis.settings, ueberfahrzeitAnsatz: 'fest', rilsa } }; } /** Text des Warnbalkens unter dem Auswahlfeld des Ansatzes. */ function balken(wurzel: HTMLElement): string { const treffer = [...wurzel.querySelectorAll('.hinweisbalken-warnung')].find((k) => (k.textContent ?? '').includes('Feste Überfahrzeit gewählt'), ); expect(treffer, 'Der Warnbalken zum festen Ansatz fehlt').not.toBeUndefined(); return (treffer?.textContent ?? '').replace(/\s+/gu, ' '); } /** Nur die Ueberschrift des Balkens - die staerkste Aussage darin. */ function ueberschrift(wurzel: HTMLElement): string { const treffer = [...wurzel.querySelectorAll('.hinweisbalken-warnung')].find((k) => (k.textContent ?? '').includes('Feste Überfahrzeit gewählt'), ); return treffer?.querySelector('strong')?.textContent ?? ''; } it('bleibt bei den Regelwerten bei der Zuschreibung an die RiLSA', () => { // Grenzwache: Solange die Regelwerte gelten, ist der Satz richtig. const text = balken(zeichne(mitFestemAnsatz())); expect(text).toContain('Die Werte sind die Regelwerte der RiLSA 2015 (Abschnitt 2.5.2'); expect(text, 'die Richtung des Regelansatzes').toContain('rechnet nie kürzer als diese'); expect(text, 'die Uebertreibung').not.toContain('längere Ansatz'); }); it('nennt eingetragene Werte Vorgaben dieses Plans', () => { const text = balken( zeichne(mitFestemAnsatz({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 })), ); expect(text, 'Vorbedingung: die eingetragenen Werte stehen im Balken').toContain('6 s'); expect(text).toContain('Die Werte sind Vorgaben dieses Plans'); expect(text).not.toContain('Die Werte sind die Regelwerte der RiLSA 2015'); expect(text, 'die belegten Zahlen gehoeren daneben').toContain( `${RILSA_DEFAULTS.crossingTime.kfzGeradeaus} s geradeaus und ` + `${RILSA_DEFAULTS.crossingTime.kfzAbbiegend} s abbiegend`, ); }); /* * NACHTRAG (Befund 4): Der Satz im Balken war bedingt gemacht, die * UEBERSCHRIFT darueber nicht - und sie ist die staerkste und sichtbarste * Aussage des Balkens. Bei 6 s festen Vorgaben stand dort weiter "die * Zwischenzeiten fallen kürzer aus", waehrend der Satz drei Zeilen tiefer * "nirgends kürzer ... nicht verkürzt" sagte. Gemessen: * `ueberfahrzeitVerkuerzung` liefert dann -1 s je Richtung. */ it('sagt in der Ueberschrift nur die Richtung, die die Vorgaben hergeben', () => { expect( ueberschrift(zeichne(mitFestemAnsatz())), 'bei den Regelwerten verkuerzt der Ansatz wirklich', ).toBe('Feste Überfahrzeit gewählt – die Zwischenzeiten fallen kürzer aus'); const mitSechs = ueberschrift( zeichne(mitFestemAnsatz({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 })), ); expect(mitSechs, 'Vorbedingung: der Balken ist da').toContain('Feste Überfahrzeit gewählt'); expect(mitSechs).not.toContain('fallen kürzer aus'); expect(mitSechs).toContain('nicht verkürzt'); }); it('widerspricht dem Feldhinweis darueber nicht', () => { /* * DIE PROBE, DIE DEN BEFUND GEFUNDEN HAETTE: Ueberschrift, Feldhinweis und * Balken stehen im selben Bildschirmausschnitt und beantworten dieselbe * Frage. Der Feldhinweis holte die Richtung seit 5.9.0 aus dem Fachkern, * die Ueberschrift des Balkens und der Nebensatz zum Pruefbericht nicht - * drei Zeilen auseinander stand beides, "nirgends kürzer" und "fallen * kürzer aus". */ const wurzel = zeichne( mitFestemAnsatz({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 }), ); const zusammen = `${ueberschrift(wurzel)} ${feldhinweis(wurzel)} ${balken(wurzel)}`; expect(zusammen, 'Vorbedingung: die Richtung steht im Feldhinweis').toContain( 'fällt die Überfahrzeit nirgends kürzer aus', ); expect(zusammen, 'keine Verkuerzung behaupten, wo keine ist').not.toContain( 'fallen kürzer aus', ); expect(zusammen, 'auch nicht im Nebensatz zum Pruefbericht').not.toContain( 'kürzere Zwischenzeiten ergibt', ); }); it('sagt auch der Sprachausgabe nur die Richtung, die gilt', () => { /* * DIE KURZMELDUNG BEIM UMSCHALTEN. Sie ist fuer den da, der ohne Bildschirm * plant - er hoert diesen einen Satz und sieht den Balken nie. Er sagte * unbedingt "Die Zwischenzeiten der Kraftfahrzeugströme fallen kürzer aus". */ const wurzel = zeichne( mitVorgaben({ crossingTimeKfzGeradeaus: 6, crossingTimeKfzAbbiegend: 6 }), ); const label = [...wurzel.querySelectorAll('label')].find( (l) => beschriftungstext(l) === BESCHRIFTUNG, ); const feld = wurzel.querySelector( `[id="${label?.getAttribute('for') ?? ''}"]`, ); expect(feld, 'Vorbedingung: das Auswahlfeld ist da').not.toBeNull(); feld!.value = 'fest'; feld!.dispatchEvent(new Event('change')); const meldung = [...document.querySelectorAll('.meldung')] .map((k) => k.textContent ?? '') .join(' '); expect(meldung, 'Vorbedingung: die Kurzmeldung ist erschienen').toContain( 'Feste Überfahrzeit gewählt', ); expect(meldung).toContain('nirgends kürzer aus'); expect(meldung).not.toContain('fallen kürzer aus'); }); it('haelt Balken und Pruefbericht auf derselben Antwort', () => { /* * Der Balken holt den Satz aus demselben Fachkern wie der Pruefbericht * (ansaetze.ts, festeWerteZuschreibungSatz). Ohne das waeren es zwei * Fassungen derselben Aussage - der Fehlertyp, aus dem dieser Befund * entstanden ist. */ expect(balken(zeichne(mitFestemAnsatz()))).toContain( festeWerteZuschreibungSatz(RILSA_DEFAULTS), ); }); });