lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui schwerverkehrsaufteilung.test.ts
| 1 | import { afterEach, describe, expect, it } from 'vitest'; |
| 2 | import { ProjectStore } from '@/app/store'; |
| 3 | import { buildSignalPlan } from '@/domain/plan/signalPlan'; |
| 4 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 5 | import type { Project, TrafficDemand } from '@/domain/model/project'; |
| 6 | import { |
| 7 | HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG, |
| 8 | HBS_PKW_GLEICHWERTE, |
| 9 | } from '@/domain/rilsa/constants'; |
| 10 | import { SCHWERVERKEHR_AUFTEILUNG_MELDESCHWELLE } from '@/domain/validation/rules'; |
| 11 | import { HILFE, glossarEintraege, glossarSuche } from '@/ui/help/texte'; |
| 12 | import { |
| 13 | ASSISTENT_VORGABE, |
| 14 | assistentZusammenfassung, |
| 15 | projektAusAngaben, |
| 16 | starteAssistent, |
| 17 | } from '@/ui/assistent'; |
| 18 | import type { ViewDefinition } from '@/ui/shell'; |
| 19 | import { meldungsverlauf } from '@/ui/feedback'; |
| 20 | import * as fmt from '@/ui/format'; |
| 21 | import { |
| 22 | lastzuganteilAusserhalbFolge, |
| 23 | lastzuganteilHinweis, |
| 24 | lastzuganteilPlatzhalter, |
| 25 | schwerverkehrsanteilHinweis, |
| 26 | schwerverkehrsfaktorVermerk, |
| 27 | signalGroupsView, |
| 28 | wirksamerSchwerverkehrsfaktor, |
| 29 | } from '@/ui/views/signalGroupsView'; |
| 30 | |
| 31 | /** |
| 32 | * Die Aufteilung des Schwerverkehrs auf dem Bildschirm. |
| 33 | * |
| 34 | * Der Fachkern rechnet nach HBS 2015 Gl. 2-5, sobald an einer |
| 35 | * Verkehrsstaerke ein Lastzuganteil erfasst ist, und sonst unveraendert nach |
| 36 | * Gl. 2-6. Erfassen laesst sich der Anteil aber nur, wenn die Oberflaeche ein |
| 37 | * Feld dafuer hat - und ohne den wirksamen Faktor daneben bliebe eine Eingabe |
| 38 | * folgenlos aussehen: Man traegt 100 % Lastzuege ein, am Bildschirm aendert |
| 39 | * sich nichts, und ob das Feld ueberhaupt gewirkt hat, erfaehrt man erst im |
| 40 | * Ausdruck. |
| 41 | * |
| 42 | * GEGEN DEN ALTSTAND SCHLAEGT JEDER UNTERSCHEIDENDE FALL FEHL: Dort gab es |
| 43 | * weder die Spalte noch den Hilfeeintrag noch die Ausfuhr |
| 44 | * `wirksamerSchwerverkehrsfaktor`, und der Satz unter der Tabelle behauptete |
| 45 | * ausdruecklich, "eine Aufteilung nach Fahrzeugklassen erfasst dieses Programm |
| 46 | * nicht". |
| 47 | * |
| 48 | * KORREKTUR DIESER ZUSAGE (Fassung 5.6.0): Hier stand "JEDER FALL". Zwei Faelle |
| 49 | * dieser Datei bestehen auch gegen den Altstand, und das ist Absicht und kein |
| 50 | * Mangel: |
| 51 | * |
| 52 | * - "steht unter der Tabelle, sobald es eine Gruppe mit |
| 53 | * Saettigungsverkehrsstaerke gibt" prueft die VERDRAHTUNG - dass der Satz |
| 54 | * ueberhaupt angezeigt wird - gegen `schwerverkehrsanteilHinweis()`, also |
| 55 | * gegen sich selbst. Er faellt, wenn die Ansicht den Satz weglaesst, und |
| 56 | * nicht, wenn der Satz anders lautet. Was IM Satz steht, pruefen die |
| 57 | * Faelle unter "Satz unter der Signalgruppentabelle", und die schlagen |
| 58 | * gegen den Altstand fehl. |
| 59 | * - "Assistent legt die Verkehrsstaerken ohne Lastzuganteil an" ist eine |
| 60 | * NEGATIVzusage. Auf dem Altstand gilt sie trivial - dort kann es das Feld |
| 61 | * gar nicht geben. Sie ist trotzdem noetig: Sie haelt fest, dass der |
| 62 | * Assistent es auch kuenftig nicht vorbelegt, denn eine Vorbelegung waere |
| 63 | * eine Behauptung ueber eine Zaehlung, die es nicht gibt. |
| 64 | * |
| 65 | * Eine Zusage, die mehr behauptet, als der Nachweis hergibt, ist so wenig wert |
| 66 | * wie die Zusage, die sie prueft. |
| 67 | * |
| 68 | * DIE ZAHLEN SIND NACHGERECHNET, nicht abgeschrieben: |
| 69 | * pSV 20 %, keine Aufteilung -> fSV = 1 + 0,90 · 0,20 = 1,180 -> qS 1695 Kfz/h |
| 70 | * pSV 20 %, 100 % Lastzuege -> fSV = 1 + 1,50 · 0,20 = 1,300 -> qS 1538 Kfz/h |
| 71 | * pSV 20 %, 20 % Lastzuege -> fSV = 1 + 0,75 · 0,16 + 1,50 · 0,04 = 1,180 |
| 72 | * Der letzte Fall ist der Konsistenzbeleg: 1,75 · 0,80 + 2,50 · 0,20 = 1,90, |
| 73 | * also ist Gl. 2-6 nichts anderes als Gl. 2-5 mit 20 % Lastzuegen (BASt-Bericht |
| 74 | * V 400, 2025, S. 10). Wer 20 % eintraegt, aendert an seinen Zahlen nichts - |
| 75 | * und das muss auch der Bildschirm zeigen. |
| 76 | */ |
| 77 | |
| 78 | const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); |
| 79 | |
| 80 | function zeichne( |
| 81 | view: ViewDefinition, |
| 82 | project: Project, |
| 83 | ): { wurzel: HTMLElement; store: ProjectStore; aufraeumen: () => void } { |
| 84 | const wurzel = document.createElement('div'); |
| 85 | document.body.append(wurzel); |
| 86 | const store = new ProjectStore(project); |
| 87 | const abmelden = view.render(wurzel, { |
| 88 | store, |
| 89 | refresh: () => {}, |
| 90 | navigate: () => {}, |
| 91 | target: null, |
| 92 | }); |
| 93 | return { |
| 94 | wurzel, |
| 95 | store, |
| 96 | aufraeumen: () => { |
| 97 | abmelden(); |
| 98 | wurzel.remove(); |
| 99 | }, |
| 100 | }; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Ein Knotenpunkt, bei dem K1 den geprueften Fall traegt: 20 % Schwerverkehr |
| 105 | * und - wenn `lastzugAnteil` gesetzt ist - eine erfasste Aufteilung. Die |
| 106 | * uebrigen Stroeme bleiben unveraendert, damit ein Unterschied in der Zeile K1 |
| 107 | * nicht aus einer Verschiebung des ganzen Plans kommen kann. |
| 108 | */ |
| 109 | function mitAufteilung(lastzugAnteil?: number): Project { |
| 110 | const basis = createStandardIntersectionProject('Aufteilung', FESTES_DATUM); |
| 111 | const k1 = basis.signalGroups.find((g) => g.name === 'K1')!; |
| 112 | return { |
| 113 | ...basis, |
| 114 | demands: basis.demands.map((d) => |
| 115 | d.signalGroupId === k1.id |
| 116 | ? { |
| 117 | ...d, |
| 118 | heavyVehicleShare: 0.2, |
| 119 | // Ausdruecklich weggelassen statt auf `undefined` gesetzt: Mit |
| 120 | // `exactOptionalPropertyTypes` ist das nicht dasselbe, und genau |
| 121 | // dieser Unterschied - Feld da oder nicht - entscheidet ueber die |
| 122 | // Gleichung. |
| 123 | ...(lastzugAnteil === undefined ? {} : { lastzugAnteil }), |
| 124 | } |
| 125 | : d, |
| 126 | ), |
| 127 | }; |
| 128 | } |
| 129 | |
| 130 | function zeile(wurzel: HTMLElement, name: string): HTMLTableRowElement { |
| 131 | const treffer = [...wurzel.querySelectorAll<HTMLTableRowElement>('tbody tr')].find( |
| 132 | (tr) => tr.querySelector<HTMLInputElement>('input[type="text"]')?.value === name, |
| 133 | ); |
| 134 | if (!treffer) throw new Error(`Zeile "${name}" fehlt`); |
| 135 | return treffer; |
| 136 | } |
| 137 | |
| 138 | function lastzugFeld(wurzel: HTMLElement, name: string): HTMLInputElement | null { |
| 139 | return zeile(wurzel, name).querySelector<HTMLInputElement>( |
| 140 | `input[aria-label="Anteil der Lastzüge am Schwerverkehr in Prozent, ${name}"]`, |
| 141 | ); |
| 142 | } |
| 143 | |
| 144 | function faktorVermerk(wurzel: HTMLElement, name: string): HTMLElement | null { |
| 145 | return zeile(wurzel, name).querySelector<HTMLElement>('.schwerverkehrsfaktor'); |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Das Feld "Schwerverkehrsanteil" derselben Zeile - das Gegenstueck, an dem |
| 150 | * BEGRENZEN richtig ist. |
| 151 | * |
| 152 | * Es traegt keinen eigenen `aria-label`-Aufruf in der Ansicht; den Namen |
| 153 | * vergibt `benenneBedienelemente()` aus Spaltenkopf und Zeilennamen. |
| 154 | */ |
| 155 | /** |
| 156 | * Der Satz, den eine Sprachausgabe VOR der Eingabe vorliest. |
| 157 | * |
| 158 | * `numberCell` legt ihn als `data-bereich` ab; beim Einhaengen macht |
| 159 | * `ergaenzeBereichshinweis()` daraus einen eigenen Absatz mit `aria-describedby` |
| 160 | * und entfernt das Merkmal wieder. Geprueft wird deshalb, was am Ende am Feld |
| 161 | * haengt, und nicht der Zwischenstand. |
| 162 | */ |
| 163 | function bereichssatz(feld: HTMLInputElement): string { |
| 164 | const id = feld.getAttribute('aria-describedby'); |
| 165 | if (id === null) return feld.getAttribute('data-bereich') ?? ''; |
| 166 | return document.getElementById(id)?.textContent ?? ''; |
| 167 | } |
| 168 | |
| 169 | function schwerverkehrsfeld(wurzel: HTMLElement, name: string): HTMLInputElement { |
| 170 | const feld = zeile(wurzel, name).querySelector<HTMLInputElement>( |
| 171 | 'input[aria-label^="Schwerverkehrsanteil in Prozent"]', |
| 172 | ); |
| 173 | if (!feld) throw new Error(`Schwerverkehrsfeld der Zeile "${name}" fehlt`); |
| 174 | return feld; |
| 175 | } |
| 176 | |
| 177 | function nachfrage(project: Project, name: string): TrafficDemand { |
| 178 | const gruppe = project.signalGroups.find((g) => g.name === name)!; |
| 179 | const demand = project.demands.find((d) => d.signalGroupId === gruppe.id); |
| 180 | if (!demand) throw new Error(`Keine Verkehrsstärke zu "${name}"`); |
| 181 | return demand; |
| 182 | } |
| 183 | |
| 184 | afterEach(() => { |
| 185 | document.body.replaceChildren(); |
| 186 | }); |
| 187 | |
| 188 | describe('Spalte "Lastzüge am SV"', () => { |
| 189 | it('steht neben dem Schwerverkehrsanteil und führt zur Hilfe über die Aufteilung', () => { |
| 190 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 191 | try { |
| 192 | const koepfe = [...wurzel.querySelectorAll('thead th')]; |
| 193 | const namen = koepfe.map((th) => th.querySelector('span')?.textContent ?? ''); |
| 194 | const svSpalte = namen.indexOf('SV-Anteil'); |
| 195 | expect(svSpalte, 'Spalte "SV-Anteil" fehlt').toBeGreaterThan(-1); |
| 196 | expect(namen[svSpalte + 1]).toBe('Lastzüge am SV'); |
| 197 | |
| 198 | // Der Hilfeknopf der Spalte zeigt auf den neuen Eintrag - sonst landet |
| 199 | // wer fragt beim Schwerverkehrsanteil, der die Aufteilung nicht erklaert. |
| 200 | const knopf = koepfe[svSpalte + 1]!.querySelector<HTMLButtonElement>('.hilfe-knopf'); |
| 201 | expect(knopf, 'Spaltenkopf ohne Hilfeknopf').not.toBeNull(); |
| 202 | expect(knopf!.getAttribute('aria-label')).toBe( |
| 203 | `Hilfe zu ${HILFE.schwerverkehrsaufteilung.titel}`, |
| 204 | ); |
| 205 | } finally { |
| 206 | aufraeumen(); |
| 207 | } |
| 208 | }); |
| 209 | |
| 210 | it('gibt es nur für Signalgruppen mit Sättigungsverkehrsstärke', () => { |
| 211 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 212 | try { |
| 213 | expect(lastzugFeld(wurzel, 'K1'), 'Kfz-Gruppe ohne Feld').not.toBeNull(); |
| 214 | // Fussgaenger werden nach HBS 2015 ueber die maximale Wartezeit bewertet; |
| 215 | // ein Schwerverkehr, den es nicht gibt, laesst sich nicht aufteilen. |
| 216 | expect(lastzugFeld(wurzel, 'F1'), 'Fußgängergruppe mit Aufteilungsfeld').toBeNull(); |
| 217 | expect(faktorVermerk(wurzel, 'F1')).toBeNull(); |
| 218 | } finally { |
| 219 | aufraeumen(); |
| 220 | } |
| 221 | }); |
| 222 | |
| 223 | it('nennt im Platzhalter die Annahme, die bei leerem Feld gilt', () => { |
| 224 | // Nicht "unbekannt": Leer lassen heisst nicht, auf eine Annahme zu |
| 225 | // verzichten, sondern mit 20 % zu rechnen (Gl. 2-6 IST Gl. 2-5 mit 20 %). |
| 226 | expect(lastzuganteilPlatzhalter()).toBe('20 (Annahme)'); |
| 227 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 228 | try { |
| 229 | const feld = lastzugFeld(wurzel, 'K1')!; |
| 230 | expect(feld.value, 'Ohne Erfassung muss das Feld leer sein').toBe(''); |
| 231 | expect(feld.placeholder).toBe(lastzuganteilPlatzhalter()); |
| 232 | } finally { |
| 233 | aufraeumen(); |
| 234 | } |
| 235 | }); |
| 236 | |
| 237 | it('nennt im title die Bezugsgröße, beide Gleichungen und die Herkunft der 20 %', () => { |
| 238 | const hinweis = lastzuganteilHinweis(); |
| 239 | expect(hinweis).toContain('AM SCHWERVERKEHR'); |
| 240 | expect(hinweis).toContain('nicht an der Verkehrsstärke'); |
| 241 | expect(hinweis).toContain('Gleichung (2-5)'); |
| 242 | expect(hinweis).toContain('Gleichung (2-6)'); |
| 243 | expect(hinweis).toContain('1,75'); |
| 244 | expect(hinweis).toContain('2,50'); |
| 245 | expect(hinweis).toContain('1,90'); |
| 246 | expect(hinweis).toContain('20 % Lastzüge am Schwerverkehr'); |
| 247 | expect(hinweis).toContain('BASt-Bericht V 400, 2025, S. 10'); |
| 248 | expect(hinweis).toContain('ändert an den Zahlen nichts'); |
| 249 | |
| 250 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 251 | try { |
| 252 | expect(lastzugFeld(wurzel, 'K1')!.title).toBe(hinweis); |
| 253 | } finally { |
| 254 | aufraeumen(); |
| 255 | } |
| 256 | }); |
| 257 | |
| 258 | it('behält trotz des title seinen Namen für die Sprachausgabe', () => { |
| 259 | // benenneBedienelemente() ueberspringt jedes Feld mit `title`. Ohne |
| 260 | // eigenes aria-label waere der ganze Hinweistext der Name des Felds, und |
| 261 | // jede Meldung von numberCell begaenne damit. |
| 262 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 263 | try { |
| 264 | const feld = lastzugFeld(wurzel, 'K1')!; |
| 265 | expect(feld.getAttribute('aria-label')).toBe( |
| 266 | 'Anteil der Lastzüge am Schwerverkehr in Prozent, K1', |
| 267 | ); |
| 268 | expect(feld.getAttribute('aria-label')).not.toBe(feld.title); |
| 269 | } finally { |
| 270 | aufraeumen(); |
| 271 | } |
| 272 | }); |
| 273 | }); |
| 274 | |
| 275 | describe('Der wirksame Faktor fSV steht an der Zeile', () => { |
| 276 | it('zeigt ohne erfasste Aufteilung den Pauschalwert und sagt, welche Gleichung gilt', () => { |
| 277 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 278 | try { |
| 279 | const vermerk = faktorVermerk(wurzel, 'K1')!; |
| 280 | expect(vermerk.textContent).toBe('fSV 1,180'); |
| 281 | expect(vermerk.title).toContain('Keine Aufteilung des Schwerverkehrs erfasst'); |
| 282 | expect(vermerk.title).toContain('Gleichung (2-6)'); |
| 283 | expect(vermerk.title).toContain('Pauschalwert 1,90'); |
| 284 | expect(vermerk.title).toContain('20 % Lastzüge am Schwerverkehr'); |
| 285 | expect(vermerk.title).toContain('Sättigungsverkehrsstärke 1.695 Fz/h'); |
| 286 | } finally { |
| 287 | aufraeumen(); |
| 288 | } |
| 289 | }); |
| 290 | |
| 291 | it('springt mit der erfassten Aufteilung auf Gleichung (2-5)', () => { |
| 292 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung(1)); |
| 293 | try { |
| 294 | const vermerk = faktorVermerk(wurzel, 'K1')!; |
| 295 | // Der Nachweis, dass die Eingabe wirkt: 1,180 -> 1,300, 1695 -> 1538. |
| 296 | expect(vermerk.textContent).toBe('fSV 1,300'); |
| 297 | expect(vermerk.title).toContain('Gleichung (2-5)'); |
| 298 | expect(vermerk.title).toContain('20 % Schwerverkehr, davon 100 % Lastzüge'); |
| 299 | expect(vermerk.title).toContain('Sättigungsverkehrsstärke 1.538 Fz/h'); |
| 300 | expect(vermerk.title).not.toContain('Keine Aufteilung'); |
| 301 | |
| 302 | // Und die Nachbarzeile ohne Eintrag bleibt, wo sie war. |
| 303 | expect(faktorVermerk(wurzel, 'K2')!.textContent).toBe('fSV 1,045'); |
| 304 | } finally { |
| 305 | aufraeumen(); |
| 306 | } |
| 307 | }); |
| 308 | |
| 309 | it('zeigt bei genau der Annahme dieselbe Zahl wie ohne Eintrag', () => { |
| 310 | // Der Konsistenzbeleg am Bildschirm: 1,75 · 0,80 + 2,50 · 0,20 = 1,90. |
| 311 | const ohne = zeichne(signalGroupsView, mitAufteilung()); |
| 312 | const mit = zeichne( |
| 313 | signalGroupsView, |
| 314 | mitAufteilung(HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG), |
| 315 | ); |
| 316 | try { |
| 317 | expect(faktorVermerk(mit.wurzel, 'K1')!.textContent).toBe( |
| 318 | faktorVermerk(ohne.wurzel, 'K1')!.textContent, |
| 319 | ); |
| 320 | // Aber die Herkunft der Zahl ist eine andere - erfasst statt angenommen. |
| 321 | expect(faktorVermerk(mit.wurzel, 'K1')!.title).toContain('Gleichung (2-5)'); |
| 322 | expect(faktorVermerk(ohne.wurzel, 'K1')!.title).toContain('Gleichung (2-6)'); |
| 323 | } finally { |
| 324 | mit.aufraeumen(); |
| 325 | ohne.aufraeumen(); |
| 326 | } |
| 327 | }); |
| 328 | |
| 329 | it('macht einen unbrauchbaren Eintrag sichtbar, statt ihn still zu verwerfen', () => { |
| 330 | // Ueber die Datei ist der Fall nicht erreichbar (das Schema verwirft ihn |
| 331 | // beim Lesen), ueber Fremdwerkzeuge schon. Verworfen wird er auch hier - |
| 332 | // aber nicht stillschweigend. |
| 333 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung(1.5)); |
| 334 | try { |
| 335 | const vermerk = faktorVermerk(wurzel, 'K1')!; |
| 336 | expect(vermerk.textContent).toBe('fSV 1,180'); |
| 337 | expect(vermerk.title).toContain('außerhalb von 0 bis 100 %'); |
| 338 | expect(vermerk.title).toContain('gilt als nicht erfasst'); |
| 339 | expect(vermerk.title).toContain('Gleichung (2-6)'); |
| 340 | expect(vermerk.title).toContain('meldet das als Warnung'); |
| 341 | // Der gespeicherte Wert bleibt im Feld stehen - er ist gespeichert, nur |
| 342 | // ohne Wirkung; ein leeres Feld verschwiege die Fehleingabe. |
| 343 | expect(lastzugFeld(wurzel, 'K1')!.value).toBe('150'); |
| 344 | } finally { |
| 345 | aufraeumen(); |
| 346 | } |
| 347 | }); |
| 348 | |
| 349 | it('rechnet denselben Faktor wie der Signalzeitenplan', () => { |
| 350 | // Keine zweite Fassung der Fallunterscheidung in der Ansicht: qS des Plans |
| 351 | // geteilt durch den angezeigten Faktor muss die Grundsaettigung ergeben. |
| 352 | for (const anteil of [undefined, 0, 0.2, 0.35, 1]) { |
| 353 | const projekt = mitAufteilung(anteil); |
| 354 | const plan = buildSignalPlan(projekt); |
| 355 | const k1 = projekt.signalGroups.find((g) => g.name === 'K1')!; |
| 356 | const geplant = plan.groups.find((g) => g.groupId === k1.id)!; |
| 357 | const faktor = wirksamerSchwerverkehrsfaktor(nachfrage(projekt, 'K1')); |
| 358 | expect(geplant.capacity, `Kein Kapazitätsergebnis bei ${String(anteil)}`).not.toBeNull(); |
| 359 | expect(geplant.capacity!.saturationFlow * faktor, `Aufteilung ${String(anteil)}`).toBeCloseTo( |
| 360 | 2000, |
| 361 | 6, |
| 362 | ); |
| 363 | } |
| 364 | }); |
| 365 | |
| 366 | it('steht ohne Verkehrsstärke gar nicht erst da', () => { |
| 367 | // Ein Faktor zu einem Strom, ueber den nichts erfasst ist, waere eine Zahl |
| 368 | // ohne Grundlage - das Feld bleibt, der Vermerk nicht. |
| 369 | const ohne: Project = { ...mitAufteilung(), demands: [] }; |
| 370 | expect(schwerverkehrsfaktorVermerk(undefined, null)).toBeNull(); |
| 371 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, ohne); |
| 372 | try { |
| 373 | expect(lastzugFeld(wurzel, 'K1'), 'Das Feld selbst bleibt').not.toBeNull(); |
| 374 | expect(faktorVermerk(wurzel, 'K1')).toBeNull(); |
| 375 | } finally { |
| 376 | aufraeumen(); |
| 377 | } |
| 378 | }); |
| 379 | |
| 380 | it('spricht ohne Schwerverkehr nicht von einer fehlenden Aufteilung', () => { |
| 381 | // Bei 0 % Schwerverkehr liefern beide Gleichungen fSV = 1. Ein Hinweis auf |
| 382 | // eine "nicht erfasste Aufteilung" spraeche dort von einer Annahme, die auf |
| 383 | // keine Zahl wirkt - und wird nach dem dritten Mal ueberlesen. |
| 384 | const ohneSV: TrafficDemand = { signalGroupId: 'x', volume: 500, heavyVehicleShare: 0 }; |
| 385 | const vermerk = schwerverkehrsfaktorVermerk(ohneSV, 2000)!; |
| 386 | expect(vermerk.text).toBe('fSV 1,000'); |
| 387 | expect(vermerk.title).toContain('Ohne Schwerverkehr wirkt die Aufteilung nicht'); |
| 388 | expect(vermerk.title).not.toContain('Keine Aufteilung des Schwerverkehrs erfasst'); |
| 389 | }); |
| 390 | |
| 391 | it('nennt die Sättigungsverkehrsstärke nur, wenn der Plan eine hergibt', () => { |
| 392 | const demand: TrafficDemand = { signalGroupId: 'x', volume: 500, heavyVehicleShare: 0.2 }; |
| 393 | expect(schwerverkehrsfaktorVermerk(demand, null)!.title).not.toContain( |
| 394 | 'Sättigungsverkehrsstärke', |
| 395 | ); |
| 396 | expect(schwerverkehrsfaktorVermerk(demand, 1695)!.title).toContain( |
| 397 | 'Sättigungsverkehrsstärke 1.695 Fz/h', |
| 398 | ); |
| 399 | }); |
| 400 | }); |
| 401 | |
| 402 | describe('Eingabe des Lastzuganteils', () => { |
| 403 | const setze = (feld: HTMLInputElement, wert: string): void => { |
| 404 | feld.value = wert; |
| 405 | feld.dispatchEvent(new Event('change')); |
| 406 | }; |
| 407 | |
| 408 | it('speichert den Anteil AM SCHWERVERKEHR, nicht an der Verkehrsstärke', () => { |
| 409 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 410 | try { |
| 411 | setze(lastzugFeld(wurzel, 'K1')!, '25'); |
| 412 | const demand = nachfrage(store.getProject(), 'K1'); |
| 413 | // 25 % der Schwerfahrzeuge, nicht 25 % der Verkehrsstaerke: Der |
| 414 | // Schwerverkehrsanteil bleibt unveraendert bei 20 %. |
| 415 | expect(demand.lastzugAnteil).toBeCloseTo(0.25, 9); |
| 416 | expect(demand.heavyVehicleShare).toBeCloseTo(0.2, 9); |
| 417 | expect(demand.volume).toBe(620); |
| 418 | } finally { |
| 419 | aufraeumen(); |
| 420 | } |
| 421 | }); |
| 422 | |
| 423 | it('entfernt die Angabe beim Leeren, statt 0 % zu speichern', () => { |
| 424 | // "0 % Lastzuege" und "nicht erfasst" sind der Unterschied zwischen |
| 425 | // Gl. 2-5 und Gl. 2-6 - ein Feld, das beides gleich behandelt, waere |
| 426 | // wertlos. |
| 427 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, mitAufteilung(0.4)); |
| 428 | try { |
| 429 | const feld = lastzugFeld(wurzel, 'K1')!; |
| 430 | expect(feld.value).toBe('40'); |
| 431 | setze(feld, ''); |
| 432 | const demand = nachfrage(store.getProject(), 'K1'); |
| 433 | expect('lastzugAnteil' in demand, 'Das Feld muss verschwinden, nicht auf 0 fallen').toBe( |
| 434 | false, |
| 435 | ); |
| 436 | } finally { |
| 437 | aufraeumen(); |
| 438 | } |
| 439 | }); |
| 440 | |
| 441 | it('zeigt 12,5 % als 12,5 und nicht als 13', () => { |
| 442 | /* |
| 443 | * Der Befund: Das Feld rundete auf ganze Prozent, waehrend Ausdruck und CSV |
| 444 | * eine Nachkommastelle ausweisen und gerechnet wird mit dem gespeicherten |
| 445 | * Wert - drei Zahlen zu einer Angabe. Der Schaden steht eine Zeile weiter: |
| 446 | * Wer das Feld anfasst, schreibt die gerundete Zahl zurueck. |
| 447 | */ |
| 448 | const basis = mitAufteilung(0.125); |
| 449 | const projekt: Project = { |
| 450 | ...basis, |
| 451 | // Derselbe Fehler stand am Feld daneben; beide Zahlen kommen aus |
| 452 | // derselben Umrechnung. |
| 453 | demands: basis.demands.map((d) => |
| 454 | d.lastzugAnteil === 0.125 ? { ...d, heavyVehicleShare: 0.125 } : d, |
| 455 | ), |
| 456 | }; |
| 457 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, projekt); |
| 458 | try { |
| 459 | expect(lastzugFeld(wurzel, 'K1')!.value).toBe('12.5'); |
| 460 | expect(schwerverkehrsfeld(wurzel, 'K1').value).toBe('12.5'); |
| 461 | // Nichts geaendert heisst nichts gespeichert: Der Wert bleibt, wie er ist. |
| 462 | expect(nachfrage(store.getProject(), 'K1').lastzugAnteil).toBe(0.125); |
| 463 | } finally { |
| 464 | aufraeumen(); |
| 465 | } |
| 466 | }); |
| 467 | |
| 468 | it('unterscheidet 0 % von "nicht erfasst"', () => { |
| 469 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 470 | try { |
| 471 | setze(lastzugFeld(wurzel, 'K1')!, '0'); |
| 472 | const demand = nachfrage(store.getProject(), 'K1'); |
| 473 | expect(demand.lastzugAnteil).toBe(0); |
| 474 | // Ein Schwerverkehr ohne Lastzuege ist die guenstigere Rechnung als der |
| 475 | // Pauschalwert - deshalb darf sie nur aus einer Angabe entstehen. |
| 476 | expect(wirksamerSchwerverkehrsfaktor(demand)).toBeCloseTo(1.15, 9); |
| 477 | } finally { |
| 478 | aufraeumen(); |
| 479 | } |
| 480 | }); |
| 481 | }); |
| 482 | |
| 483 | /** |
| 484 | * Das Feld klemmt einen Wert ausserhalb von 0 bis 100 % NICHT. |
| 485 | * |
| 486 | * DER SCHWERSTE BEFUND ZUR AUFTEILUNG DES SCHWERVERKEHRS (Fassung 5.6.0). Die |
| 487 | * Ansicht gab `min: 0, max: 100` an `numberCell`, und das begrenzt: Aus `-30` |
| 488 | * wurde still `0`. Damit galt der Strom als "Aufteilung erfasst, kein einziger |
| 489 | * Lastzug", die Saettigungsverkehrsstaerke stieg von 1695 auf 1739 Kfz/h (2,6 % |
| 490 | * in die unsichere Richtung), die Plannotiz `lastzuganteil-ausserhalb` entstand |
| 491 | * nie, der Pruefbericht schwieg, und Ausdruck wie CSV druckten "Lastzuege 0,0 % |
| 492 | * des Schwerverkehrs" - eine Behauptung ueber eine Zaehlung, die niemand |
| 493 | * vorgenommen hat. Das einzige Signal war eine Kurzmeldung, die nach fuenf |
| 494 | * Sekunden verschwindet; der Eintrag war dauerhaft. |
| 495 | * |
| 496 | * Genau diesen Rueckfall schliessen `istLastzuganteil`, |
| 497 | * `actions.setLastzugAnteil`, das Schema und die Bedienungsanleitung |
| 498 | * ausdruecklich aus - er war nur eine Ebene hoeher wieder eingebaut. |
| 499 | */ |
| 500 | describe('Ein Wert außerhalb von 0 bis 100 % bleibt stehen', () => { |
| 501 | const setze = (feld: HTMLInputElement, wert: string): void => { |
| 502 | feld.value = wert; |
| 503 | feld.dispatchEvent(new Event('change')); |
| 504 | }; |
| 505 | |
| 506 | it('macht aus -30 % keine gezählte 0 %', () => { |
| 507 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 508 | try { |
| 509 | const feld = lastzugFeld(wurzel, 'K1')!; |
| 510 | setze(feld, '-30'); |
| 511 | |
| 512 | const demand = nachfrage(store.getProject(), 'K1'); |
| 513 | expect(demand.lastzugAnteil, 'Der Eintrag darf nicht auf 0 gezogen werden').toBeCloseTo( |
| 514 | -0.3, |
| 515 | 9, |
| 516 | ); |
| 517 | expect(feld.value, 'Im Feld steht, was eingegeben wurde').toBe('-30'); |
| 518 | expect(feld.getAttribute('aria-invalid'), 'Das Feld ist gekennzeichnet').toBe('true'); |
| 519 | |
| 520 | // Verworfen und nicht gerechnet: Gl. 2-6 wie ohne jede Angabe - und |
| 521 | // NICHT 1,150, was ein Rueckfall auf 0 % ergaebe. |
| 522 | expect(wirksamerSchwerverkehrsfaktor(demand)).toBeCloseTo(1.18, 9); |
| 523 | |
| 524 | const plan = buildSignalPlan(store.getProject()); |
| 525 | const notiz = plan.notes.find((n) => n.code === 'lastzuganteil-ausserhalb'); |
| 526 | expect(notiz?.severity, 'Der Plan meldet den unbrauchbaren Eintrag').toBe('warnung'); |
| 527 | expect(notiz?.message).toContain('-30 %'); |
| 528 | |
| 529 | /* |
| 530 | * Und beim naechsten Zeichnen steht es immer noch da. Das ist der |
| 531 | * eigentliche Unterschied zur Kurzmeldung: Die Ansicht baut sich nach |
| 532 | * jeder Eingabe neu auf, die Meldung verschwindet nach fuenf Sekunden - |
| 533 | * der Eintrag bleibt. Kennzeichnung und Vermerk muessen den Neuaufbau |
| 534 | * ueberleben, sonst ist der unbrauchbare Wert ab der naechsten Sitzung |
| 535 | * nur noch im Pruefbericht zu finden. |
| 536 | */ |
| 537 | const neu = zeichne(signalGroupsView, store.getProject()); |
| 538 | try { |
| 539 | expect(lastzugFeld(neu.wurzel, 'K1')!.value).toBe('-30'); |
| 540 | expect(lastzugFeld(neu.wurzel, 'K1')!.getAttribute('aria-invalid')).toBe('true'); |
| 541 | expect(faktorVermerk(neu.wurzel, 'K1')!.title).toContain('gilt als nicht erfasst'); |
| 542 | } finally { |
| 543 | neu.aufraeumen(); |
| 544 | } |
| 545 | } finally { |
| 546 | aufraeumen(); |
| 547 | } |
| 548 | }); |
| 549 | |
| 550 | it('macht aus 150 % keine gezählten 100 %', () => { |
| 551 | // Die andere Richtung ist die konservative - und derselbe Fehler: Aus |
| 552 | // einer Fehleingabe wuerde die Aussage "alles Lastzuege". |
| 553 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 554 | try { |
| 555 | setze(lastzugFeld(wurzel, 'K1')!, '150'); |
| 556 | const demand = nachfrage(store.getProject(), 'K1'); |
| 557 | expect(demand.lastzugAnteil).toBeCloseTo(1.5, 9); |
| 558 | expect(wirksamerSchwerverkehrsfaktor(demand), 'nicht 1,300').toBeCloseTo(1.18, 9); |
| 559 | } finally { |
| 560 | aufraeumen(); |
| 561 | } |
| 562 | }); |
| 563 | |
| 564 | it('sagt vor der Eingabe und nach der Fehleingabe, was daraus folgt', () => { |
| 565 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 566 | try { |
| 567 | const feld = lastzugFeld(wurzel, 'K1')!; |
| 568 | // Vorgelesen wird der Satz beim Fokussieren (dom.ts, |
| 569 | // ergaenzeBereichshinweis) - er muss die Folge nennen und nicht nur die |
| 570 | // Grenze, sonst hoert der Anwender "zulaessig sind 0 bis 100 %" und |
| 571 | // schliesst daraus auf ein Feld, das ihn schuetzt. |
| 572 | expect(bereichssatz(feld)).toContain(lastzuganteilAusserhalbFolge()); |
| 573 | expect(bereichssatz(feld)).toContain('Zulässig sind 0 % bis 100 %.'); |
| 574 | |
| 575 | setze(feld, '-30'); |
| 576 | const meldung = meldungsverlauf()[0]!; |
| 577 | expect(meldung.art).toBe('warnung'); |
| 578 | expect(meldung.text).toContain('außerhalb des zulässigen Bereichs'); |
| 579 | expect(meldung.text).toContain(lastzuganteilAusserhalbFolge()); |
| 580 | // Kein Satz ueber einen ersatzweise eingetragenen Wert - es gibt keinen. |
| 581 | expect(meldung.text).not.toContain('Eingetragen wurde'); |
| 582 | } finally { |
| 583 | aufraeumen(); |
| 584 | } |
| 585 | }); |
| 586 | |
| 587 | it('nennt in der Folge die Gleichung und den Pauschalwert', () => { |
| 588 | const satz = lastzuganteilAusserhalbFolge(); |
| 589 | expect(satz).toContain('nicht begrenzt'); |
| 590 | expect(satz).toContain('verworfen'); |
| 591 | expect(satz).toContain('Gleichung (2-6)'); |
| 592 | expect(satz).toContain(fmt.num(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal, 2)); |
| 593 | }); |
| 594 | |
| 595 | it('begrenzt den Schwerverkehrsanteil daneben unverändert', () => { |
| 596 | /* |
| 597 | * Die Ausnahme gilt genau EINEM Feld. Beim Schwerverkehrsanteil ist |
| 598 | * Begrenzen richtig: Ein Anteil ueber 100 % ist eine unmoegliche Menge, |
| 599 | * aber eine Angabe - "sehr viel Schwerverkehr" auf 100 % zu ziehen bleibt |
| 600 | * in der Sache. Beim Lastzuganteil entscheidet der Bereich dagegen |
| 601 | * darueber, OB eine Aufteilung erfasst ist. |
| 602 | */ |
| 603 | const { wurzel, store, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 604 | try { |
| 605 | const feld = schwerverkehrsfeld(wurzel, 'K1'); |
| 606 | setze(feld, '150'); |
| 607 | expect(feld.value).toBe('100'); |
| 608 | expect(nachfrage(store.getProject(), 'K1').heavyVehicleShare).toBeCloseTo(1, 9); |
| 609 | expect(bereichssatz(feld)).not.toContain('nicht begrenzt'); |
| 610 | } finally { |
| 611 | aufraeumen(); |
| 612 | } |
| 613 | }); |
| 614 | }); |
| 615 | |
| 616 | describe('Satz unter der Signalgruppentabelle', () => { |
| 617 | const satz = schwerverkehrsanteilHinweis(); |
| 618 | |
| 619 | it('nennt beide Gleichungen und rechnet den Pauschalwert nach', () => { |
| 620 | expect(satz).toContain('Gleichung (2-5)'); |
| 621 | expect(satz).toContain('Gleichung (2-6)'); |
| 622 | expect(satz).toContain('1,75 · 0,80 + 2,50 · 0,20 = 1,90'); |
| 623 | expect(satz).toContain('Lastzüge am SV'); |
| 624 | }); |
| 625 | |
| 626 | it('behauptet nicht mehr, das Programm erfasse keine Aufteilung', () => { |
| 627 | expect(satz).not.toContain('erfasst dieses Programm nicht'); |
| 628 | // Der Pauschalwert ist nicht mehr die Eigenschaft des Programms, sondern |
| 629 | // die Folge einer Datenlage - und das muss der Satz sagen. |
| 630 | expect(satz).toContain('Welche Gleichung des HBS 2015 gilt, entscheidet die Spalte'); |
| 631 | }); |
| 632 | |
| 633 | it('bildet die Zahlen aus den Konstanten, nicht als festen Text', () => { |
| 634 | // Befund C21: Ein fester Text veraltet still. Wenn eine Konstante sich |
| 635 | // aendert, muss der Satz mitgehen - hier nachgewiesen ueber die Zahl selbst. |
| 636 | expect(satz).toContain(HBS_PKW_GLEICHWERTE.schwerverkehrPauschal.toFixed(2).replace('.', ',')); |
| 637 | expect(satz).toContain(HBS_PKW_GLEICHWERTE.lkwMitAnhaenger.toFixed(2).replace('.', ',')); |
| 638 | expect(satz).toContain(`${HBS_LASTZUGANTEIL_BEI_UNBEKANNTER_AUFTEILUNG * 100} %`); |
| 639 | }); |
| 640 | |
| 641 | it('steht unter der Tabelle, sobald es eine Gruppe mit Sättigungsverkehrsstärke gibt', () => { |
| 642 | const { wurzel, aufraeumen } = zeichne(signalGroupsView, mitAufteilung()); |
| 643 | try { |
| 644 | expect(wurzel.textContent).toContain(satz); |
| 645 | } finally { |
| 646 | aufraeumen(); |
| 647 | } |
| 648 | }); |
| 649 | }); |
| 650 | |
| 651 | describe('Hilfe zur Aufteilung des Schwerverkehrs', () => { |
| 652 | const eintrag = HILFE.schwerverkehrsaufteilung; |
| 653 | |
| 654 | it('sagt, was zu zählen ist', () => { |
| 655 | expect(eintrag.wasIstDas).toContain('Lkw mit Anhänger und Sattelkraftfahrzeuge'); |
| 656 | // Die Bezugsgroesse ist die haeufigste Verwechslung: bei 20 % |
| 657 | // Schwerverkehr liegt zwischen beiden Lesarten ein Faktor 5. |
| 658 | expect(eintrag.wasIstDas).toContain( |
| 659 | 'bezieht sich auf den SCHWERVERKEHR, nicht auf die Verkehrsstärke', |
| 660 | ); |
| 661 | expect(eintrag.wasIstDas).toContain('jedes vierte Schwerfahrzeug ist ein Lastzug'); |
| 662 | expect(eintrag.woher).toBeDefined(); |
| 663 | expect(eintrag.woher).toContain('nicht die Solo-Lkw und nicht die Busse'); |
| 664 | expect(eintrag.woher).toContain('Fahrzeugklassen'); |
| 665 | }); |
| 666 | |
| 667 | it('nennt die drei Pkw-Gleichwerte und rechnet den Pauschalwert nach', () => { |
| 668 | expect(eintrag.wasIstDas).toContain('1,75'); |
| 669 | expect(eintrag.wasIstDas).toContain('2,50'); |
| 670 | expect(eintrag.wasIstDas).toContain('1,90'); |
| 671 | expect(eintrag.wasIstDas).toContain('1,75 · 0,80 + 2,50 · 0,20 = 1,90'); |
| 672 | expect(eintrag.wasIstDas).toContain('BASt-Bericht V 400'); |
| 673 | }); |
| 674 | |
| 675 | it('sagt, was ohne Angabe passiert - und dass ein leeres Feld keine vorsichtige Wahl ist', () => { |
| 676 | expect(eintrag.woher).toContain('lassen Sie das Feld leer'); |
| 677 | expect(eintrag.woher).toContain('Gleichung (2-6)'); |
| 678 | expect(eintrag.wennFalsch).toBeDefined(); |
| 679 | expect(eintrag.wennFalsch).toContain('1538'); |
| 680 | expect(eintrag.wennFalsch).toContain('1695'); |
| 681 | expect(eintrag.wennFalsch).toContain('10,2 Prozent zu groß'); |
| 682 | expect(eintrag.wennFalsch).toContain('31,6 Prozent'); |
| 683 | expect(eintrag.wennFalsch).toContain('2,5 Prozent zu klein'); |
| 684 | expect(eintrag.wennFalsch).toContain('keine vorsichtige Wahl'); |
| 685 | }); |
| 686 | |
| 687 | it('nennt die Meldeschwelle der Prüfregel und nicht eine eigene Zahl', () => { |
| 688 | expect(eintrag.woher).toContain(`über ${SCHWERVERKEHR_AUFTEILUNG_MELDESCHWELLE * 100} Prozent`); |
| 689 | }); |
| 690 | |
| 691 | it('trennt Regelwerks- von Erfahrungswerten', () => { |
| 692 | expect(eintrag.ueblich).toBeDefined(); |
| 693 | expect(eintrag.ueblich).toContain('einzige belegte Zahl'); |
| 694 | expect(eintrag.ueblich).toContain('Erfahrung und kein Regelwerkswert'); |
| 695 | // Und der Umgang mit unbrauchbaren Werten: verworfen, nicht begrenzt. |
| 696 | expect(eintrag.ueblich).toContain('nicht begrenzt, sondern verworfen'); |
| 697 | }); |
| 698 | |
| 699 | it('steht im Glossar und ist von den Nachbareinträgen aus erreichbar', () => { |
| 700 | expect(glossarEintraege().map((e) => e.schluessel)).toContain('schwerverkehrsaufteilung'); |
| 701 | expect(glossarSuche('Aufteilung').map((e) => e.schluessel)).toContain( |
| 702 | 'schwerverkehrsaufteilung', |
| 703 | ); |
| 704 | for (const schluessel of ['schwerverkehrsanteil', 'saettigungsverkehrsstaerke'] as const) { |
| 705 | expect(HILFE[schluessel].siehe ?? [], schluessel).toContain('schwerverkehrsaufteilung'); |
| 706 | } |
| 707 | }); |
| 708 | |
| 709 | it('nimmt dem Eintrag zum Schwerverkehrsanteil die überholte Behauptung', () => { |
| 710 | const anteil = HILFE.schwerverkehrsanteil; |
| 711 | expect(anteil.wasIstDas).not.toContain('keine Aufteilung nach Fahrzeugklassen'); |
| 712 | expect(anteil.wasIstDas).not.toContain('rechnet deshalb immer mit dem Pauschalwert'); |
| 713 | expect(anteil.wasIstDas).toContain('Bleibt die Spalte „Lastzüge am SV“ daneben leer'); |
| 714 | expect(anteil.wennFalsch).toContain('Tragen Sie in diesem Fall den Lastzuganteil ein'); |
| 715 | }); |
| 716 | }); |
| 717 | |
| 718 | describe('Assistent erfindet keine Aufteilung', () => { |
| 719 | it('legt die Verkehrsstärken ohne Lastzuganteil an', () => { |
| 720 | const projekt = projektAusAngaben( |
| 721 | { ...ASSISTENT_VORGABE, verkehrsstaerke: 400, schwerverkehrsanteil: 9 }, |
| 722 | 'Assistent', |
| 723 | FESTES_DATUM, |
| 724 | ); |
| 725 | expect(projekt.demands.length).toBeGreaterThan(0); |
| 726 | for (const demand of projekt.demands) { |
| 727 | // Nicht 0 und nicht 20 %: Der Assistent hat nicht gezaehlt. |
| 728 | expect('lastzugAnteil' in demand, 'Der Assistent behauptet eine Erhebung').toBe(false); |
| 729 | } |
| 730 | }); |
| 731 | |
| 732 | it('nennt die Annahme trotzdem in der Zusammenfassung', () => { |
| 733 | const saetze = assistentZusammenfassung({ ...ASSISTENT_VORGABE, verkehrsstaerke: 400 }).join( |
| 734 | ' ', |
| 735 | ); |
| 736 | expect(saetze).toContain('Keine Aufteilung des Schwerverkehrs'); |
| 737 | expect(saetze).toContain('Gleichung (2-6)'); |
| 738 | expect(saetze).toContain('Pauschalwert 1,90'); |
| 739 | expect(saetze).toContain('20 % Lastzügen am Schwerverkehr'); |
| 740 | |
| 741 | // Ohne Verkehrsstaerke gibt es keinen Schwerverkehr und nichts anzunehmen. |
| 742 | const ohne = assistentZusammenfassung({ ...ASSISTENT_VORGABE, verkehrsstaerke: 0 }).join(' '); |
| 743 | expect(ohne).not.toContain('Aufteilung des Schwerverkehrs'); |
| 744 | }); |
| 745 | |
| 746 | it('erwähnt sie auch im Hinweis am Schwerverkehrsfeld des Dialogs', async () => { |
| 747 | const ergebnis = starteAssistent(); |
| 748 | const dialog = (): HTMLElement => document.querySelector<HTMLElement>('.dialog')!; |
| 749 | const knopf = (beschriftung: string): HTMLButtonElement => |
| 750 | [...dialog().querySelectorAll<HTMLButtonElement>('button')].find( |
| 751 | (b) => b.textContent?.trim() === beschriftung, |
| 752 | )!; |
| 753 | knopf('Weiter').click(); |
| 754 | knopf('Weiter').click(); |
| 755 | |
| 756 | const label = [...dialog().querySelectorAll('label')].find( |
| 757 | (l) => l.textContent?.trim() === 'Schwerverkehrsanteil', |
| 758 | )!; |
| 759 | const feld = dialog().querySelector<HTMLInputElement>(`#${label.htmlFor}`)!; |
| 760 | const hinweis = dialog().querySelector<HTMLElement>( |
| 761 | `#${feld.getAttribute('aria-describedby')}`, |
| 762 | )!; |
| 763 | expect(hinweis.textContent).toContain( |
| 764 | 'Eine Aufteilung des Schwerverkehrs erfasst der Assistent nicht', |
| 765 | ); |
| 766 | expect(hinweis.textContent).toContain('Gleichung (2-6)'); |
| 767 | expect(hinweis.textContent).toContain('20 %'); |
| 768 | expect(hinweis.textContent).toContain('Lastzüge am SV'); |
| 769 | expect(hinweis.textContent).toContain('Gleichung (2-5)'); |
| 770 | |
| 771 | knopf('Abbrechen').click(); |
| 772 | await ergebnis; |
| 773 | }); |
| 774 | }); |