lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui assistentRueckfallwert.test.ts
| 1 | import { afterEach, describe, expect, it } from 'vitest'; |
| 2 | import { |
| 3 | ASSISTENT_VORGABE, |
| 4 | engstellenFolgen, |
| 5 | starteAssistent, |
| 6 | vorgabenDerNeuenPlanung, |
| 7 | } from '@/ui/assistent'; |
| 8 | import * as fmt from '@/ui/format'; |
| 9 | |
| 10 | /** |
| 11 | * Gefuehrter Einstieg: Was nach einer verworfenen Eingabe im Feld steht, muss |
| 12 | * die Zahl sein, mit der der Assistent weiterrechnet (Befund 21 zur |
| 13 | * Fassung 5.8.0). |
| 14 | * |
| 15 | * `numberCell` fuehrt den zuletzt uebernommenen Wert selbst mit und stellt ihn |
| 16 | * wieder her, wenn eine Eingabe verworfen wird - ohne `onChange` zu rufen |
| 17 | * (Fassung 5.9.0). Bis dahin merkte es sich den beim Bau uebergebenen Wert. |
| 18 | * Ueberall sonst fiel das nicht auf, weil sich die Ansicht nach jeder Aenderung |
| 19 | * neu aufbaut und das Feld dabei mit dem gespeicherten Wert neu entsteht. Der |
| 20 | * Assistent zeichnet den Schritt bewusst NICHT neu; der Rueckfallwert blieb |
| 21 | * dort dauerhaft der Startwert des Schrittes. |
| 22 | * |
| 23 | * Was der Anwender sonst erlebt: Er traegt bei der einstreifigen |
| 24 | * Verkehrsfuehrung 60 m ein, leert das Feld versehentlich und liest "Der |
| 25 | * vorherige Wert 120 wurde wiederhergestellt". Im Feld stehen 120 m - angelegt |
| 26 | * wird die Planung aber aus 60 m. Der Raeumweg beider Fahrtrichtungen ist bei |
| 27 | * einer Engstelle die ganze Laenge; die Zwischenzeit faellt damit um rund |
| 28 | * 6,0 s kuerzer aus, als die angezeigte Laenge hergibt, und die Gegenrichtung |
| 29 | * bekommt Freigabe, waehrend noch geraeumt wird. |
| 30 | * |
| 31 | * Bewacht ist hier, was im FELD steht und womit angelegt wird; den WORTLAUT der |
| 32 | * Meldung bewacht `tests/ui/rueckfallwert.test.ts`. |
| 33 | */ |
| 34 | |
| 35 | const dialog = (): HTMLElement => { |
| 36 | const knoten = document.querySelector<HTMLElement>('.dialog'); |
| 37 | expect(knoten, 'kein Fenster des gefuehrten Einstiegs').not.toBeNull(); |
| 38 | return knoten ?? document.body; |
| 39 | }; |
| 40 | |
| 41 | const knopf = (beschriftung: string): HTMLButtonElement => { |
| 42 | const gefunden = [ |
| 43 | ...dialog().querySelectorAll<HTMLButtonElement>('.dialog-aktionen button'), |
| 44 | ].find((b) => (b.textContent ?? '').trim() === beschriftung); |
| 45 | expect(gefunden, `Schaltflaeche "${beschriftung}" fehlt`).toBeDefined(); |
| 46 | return gefunden as HTMLButtonElement; |
| 47 | }; |
| 48 | |
| 49 | const feldMit = (beschriftung: string): HTMLInputElement => { |
| 50 | const label = [...dialog().querySelectorAll('label')].find( |
| 51 | (l) => (l.textContent ?? '').trim() === beschriftung, |
| 52 | ); |
| 53 | expect(label, `Feld "${beschriftung}" fehlt`).toBeDefined(); |
| 54 | return dialog().querySelector<HTMLInputElement>(`#${(label as HTMLLabelElement).htmlFor}`)!; |
| 55 | }; |
| 56 | |
| 57 | /** |
| 58 | * Der Satz des Folgenkastens zur Zwischenzeit einer Engstelle dieser Laenge - |
| 59 | * gerechnet mit denselben Vorgaben wie der Assistent, statt eine Sekundenzahl |
| 60 | * in den Test zu schreiben. |
| 61 | */ |
| 62 | const zwischenzeitSatz = (laenge: number): string => { |
| 63 | const angaben = { ...ASSISTENT_VORGABE, form: 'einstreifig' as const, engstellenlaenge: laenge }; |
| 64 | const folgen = engstellenFolgen(laenge, angaben.vZul, vorgabenDerNeuenPlanung(angaben)); |
| 65 | return `Die Zwischenzeit beträgt je Fahrtrichtung ${fmt.seconds(folgen.zwischenzeit)}`; |
| 66 | }; |
| 67 | |
| 68 | /** Eine Eingabe so absetzen, wie sie beim Verlassen des Feldes ankommt. */ |
| 69 | const setzeZahl = (feld: HTMLInputElement, wert: string): void => { |
| 70 | feld.value = wert; |
| 71 | feld.dispatchEvent(new Event('change')); |
| 72 | }; |
| 73 | |
| 74 | /** Anlagenform im ersten Schritt waehlen. */ |
| 75 | const waehleForm = (form: string): void => { |
| 76 | const auswahl = dialog().querySelector<HTMLSelectElement>('select')!; |
| 77 | auswahl.value = form; |
| 78 | auswahl.dispatchEvent(new Event('change')); |
| 79 | }; |
| 80 | |
| 81 | afterEach(() => { |
| 82 | // Ein Fenster, das ein fehlgeschlagener Testfall stehen laesst, wuerde der |
| 83 | // naechste ueber `.dialog` erwischen. |
| 84 | for (const knoten of document.querySelectorAll('.dialog-hintergrund, .meldung')) knoten.remove(); |
| 85 | }); |
| 86 | |
| 87 | describe('Gefuehrter Einstieg: Rueckfallwert nach einer verworfenen Eingabe (Befund 21)', () => { |
| 88 | it('zeigt bei der Engstellenlaenge den zuletzt uebernommenen Wert und legt genau damit an', async () => { |
| 89 | const ergebnis = starteAssistent(); |
| 90 | |
| 91 | waehleForm('einstreifig'); |
| 92 | knopf('Weiter').click(); |
| 93 | |
| 94 | const laenge = feldMit('Länge der Engstelle'); |
| 95 | expect(laenge.value, 'Startwert des Schrittes').toBe('120'); |
| 96 | |
| 97 | setzeZahl(laenge, '60'); |
| 98 | setzeZahl(laenge, ''); |
| 99 | |
| 100 | expect( |
| 101 | laenge.value, |
| 102 | 'Nach der verworfenen Eingabe steht der Startwert im Feld, nicht der zuletzt uebernommene', |
| 103 | ).toBe('60'); |
| 104 | |
| 105 | // Der Folgenkasten rechnet mit dem uebernommenen Wert - Feld und Kasten |
| 106 | // duerfen sich nicht widersprechen. |
| 107 | const kasten = dialog().querySelector<HTMLElement>('.hinweisbalken'); |
| 108 | expect(kasten?.textContent ?? '').toContain(zwischenzeitSatz(60)); |
| 109 | expect(kasten?.textContent ?? '').not.toContain(zwischenzeitSatz(120)); |
| 110 | |
| 111 | knopf('Weiter').click(); |
| 112 | knopf('Verkehrsführung anlegen').click(); |
| 113 | |
| 114 | const projekt = await ergebnis; |
| 115 | expect(projekt).not.toBeNull(); |
| 116 | for (const konflikt of projekt!.conflicts) { |
| 117 | expect(konflikt.clearingDistance, 'Raeumweg beider Richtungen = angezeigte Laenge').toBe(60); |
| 118 | } |
| 119 | }); |
| 120 | |
| 121 | it('zeigt bei der zulaessigen Hoechstgeschwindigkeit den zuletzt uebernommenen Wert', async () => { |
| 122 | const ergebnis = starteAssistent(); |
| 123 | |
| 124 | const vZul = feldMit('Zulässige Höchstgeschwindigkeit'); |
| 125 | expect(vZul.value).toBe('50'); |
| 126 | |
| 127 | setzeZahl(vZul, '30'); |
| 128 | setzeZahl(vZul, ''); |
| 129 | |
| 130 | expect(vZul.value, 'Startwert statt des zuletzt uebernommenen Wertes').toBe('30'); |
| 131 | |
| 132 | waehleForm('einstreifig'); |
| 133 | knopf('Weiter').click(); |
| 134 | knopf('Weiter').click(); |
| 135 | knopf('Verkehrsführung anlegen').click(); |
| 136 | |
| 137 | const projekt = await ergebnis; |
| 138 | expect(projekt!.intersection.vZul, 'angelegt wird mit der angezeigten Geschwindigkeit').toBe( |
| 139 | 30, |
| 140 | ); |
| 141 | }); |
| 142 | |
| 143 | it('laesst eine verworfene Eingabe ohne vorherige Aenderung beim Startwert', async () => { |
| 144 | // Gegenprobe: Wo noch nichts uebernommen wurde, IST der Startwert der |
| 145 | // zuletzt gueltige Wert - an diesem Fall aendert sich nichts. |
| 146 | const ergebnis = starteAssistent(); |
| 147 | |
| 148 | const vZul = feldMit('Zulässige Höchstgeschwindigkeit'); |
| 149 | setzeZahl(vZul, ''); |
| 150 | expect(vZul.value).toBe('50'); |
| 151 | |
| 152 | knopf('Abbrechen').click(); |
| 153 | expect(await ergebnis).toBeNull(); |
| 154 | }); |
| 155 | }); |