import { afterEach, describe, expect, it } from 'vitest'; import { ASSISTENT_VORGABE, engstellenFolgen, starteAssistent, vorgabenDerNeuenPlanung, } from '@/ui/assistent'; import * as fmt from '@/ui/format'; /** * Gefuehrter Einstieg: Was nach einer verworfenen Eingabe im Feld steht, muss * die Zahl sein, mit der der Assistent weiterrechnet (Befund 21 zur * Fassung 5.8.0). * * `numberCell` fuehrt den zuletzt uebernommenen Wert selbst mit und stellt ihn * wieder her, wenn eine Eingabe verworfen wird - ohne `onChange` zu rufen * (Fassung 5.9.0). Bis dahin merkte es sich den beim Bau uebergebenen Wert. * Ueberall sonst fiel das nicht auf, weil sich die Ansicht nach jeder Aenderung * neu aufbaut und das Feld dabei mit dem gespeicherten Wert neu entsteht. Der * Assistent zeichnet den Schritt bewusst NICHT neu; der Rueckfallwert blieb * dort dauerhaft der Startwert des Schrittes. * * Was der Anwender sonst erlebt: Er traegt bei der einstreifigen * Verkehrsfuehrung 60 m ein, leert das Feld versehentlich und liest "Der * vorherige Wert 120 wurde wiederhergestellt". Im Feld stehen 120 m - angelegt * wird die Planung aber aus 60 m. Der Raeumweg beider Fahrtrichtungen ist bei * einer Engstelle die ganze Laenge; die Zwischenzeit faellt damit um rund * 6,0 s kuerzer aus, als die angezeigte Laenge hergibt, und die Gegenrichtung * bekommt Freigabe, waehrend noch geraeumt wird. * * Bewacht ist hier, was im FELD steht und womit angelegt wird; den WORTLAUT der * Meldung bewacht `tests/ui/rueckfallwert.test.ts`. */ const dialog = (): HTMLElement => { const knoten = document.querySelector('.dialog'); expect(knoten, 'kein Fenster des gefuehrten Einstiegs').not.toBeNull(); return knoten ?? document.body; }; const knopf = (beschriftung: string): HTMLButtonElement => { const gefunden = [ ...dialog().querySelectorAll('.dialog-aktionen button'), ].find((b) => (b.textContent ?? '').trim() === beschriftung); expect(gefunden, `Schaltflaeche "${beschriftung}" fehlt`).toBeDefined(); return gefunden as HTMLButtonElement; }; const feldMit = (beschriftung: string): HTMLInputElement => { const label = [...dialog().querySelectorAll('label')].find( (l) => (l.textContent ?? '').trim() === beschriftung, ); expect(label, `Feld "${beschriftung}" fehlt`).toBeDefined(); return dialog().querySelector(`#${(label as HTMLLabelElement).htmlFor}`)!; }; /** * Der Satz des Folgenkastens zur Zwischenzeit einer Engstelle dieser Laenge - * gerechnet mit denselben Vorgaben wie der Assistent, statt eine Sekundenzahl * in den Test zu schreiben. */ const zwischenzeitSatz = (laenge: number): string => { const angaben = { ...ASSISTENT_VORGABE, form: 'einstreifig' as const, engstellenlaenge: laenge }; const folgen = engstellenFolgen(laenge, angaben.vZul, vorgabenDerNeuenPlanung(angaben)); return `Die Zwischenzeit beträgt je Fahrtrichtung ${fmt.seconds(folgen.zwischenzeit)}`; }; /** Eine Eingabe so absetzen, wie sie beim Verlassen des Feldes ankommt. */ const setzeZahl = (feld: HTMLInputElement, wert: string): void => { feld.value = wert; feld.dispatchEvent(new Event('change')); }; /** Anlagenform im ersten Schritt waehlen. */ const waehleForm = (form: string): void => { const auswahl = dialog().querySelector('select')!; auswahl.value = form; auswahl.dispatchEvent(new Event('change')); }; afterEach(() => { // Ein Fenster, das ein fehlgeschlagener Testfall stehen laesst, wuerde der // naechste ueber `.dialog` erwischen. for (const knoten of document.querySelectorAll('.dialog-hintergrund, .meldung')) knoten.remove(); }); describe('Gefuehrter Einstieg: Rueckfallwert nach einer verworfenen Eingabe (Befund 21)', () => { it('zeigt bei der Engstellenlaenge den zuletzt uebernommenen Wert und legt genau damit an', async () => { const ergebnis = starteAssistent(); waehleForm('einstreifig'); knopf('Weiter').click(); const laenge = feldMit('Länge der Engstelle'); expect(laenge.value, 'Startwert des Schrittes').toBe('120'); setzeZahl(laenge, '60'); setzeZahl(laenge, ''); expect( laenge.value, 'Nach der verworfenen Eingabe steht der Startwert im Feld, nicht der zuletzt uebernommene', ).toBe('60'); // Der Folgenkasten rechnet mit dem uebernommenen Wert - Feld und Kasten // duerfen sich nicht widersprechen. const kasten = dialog().querySelector('.hinweisbalken'); expect(kasten?.textContent ?? '').toContain(zwischenzeitSatz(60)); expect(kasten?.textContent ?? '').not.toContain(zwischenzeitSatz(120)); knopf('Weiter').click(); knopf('Verkehrsführung anlegen').click(); const projekt = await ergebnis; expect(projekt).not.toBeNull(); for (const konflikt of projekt!.conflicts) { expect(konflikt.clearingDistance, 'Raeumweg beider Richtungen = angezeigte Laenge').toBe(60); } }); it('zeigt bei der zulaessigen Hoechstgeschwindigkeit den zuletzt uebernommenen Wert', async () => { const ergebnis = starteAssistent(); const vZul = feldMit('Zulässige Höchstgeschwindigkeit'); expect(vZul.value).toBe('50'); setzeZahl(vZul, '30'); setzeZahl(vZul, ''); expect(vZul.value, 'Startwert statt des zuletzt uebernommenen Wertes').toBe('30'); waehleForm('einstreifig'); knopf('Weiter').click(); knopf('Weiter').click(); knopf('Verkehrsführung anlegen').click(); const projekt = await ergebnis; expect(projekt!.intersection.vZul, 'angelegt wird mit der angezeigten Geschwindigkeit').toBe( 30, ); }); it('laesst eine verworfene Eingabe ohne vorherige Aenderung beim Startwert', async () => { // Gegenprobe: Wo noch nichts uebernommen wurde, IST der Startwert der // zuletzt gueltige Wert - an diesem Fall aendert sich nichts. const ergebnis = starteAssistent(); const vZul = feldMit('Zulässige Höchstgeschwindigkeit'); setzeZahl(vZul, ''); expect(vZul.value).toBe('50'); knopf('Abbrechen').click(); expect(await ergebnis).toBeNull(); }); });