lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui rueckfallwert.test.ts
| 1 | import { afterEach, describe, expect, it } from 'vitest'; |
| 2 | import { numberCell } from '@/ui/inputs'; |
| 3 | |
| 4 | /** |
| 5 | * Der Rueckfallwert des Zahlenfelds ist der zuletzt UEBERNOMMENE Wert, nicht |
| 6 | * der beim Bau uebergebene. |
| 7 | * |
| 8 | * `numberCell` hielt den Parameter `value` fest und las ihn in beiden |
| 9 | * Wiederherstellungswegen sowie im Meldungssatz. Fuer jeden Aufrufer, der die |
| 10 | * Ansicht nicht nach jeder Eingabe neu baut, ist dieser Wert veraltet: Wer 60 |
| 11 | * eintraegt und danach etwas Ungueltiges, bekommt den Bauwert 120 zurueck - im |
| 12 | * Feld und im Satz -, obwohl 60 der zuletzt gueltige Wert war und der Aufrufer |
| 13 | * genau damit weiterrechnet. |
| 14 | * |
| 15 | * Gefunden im gefuehrten Einstieg (Befund 21 zur Fassung 5.8.0): Bei der |
| 16 | * einstreifigen Verkehrsfuehrung IST die Engstellenlaenge der Raeumweg beider |
| 17 | * Fahrtrichtungen; Feld und Rechnung wichen um 60 m voneinander ab, die |
| 18 | * Zwischenzeit fiel um 6,0 s kuerzer aus als die angezeigte Laenge hergab. |
| 19 | * Der Assistent fing das mit einer eigenen Bauhilfe ab - hier ist die Ursache |
| 20 | * bewacht, unabhaengig von jedem Aufrufer. |
| 21 | * |
| 22 | * Nachgestellt wird ein Aufrufer OHNE Neuaufbau: Das Feld wird einmal gebaut |
| 23 | * und bleibt stehen; nur der Empfaenger schreibt mit. |
| 24 | */ |
| 25 | |
| 26 | afterEach(() => { |
| 27 | document.body.replaceChildren(); |
| 28 | }); |
| 29 | |
| 30 | /** Der Text der zuletzt gezeigten Warnung. */ |
| 31 | function letzteWarnung(): string { |
| 32 | const meldungen = [...document.querySelectorAll('.meldung-warnung')]; |
| 33 | return meldungen[meldungen.length - 1]?.textContent ?? ''; |
| 34 | } |
| 35 | |
| 36 | /** Eine Eingabe so absetzen, wie sie beim Verlassen des Feldes ankommt. */ |
| 37 | function setzeZahl(feld: HTMLInputElement, wert: string): void { |
| 38 | feld.value = wert; |
| 39 | feld.dispatchEvent(new Event('change')); |
| 40 | } |
| 41 | |
| 42 | /** |
| 43 | * Eine unlesbare Eingabe absetzen. |
| 44 | * |
| 45 | * Ein `input[type=number]` gibt sie als leere Zeichenkette heraus - in jsdom wie |
| 46 | * im Browser. Um den zweiten Rueckfallweg zu erreichen, wird der Feldtyp fuer |
| 47 | * die Eingabe kurz auf `text` gestellt; der Empfaenger liest nur `input.value` |
| 48 | * und sieht davon nichts. |
| 49 | */ |
| 50 | function setzeUnlesbar(feld: HTMLInputElement, wert: string): void { |
| 51 | const zuvor = feld.getAttribute('type') ?? 'number'; |
| 52 | feld.setAttribute('type', 'text'); |
| 53 | setzeZahl(feld, wert); |
| 54 | feld.setAttribute('type', zuvor); |
| 55 | } |
| 56 | |
| 57 | describe('Zahlenfeld: Rueckfall auf den zuletzt uebernommenen Wert', () => { |
| 58 | it('stellt beim geleerten Pflichtfeld den uebernommenen Wert her, nicht den Bauwert', () => { |
| 59 | // Engstellenlaenge im gefuehrten Einstieg: gebaut mit 120, eingetragen 60. |
| 60 | let gerechnet = 120; |
| 61 | const feld = numberCell( |
| 62 | 120, |
| 63 | (wert) => { |
| 64 | if (wert !== null) gerechnet = wert; |
| 65 | }, |
| 66 | { min: 10, max: 400, step: 10, einheit: 'm' }, |
| 67 | ); |
| 68 | feld.setAttribute('aria-label', 'Länge der Engstelle'); |
| 69 | document.body.append(feld); |
| 70 | |
| 71 | setzeZahl(feld, '60'); |
| 72 | expect(gerechnet, 'der Aufrufer rechnet ab hier mit 60').toBe(60); |
| 73 | |
| 74 | setzeZahl(feld, ''); |
| 75 | |
| 76 | expect(feld.value, 'im Feld steht der zuletzt uebernommene Wert').toBe('60'); |
| 77 | expect(letzteWarnung()).toContain('Der vorherige Wert 60 wurde wiederhergestellt'); |
| 78 | expect(letzteWarnung(), 'der Bauwert ist nicht mehr der vorherige Wert').not.toContain('120'); |
| 79 | expect(gerechnet, 'eine abgelehnte Eingabe aendert die Rechnung nicht').toBe(60); |
| 80 | }); |
| 81 | |
| 82 | it('nennt bei unlesbarer Eingabe den uebernommenen Wert im Feld und im Satz', () => { |
| 83 | const feld = numberCell(120, () => {}, { min: 10, max: 400, step: 10, einheit: 'm' }); |
| 84 | feld.setAttribute('aria-label', 'Länge der Engstelle'); |
| 85 | document.body.append(feld); |
| 86 | |
| 87 | setzeZahl(feld, '60'); |
| 88 | setzeUnlesbar(feld, 'abc'); |
| 89 | |
| 90 | expect(feld.value).toBe('60'); |
| 91 | expect(letzteWarnung()).toContain('Der vorherige Wert 60 gilt weiter'); |
| 92 | expect(letzteWarnung()).not.toContain('120'); |
| 93 | }); |
| 94 | |
| 95 | it('faellt nach einer begrenzten Eingabe auf den begrenzten Wert zurueck', () => { |
| 96 | // Zulaessige Hoechstgeschwindigkeit: 200 wird auf 130 begrenzt und genau so |
| 97 | // weitergegeben - 130 ist ab da der zuletzt uebernommene Wert. |
| 98 | let gerechnet = 50; |
| 99 | const feld = numberCell( |
| 100 | 50, |
| 101 | (wert) => { |
| 102 | if (wert !== null) gerechnet = wert; |
| 103 | }, |
| 104 | { min: 5, max: 130, step: 5, einheit: 'km/h' }, |
| 105 | ); |
| 106 | feld.setAttribute('aria-label', 'Zulässige Höchstgeschwindigkeit'); |
| 107 | document.body.append(feld); |
| 108 | |
| 109 | setzeZahl(feld, '200'); |
| 110 | expect(feld.value, 'begrenzt auf die Obergrenze').toBe('130'); |
| 111 | expect(gerechnet).toBe(130); |
| 112 | |
| 113 | setzeZahl(feld, ''); |
| 114 | |
| 115 | expect(feld.value).toBe('130'); |
| 116 | expect(letzteWarnung()).toContain('Der vorherige Wert 130 wurde wiederhergestellt'); |
| 117 | }); |
| 118 | |
| 119 | it('faellt bei uebernommenen Werten ausserhalb der Grenzen auf diesen Wert zurueck', () => { |
| 120 | // `ausserhalbUebernehmen`: Der Wert wird NICHT begrenzt, sondern |
| 121 | // unveraendert weitergereicht - dann ist er auch der Rueckfall. |
| 122 | const feld = numberCell(15, () => {}, { |
| 123 | min: 0, |
| 124 | max: 100, |
| 125 | step: 1, |
| 126 | einheit: '%', |
| 127 | ausserhalbUebernehmen: 'Außerhalb wird die Angabe verworfen.', |
| 128 | }); |
| 129 | feld.setAttribute('aria-label', 'Lastzüge am SV'); |
| 130 | document.body.append(feld); |
| 131 | |
| 132 | setzeZahl(feld, '120'); |
| 133 | expect(feld.value, 'der Wert bleibt unveraendert stehen').toBe('120'); |
| 134 | |
| 135 | setzeUnlesbar(feld, 'abc'); |
| 136 | |
| 137 | expect(feld.value).toBe('120'); |
| 138 | expect(letzteWarnung()).toContain('Der vorherige Wert 120 gilt weiter'); |
| 139 | expect(letzteWarnung(), 'der Bauwert ist nicht mehr der vorherige Wert').not.toContain('15'); |
| 140 | }); |
| 141 | |
| 142 | it('hat nach einem zulaessig geleerten Feld keinen vorherigen Wert mehr', () => { |
| 143 | // `allowEmpty`: Das leere Feld wird als `null` uebernommen. Danach gibt es |
| 144 | // keinen vorherigen Wert - der Satz darf keinen nennen. |
| 145 | const feld = numberCell(12, () => {}, { min: 0, max: 100, allowEmpty: true }); |
| 146 | feld.setAttribute('aria-label', 'Lastzüge am SV'); |
| 147 | document.body.append(feld); |
| 148 | |
| 149 | setzeZahl(feld, ''); |
| 150 | expect(feld.value).toBe(''); |
| 151 | |
| 152 | setzeUnlesbar(feld, 'abc'); |
| 153 | |
| 154 | expect(feld.value, 'ohne vorherigen Wert bleibt das Feld leer').toBe(''); |
| 155 | expect(letzteWarnung(), 'ohne vorherigen Wert steht keine Zahl im Satz').not.toMatch(/\d/); |
| 156 | }); |
| 157 | |
| 158 | it('laesst den Bauwert stehen, solange nichts uebernommen wurde', () => { |
| 159 | // Gegenprobe: Wo noch keine Eingabe angekommen ist, IST der Bauwert der |
| 160 | // zuletzt gueltige Wert - an diesem Fall aendert sich nichts. |
| 161 | const feld = numberCell(120, () => {}, { min: 10, max: 400, step: 10, einheit: 'm' }); |
| 162 | feld.setAttribute('aria-label', 'Länge der Engstelle'); |
| 163 | document.body.append(feld); |
| 164 | |
| 165 | setzeZahl(feld, ''); |
| 166 | |
| 167 | expect(feld.value).toBe('120'); |
| 168 | expect(letzteWarnung()).toContain('Der vorherige Wert 120 wurde wiederhergestellt'); |
| 169 | }); |
| 170 | }); |