lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui anordnungsangaben.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 { validateProject } from '@/domain/validation'; |
| 5 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 6 | import { RILSA_DEFAULTS } from '@/domain/rilsa/constants'; |
| 7 | import { RECHTSGRUNDLAGEN, RECHTSGRUNDLAGEN_EINSTREIFIG } from '@/domain/rilsa/quellen'; |
| 8 | import { DEFAULT_PDF_OPTIONS } from '@/services/export/pdf'; |
| 9 | import type { Anlagenart, Project } from '@/domain/model/project'; |
| 10 | import type { AppState } from '@/app/store'; |
| 11 | import type { ViewDefinition } from '@/ui/shell'; |
| 12 | import { ASSISTENT_VORGABE, projektAusAngaben } from '@/ui/assistent'; |
| 13 | import { naechsterSchritt } from '@/ui/naechsterSchritt'; |
| 14 | import { HILFE, glossarEintraege, glossarSuche } from '@/ui/help/texte'; |
| 15 | import { projectView } from '@/ui/views/projectView'; |
| 16 | import { settingsView } from '@/ui/views/settingsView'; |
| 17 | import { |
| 18 | RECHENWEG_ABGEWAEHLT_HINWEIS, |
| 19 | RECHENWEG_SCHALTER, |
| 20 | exportView, |
| 21 | } from '@/ui/views/exportView'; |
| 22 | |
| 23 | /** |
| 24 | * Unterlagen und Rechtsgrundlagen in der Oberflaeche (Befunde C13, C14, C15, |
| 25 | * C17, C19). |
| 26 | * |
| 27 | * C14 - Einsatzzeiten und Betriebsverantwortlicher: Beide Pflichtinhalte einer |
| 28 | * verkehrsbehoerdlichen Anordnung hatten im Programm kein Feld. Der gefaehrliche |
| 29 | * Teil war nicht die fehlende Zeile, sondern der fehlende Lueckenausweis: Eine |
| 30 | * Unterlage, die alle Felder ausgefuellt zeigt, die sie kennt, sieht |
| 31 | * vollstaendig aus. |
| 32 | * C15 - Der Rechenweg der Zwischenzeiten und der Wartezeit war voreingestellt |
| 33 | * abgewaehlt und hiess nur "Rechenweg der Zwischenzeiten". |
| 34 | * C13 - RSA 21, TL und ZTV transportable LSA 2023 fehlten in der rechtlichen |
| 35 | * Einordnung; sie erscheinen jetzt bei einstreifiger Fuehrung. |
| 36 | * C17 - Die VwV-StVO gibt Gelb- und Rot-Gelb-Zeit selbst vor; die Hilfe sagte |
| 37 | * nur "aus dem Regelwerk abgeleitet". |
| 38 | * C19 - Die OePNV-Mindestfreigabezeit stand als gesicherter RiLSA-Wert. |
| 39 | * |
| 40 | * Gegen den Altstand uebersetzt diese Datei nicht einmal: HILFE.rechtsgrundlagen, |
| 41 | * HILFE.einsatzzeiten, HILFE.betriebsverantwortlicher, RECHENWEG_SCHALTER, |
| 42 | * RECHENWEG_ABGEWAEHLT_HINWEIS, RECHTSGRUNDLAGEN_EINSTREIFIG und |
| 43 | * meta.einsatzzeiten gab es nicht. |
| 44 | */ |
| 45 | |
| 46 | const FESTES_DATUM = new Date('2026-01-01T00:00:00Z'); |
| 47 | |
| 48 | function zeichne( |
| 49 | view: ViewDefinition, |
| 50 | project: Project, |
| 51 | ): { wurzel: HTMLElement; store: ProjectStore; aufraeumen: () => void } { |
| 52 | const wurzel = document.createElement('div'); |
| 53 | document.body.append(wurzel); |
| 54 | const store = new ProjectStore(project); |
| 55 | const abmelden = view.render(wurzel, { |
| 56 | store, |
| 57 | refresh: () => {}, |
| 58 | navigate: () => {}, |
| 59 | target: null, |
| 60 | }); |
| 61 | return { |
| 62 | wurzel, |
| 63 | store, |
| 64 | aufraeumen: () => { |
| 65 | abmelden(); |
| 66 | wurzel.remove(); |
| 67 | }, |
| 68 | }; |
| 69 | } |
| 70 | |
| 71 | /** Eingabefeld zu einer sichtbaren Beschriftung - ueber `for`, nicht ueber die Lage. */ |
| 72 | function feldZu(wurzel: ParentNode, beschriftung: string): HTMLInputElement { |
| 73 | const label = [...wurzel.querySelectorAll('label')].find( |
| 74 | (l) => (l.textContent ?? '').trim() === beschriftung, |
| 75 | ); |
| 76 | if (label === undefined) throw new Error(`Keine Beschriftung "${beschriftung}"`); |
| 77 | const feld = wurzel.querySelector<HTMLInputElement>(`#${label.htmlFor}`); |
| 78 | if (feld === null) throw new Error(`Beschriftung "${beschriftung}" ohne Feld`); |
| 79 | return feld; |
| 80 | } |
| 81 | |
| 82 | /** Kontrollkaestchen zu einer sichtbaren Beschriftung. */ |
| 83 | function schalterZu(wurzel: ParentNode, beschriftung: string): HTMLInputElement { |
| 84 | return feldZu(wurzel, beschriftung); |
| 85 | } |
| 86 | |
| 87 | function projektMit(anlagenart: Anlagenart, patch: Partial<Project['meta']> = {}): Project { |
| 88 | const basis = createStandardIntersectionProject('Anordnung', FESTES_DATUM); |
| 89 | return { |
| 90 | ...basis, |
| 91 | anlagenart, |
| 92 | meta: { ...basis.meta, ...patch }, |
| 93 | }; |
| 94 | } |
| 95 | |
| 96 | function zustand(project: Project): AppState { |
| 97 | const plan = buildSignalPlan(project); |
| 98 | return { |
| 99 | project, |
| 100 | plan, |
| 101 | report: validateProject(project, plan, FESTES_DATUM), |
| 102 | dirty: false, |
| 103 | filePath: null, |
| 104 | canUndo: false, |
| 105 | canRedo: false, |
| 106 | undoLabel: null, |
| 107 | redoLabel: null, |
| 108 | }; |
| 109 | } |
| 110 | |
| 111 | /** Eine bemessene Engstelle ohne Fehler - damit der Schritt ueberhaupt so weit kommt. */ |
| 112 | function engstelle(patch: Partial<Project['meta']> = {}): Project { |
| 113 | const basis = projektAusAngaben( |
| 114 | { ...ASSISTENT_VORGABE, form: 'einstreifig', verkehrsstaerke: 300 }, |
| 115 | 'Engstelle', |
| 116 | FESTES_DATUM, |
| 117 | ); |
| 118 | return { |
| 119 | ...basis, |
| 120 | meta: { |
| 121 | ...basis.meta, |
| 122 | projectNumber: 'VP-2026-018', |
| 123 | client: 'Stadt Musterhausen', |
| 124 | planner: 'Willerding', |
| 125 | location: 'Hauptstraße', |
| 126 | ...patch, |
| 127 | }, |
| 128 | }; |
| 129 | } |
| 130 | |
| 131 | afterEach(() => { |
| 132 | document.body.replaceChildren(); |
| 133 | }); |
| 134 | |
| 135 | describe('Projektdaten: Einsatzzeiten und Betriebsverantwortlicher (Befund C14)', () => { |
| 136 | it('zeigt beide Felder mit ihrem Wert und schreibt Änderungen mit eigener Undo-Beschriftung', () => { |
| 137 | const { wurzel, store, aufraeumen } = zeichne( |
| 138 | projectView, |
| 139 | projektMit('knotenpunkt', { einsatzzeiten: '12.05.–30.06.2027, ganztägig' }), |
| 140 | ); |
| 141 | try { |
| 142 | const zeiten = feldZu(wurzel, 'Einsatzzeiten / Geltungszeitraum'); |
| 143 | expect(zeiten.value).toBe('12.05.–30.06.2027, ganztägig'); |
| 144 | |
| 145 | const verantwortlich = feldZu(wurzel, 'Betriebsverantwortlicher'); |
| 146 | expect(verantwortlich.value).toBe(''); |
| 147 | |
| 148 | verantwortlich.value = 'Mustermann Bau GmbH, 0170 1234567'; |
| 149 | verantwortlich.dispatchEvent(new Event('change')); |
| 150 | expect(store.getProject().meta.betriebsverantwortlicher).toBe( |
| 151 | 'Mustermann Bau GmbH, 0170 1234567', |
| 152 | ); |
| 153 | // Die Undo-Beschriftung nennt die Angabe, nicht "Projektdaten geändert": |
| 154 | // Wer zurueckgeht, muss wissen, was er zuruecknimmt. |
| 155 | expect(store.getState().undoLabel).toBe('Betriebsverantwortlicher geändert'); |
| 156 | |
| 157 | zeiten.value = 'werktags 6–20 Uhr'; |
| 158 | zeiten.dispatchEvent(new Event('change')); |
| 159 | expect(store.getProject().meta.einsatzzeiten).toBe('werktags 6–20 Uhr'); |
| 160 | expect(store.getState().undoLabel).toBe('Einsatzzeiten geändert'); |
| 161 | } finally { |
| 162 | aufraeumen(); |
| 163 | } |
| 164 | }); |
| 165 | |
| 166 | it('nennt bei einstreifiger Führung die RSA-Fundstelle und sonst § 45 StVO', () => { |
| 167 | const einstreifig = zeichne(projectView, projektMit('einstreifig')); |
| 168 | try { |
| 169 | const text = einstreifig.wurzel.textContent ?? ''; |
| 170 | expect(text).toContain('Angaben der verkehrsbehördlichen Anordnung'); |
| 171 | expect(text).toContain('RSA 21, Teil A 1.4'); |
| 172 | expect(text).toContain('warnt der Prüfbericht'); |
| 173 | } finally { |
| 174 | einstreifig.aufraeumen(); |
| 175 | } |
| 176 | |
| 177 | const knoten = zeichne(projectView, projektMit('knotenpunkt')); |
| 178 | try { |
| 179 | const text = knoten.wurzel.textContent ?? ''; |
| 180 | // Am Knotenpunkt keine RSA: Sie zu nennen behauptete einen Bezug, den es |
| 181 | // nicht gibt - und genau das ist der Grundsatz hinter Befund C13. |
| 182 | expect(text).not.toContain('RSA 21'); |
| 183 | expect(text).toContain('§ 45 StVO'); |
| 184 | expect(text).toContain('kein bezifferter Regelwerkswert'); |
| 185 | } finally { |
| 186 | knoten.aufraeumen(); |
| 187 | } |
| 188 | }); |
| 189 | |
| 190 | it('stellt beiden Feldern einen Hilfeknopf zur Seite, ohne ihn in den Namen zu ziehen', () => { |
| 191 | const { wurzel, aufraeumen } = zeichne(projectView, projektMit('einstreifig')); |
| 192 | try { |
| 193 | for (const beschriftung of ['Einsatzzeiten / Geltungszeitraum', 'Betriebsverantwortlicher']) { |
| 194 | const label = [...wurzel.querySelectorAll('label')].find( |
| 195 | (l) => (l.textContent ?? '').trim() === beschriftung, |
| 196 | ); |
| 197 | expect(label, beschriftung).toBeDefined(); |
| 198 | const kopf = label!.parentElement; |
| 199 | expect(kopf?.classList.contains('feld-kopf'), beschriftung).toBe(true); |
| 200 | // Der Knopf steht NEBEN der Beschriftung: Im `label` waere er Teil des |
| 201 | // Feldnamens ("Betriebsverantwortlicher Hilfe zu ..."). |
| 202 | expect(label!.querySelector('.hilfe-knopf'), beschriftung).toBeNull(); |
| 203 | expect(kopf!.querySelector('button.hilfe-knopf'), beschriftung).not.toBeNull(); |
| 204 | } |
| 205 | } finally { |
| 206 | aufraeumen(); |
| 207 | } |
| 208 | }); |
| 209 | }); |
| 210 | |
| 211 | describe('Nächster Arbeitsschritt nennt die Anordnungsangaben (Befund C14)', () => { |
| 212 | it('verlangt bei einstreifiger Führung beide Angaben und führt zu den Projektdaten', () => { |
| 213 | const state = zustand(engstelle()); |
| 214 | expect(state.report.errorCount, 'Vorbedingung: kein Fehler mehr offen').toBe(0); |
| 215 | |
| 216 | const schritt = naechsterSchritt(state); |
| 217 | expect(schritt.ansicht).toBe('projekt'); |
| 218 | expect(schritt.text).toContain('die Einsatzzeiten'); |
| 219 | expect(schritt.text).toContain('den Betriebsverantwortlichen'); |
| 220 | expect(schritt.grund).toContain('RSA 21, Teil A 1.4'); |
| 221 | expect(schritt.ziel).toBe('Zu den Projektdaten'); |
| 222 | }); |
| 223 | |
| 224 | it('nennt nur noch die fehlende der beiden Angaben', () => { |
| 225 | const schritt = naechsterSchritt(zustand(engstelle({ einsatzzeiten: 'ganztägig' }))); |
| 226 | expect(schritt.ansicht).toBe('projekt'); |
| 227 | expect(schritt.text).not.toContain('die Einsatzzeiten'); |
| 228 | expect(schritt.text).toContain('den Betriebsverantwortlichen'); |
| 229 | }); |
| 230 | |
| 231 | it('schweigt, sobald beide Angaben stehen', () => { |
| 232 | const schritt = naechsterSchritt( |
| 233 | zustand(engstelle({ einsatzzeiten: 'ganztägig', betriebsverantwortlicher: 'Mustermann' })), |
| 234 | ); |
| 235 | expect(schritt.text).not.toContain('Einsatzzeiten'); |
| 236 | expect(schritt.text).not.toContain('Betriebsverantwortlichen'); |
| 237 | }); |
| 238 | |
| 239 | it('verlangt sie am Knotenpunkt nicht, auch wenn sie fehlen', () => { |
| 240 | // Dort meldet der Pruefbericht sie als Hinweis - ein Arbeitsschritt, der an |
| 241 | // jeder Anlage nach zwei Textfeldern verlangt, waere der Schritt, den man |
| 242 | // ueberliest. |
| 243 | const knoten = zustand({ ...engstelle(), anlagenart: 'knotenpunkt' }); |
| 244 | expect(knoten.project.meta.einsatzzeiten ?? '').toBe(''); |
| 245 | expect(naechsterSchritt(knoten).ansicht).not.toBe('projekt'); |
| 246 | }); |
| 247 | }); |
| 248 | |
| 249 | describe('Ausgabe: Rechenweg der Zwischenzeiten und der Wartezeit (Befund C15)', () => { |
| 250 | it('beschriftet den Schalter mit beiden Rechenwegen und übernimmt die Vorgabe des Ausdrucks', () => { |
| 251 | const { wurzel, aufraeumen } = zeichne(exportView, projektMit('knotenpunkt')); |
| 252 | try { |
| 253 | const schalter = schalterZu(wurzel, RECHENWEG_SCHALTER); |
| 254 | expect(schalter.type).toBe('checkbox'); |
| 255 | // Die Vorgabe selbst steht in pdf.ts (DEFAULT_PDF_OPTIONS); die Ansicht |
| 256 | // fuehrt keine zweite Fassung, sondern zeigt genau sie an - und sie steht |
| 257 | // seit Befund C15 auf "an", weil die Behoerdenpraxis den |
| 258 | // Zwischenzeitennachweis verlangt. |
| 259 | expect(DEFAULT_PDF_OPTIONS.includeCalculationDetails).toBe(true); |
| 260 | expect(schalter.checked).toBe(DEFAULT_PDF_OPTIONS.includeCalculationDetails); |
| 261 | const hinweis = document.getElementById(`${schalter.id}-hinweis`)?.textContent ?? ''; |
| 262 | expect(hinweis).toContain('Rechenweg der Wartezeit'); |
| 263 | expect(hinweis).toContain('Zwischenzeitennachweis'); |
| 264 | } finally { |
| 265 | aufraeumen(); |
| 266 | } |
| 267 | }); |
| 268 | |
| 269 | it('blendet beim Abwählen den Vermerk ein und beim Anwählen wieder aus', () => { |
| 270 | const { wurzel, aufraeumen } = zeichne(exportView, projektMit('knotenpunkt')); |
| 271 | try { |
| 272 | const schalter = schalterZu(wurzel, RECHENWEG_SCHALTER); |
| 273 | const vermerk = [...wurzel.querySelectorAll<HTMLElement>('[role="status"]')].find((k) => |
| 274 | (k.textContent ?? '').includes(RECHENWEG_ABGEWAEHLT_HINWEIS), |
| 275 | ); |
| 276 | expect(vermerk, 'Vermerk zum abgewählten Rechenweg fehlt').toBeDefined(); |
| 277 | |
| 278 | const setze = (wert: boolean): void => { |
| 279 | schalter.checked = wert; |
| 280 | schalter.dispatchEvent(new Event('change')); |
| 281 | }; |
| 282 | |
| 283 | setze(false); |
| 284 | expect(vermerk!.hidden, 'abgewählt: Vermerk sichtbar').toBe(false); |
| 285 | expect(vermerk!.textContent).toContain('nicht nachrechenbar'); |
| 286 | |
| 287 | setze(true); |
| 288 | expect(vermerk!.hidden, 'angewählt: Vermerk verborgen').toBe(true); |
| 289 | |
| 290 | setze(false); |
| 291 | expect(vermerk!.hidden, 'erneut abgewählt: Vermerk wieder sichtbar').toBe(false); |
| 292 | } finally { |
| 293 | aufraeumen(); |
| 294 | } |
| 295 | }); |
| 296 | }); |
| 297 | |
| 298 | describe('Rechtliche Einordnung je Anlagenart (Befund C13)', () => { |
| 299 | const nurEinstreifig = RECHTSGRUNDLAGEN_EINSTREIFIG.map((g) => g.bezeichnung); |
| 300 | |
| 301 | it('führt bei einstreifiger Führung RSA 21, TL und ZTV zusätzlich zum Grundstock', () => { |
| 302 | const { wurzel, aufraeumen } = zeichne(settingsView, projektMit('einstreifig')); |
| 303 | try { |
| 304 | const text = wurzel.textContent ?? ''; |
| 305 | for (const bezeichnung of [ |
| 306 | ...RECHTSGRUNDLAGEN.map((g) => g.bezeichnung), |
| 307 | ...nurEinstreifig, |
| 308 | ]) { |
| 309 | expect(text, bezeichnung).toContain(bezeichnung); |
| 310 | } |
| 311 | expect(text).toContain('ARS Nr. 07/2024'); |
| 312 | expect(text).toContain('Regelwerke der Arbeitsstellensicherung'); |
| 313 | } finally { |
| 314 | aufraeumen(); |
| 315 | } |
| 316 | }); |
| 317 | |
| 318 | it('lässt sie am Knotenpunkt weg und sagt, wann sie erscheinen', () => { |
| 319 | const { wurzel, aufraeumen } = zeichne(settingsView, projektMit('knotenpunkt')); |
| 320 | try { |
| 321 | const text = wurzel.textContent ?? ''; |
| 322 | for (const bezeichnung of RECHTSGRUNDLAGEN.map((g) => g.bezeichnung)) { |
| 323 | expect(text, bezeichnung).toContain(bezeichnung); |
| 324 | } |
| 325 | for (const bezeichnung of nurEinstreifig) { |
| 326 | expect(text, bezeichnung).not.toContain(bezeichnung); |
| 327 | } |
| 328 | expect(text).toContain('nur bei der Anlagenart „Einstreifige Verkehrsführung“'); |
| 329 | } finally { |
| 330 | aufraeumen(); |
| 331 | } |
| 332 | }); |
| 333 | |
| 334 | it('stellt dem Abschnitt einen Hilfeknopf zu den Rechtsgrundlagen voran', () => { |
| 335 | const { wurzel, aufraeumen } = zeichne(settingsView, projektMit('knotenpunkt')); |
| 336 | try { |
| 337 | const ueberschrift = [...wurzel.querySelectorAll('h2, h3')].find((h) => |
| 338 | (h.textContent ?? '').includes('Rechtliche Einordnung'), |
| 339 | ); |
| 340 | expect(ueberschrift, 'Abschnitt "Rechtliche Einordnung" fehlt').toBeDefined(); |
| 341 | const kopf = ueberschrift!.closest('.abschnitt-kopf') ?? ueberschrift!.parentElement; |
| 342 | const knopf = kopf?.querySelector<HTMLButtonElement>('button.hilfe-knopf'); |
| 343 | expect(knopf, 'Hilfeknopf am Abschnitt fehlt').not.toBeNull(); |
| 344 | expect(knopf!.getAttribute('aria-label')).toBe(`Hilfe zu ${HILFE.rechtsgrundlagen.titel}`); |
| 345 | } finally { |
| 346 | aufraeumen(); |
| 347 | } |
| 348 | }); |
| 349 | }); |
| 350 | |
| 351 | describe('Hilfetexte zu Rechtsgrundlagen und Kennwerten (Befunde C13, C17, C19)', () => { |
| 352 | it('erklärt die VwV-StVO als eigene Vorgabe, nicht als Verweis auf die FGSV', () => { |
| 353 | const eintrag = HILFE.rechtsgrundlagen; |
| 354 | expect(eintrag.wasIstDas).toContain('Verwaltungsvorschrift'); |
| 355 | expect(eintrag.wasIstDas).toContain('selbst vor (Rn. 17)'); |
| 356 | expect(eintrag.wasIstDas).toContain('3/4/5 s bei 50/60/70 km/h'); |
| 357 | expect(eintrag.wasIstDas).toContain('1 s, höchstens 2 s'); |
| 358 | expect(eintrag.wasIstDas).toContain('Rn. 10'); |
| 359 | // Der Vorbehalt zur Neufassung 2025 gehoert dazu: Die Randnummern koennten |
| 360 | // sich verschoben haben. |
| 361 | expect(eintrag.ueblich).toContain('2025 neu gefasst'); |
| 362 | }); |
| 363 | |
| 364 | it('nennt die Regelwerke der Arbeitsstellensicherung und ihre Einführung', () => { |
| 365 | const text = HILFE.rechtsgrundlagen.wasIstDas; |
| 366 | expect(text).toContain('RSA 21'); |
| 367 | expect(text).toContain('TL und ZTV transportable Lichtsignalanlagen 2023'); |
| 368 | expect(text).toContain('ARS Nr. 07/2024'); |
| 369 | // Und den Unterschied Gesetz / Vorschrift / Regel der Technik. |
| 370 | expect(text).toContain('anerkannte Regeln der Technik'); |
| 371 | expect(HILFE.rechtsgrundlagen.woher).toContain('nur bei einstreifiger Verkehrsführung'); |
| 372 | }); |
| 373 | |
| 374 | it('führt die Gelbzeitstaffel auf die VwV-StVO zurück', () => { |
| 375 | expect(HILFE.gelbzeit.woher).toContain('VwV-StVO zu § 37 (Rn. 17) selbst vor'); |
| 376 | expect(HILFE.gelbzeit.woher).toContain('die RiLSA nennt dieselben Werte'); |
| 377 | }); |
| 378 | |
| 379 | it('weist die ÖPNV-Mindestfreigabezeit als Wert der Planungspraxis aus', () => { |
| 380 | const text = HILFE.mindestfreigabezeit.wasIstDas; |
| 381 | expect(text).toContain(`${RILSA_DEFAULTS.minGreen.oepnv} s`); |
| 382 | expect(text).toContain('Plausibilitätswert der Planungspraxis'); |
| 383 | expect(text).toContain('kein Wert der RiLSA'); |
| 384 | expect(text).toContain('Planungspraxis“'); |
| 385 | // Der belegte Wert bleibt der belegte Wert. |
| 386 | expect(text).toContain('allgemeine Mindestfreigabezeit von 5 s'); |
| 387 | }); |
| 388 | |
| 389 | it('erklärt beide Anordnungsangaben und führt sie im Glossar', () => { |
| 390 | for (const schluessel of ['einsatzzeiten', 'betriebsverantwortlicher'] as const) { |
| 391 | const eintrag = HILFE[schluessel]; |
| 392 | expect(eintrag.woher.length, schluessel).toBeGreaterThan(40); |
| 393 | // Beide sind ausdruecklich KEINE Rechengroessen - sonst sucht jemand die |
| 394 | // Wirkung im Signalzeitenplan. |
| 395 | expect(eintrag.wennFalsch, schluessel).toContain('Keine Rechenwirkung'); |
| 396 | expect( |
| 397 | glossarEintraege().map((e) => e.schluessel), |
| 398 | schluessel, |
| 399 | ).toContain(schluessel); |
| 400 | } |
| 401 | expect(glossarSuche('Betriebsverantwortlicher').map((e) => e.schluessel)).toContain( |
| 402 | 'betriebsverantwortlicher', |
| 403 | ); |
| 404 | expect(glossarSuche('Rechtsgrundlagen').map((e) => e.schluessel)).toContain('rechtsgrundlagen'); |
| 405 | }); |
| 406 | }); |