import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ProjectStore } from '@/app/store'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import type { RilsaDefaults } from '@/domain/rilsa/constants'; import type { Project } from '@/domain/model/project'; import type { TrafficMode, VehicleClass } from '@/domain/rilsa/types'; import type { ViewContext } from '@/ui/shell'; /* * Punkt X.1 - dieselbe Schranke stand zweimal. * * `istKraftfahrzeugstrom` und `laengenzuschlagGenuegt` fuehrte die * Signalgruppenansicht als eigene Fassungen, obwohl beide seit der * Fassung 5.9.0 in `@/domain/rilsa/intergreen` stehen und dort von Einlesen und * Zwischenzeitrechnung benutzt werden. Zwei Fassungen derselben Schranke laufen * auseinander - die des Fachkerns war bereits strenger (sie prueft den * hinterlegten Zuschlag zusaetzlich mit `isFiniteNumber`). * * WIE HIER GEMESSEN WIRD: Nicht am Quelltext, sondern an der Wirkung. Der * Fachkern wird durch eine gesteuerte Fassung ersetzt; benutzt die Ansicht ihre * eigene, bleibt die Steuerung wirkungslos, und die Faelle sind rot. Der * Unterschied der beiden Fassungen selbst (`isFiniteNumber`) ist ueber die * Vorgabenverwaltung nicht erreichbar - sie kappt jede Laengenangabe auf ihren * Wertebereich - und taugt deshalb nicht als Massstab. */ const steuerung = vi.hoisted(() => ({ genuegt: null as ((art: string) => boolean) | null, kfzStrom: null as ((mode: string) => boolean) | null, })); vi.mock('@/domain/rilsa/intergreen', async () => { const echt = await vi.importActual( '@/domain/rilsa/intergreen', ); return { ...echt, laengenzuschlagGenuegt: (art: VehicleClass, defaults?: RilsaDefaults): boolean => steuerung.genuegt === null ? echt.laengenzuschlagGenuegt(art, defaults) : steuerung.genuegt(art), istKraftfahrzeugstrom: (mode: TrafficMode): boolean => steuerung.kfzStrom === null ? echt.istKraftfahrzeugstrom(mode) : steuerung.kfzStrom(mode), }; }); const { signalGroupsView } = await import('@/ui/views/signalGroupsView'); let aufraeumen: (() => void) | null = null; beforeEach(() => { steuerung.genuegt = null; steuerung.kfzStrom = null; }); afterEach(() => { aufraeumen?.(); aufraeumen = null; document.body.replaceChildren(); }); function zeichne(project: Project): { wurzel: HTMLElement; store: ProjectStore } { const wurzel = document.createElement('div'); document.body.append(wurzel); const store = new ProjectStore(project); const context: ViewContext = { store, refresh: () => {}, navigate: () => {}, target: null, }; const schliessen = signalGroupsView.render(wurzel, context); aufraeumen = () => { schliessen(); wurzel.remove(); }; return { wurzel, store }; } function feldDerZeile(wurzel: HTMLElement, spalte: string, zeile: string): HTMLSelectElement { const feld = wurzel.querySelector(`select[aria-label="${spalte}, ${zeile}"]`); expect(feld, `Kein Feld "${spalte}" in der Zeile "${zeile}"`).not.toBeNull(); return feld as HTMLSelectElement; } const werteDer = (feld: HTMLSelectElement): VehicleClass[] => [...feld.options].map((option) => option.value as VehicleClass); describe('X.1: die Signalgruppenansicht holt die Schranke aus dem Fachkern', () => { it('bietet an einem Kfz-Strom an, was `laengenzuschlagGenuegt` des Fachkerns zulaesst', () => { steuerung.genuegt = () => true; const { wurzel } = zeichne(createStandardIntersectionProject()); const werte = werteDer(feldDerZeile(wurzel, 'Fahrzeugart', 'K1')); expect(werte, 'die Ansicht entscheidet mit einer eigenen Schranke').toContain('keine'); expect(werte).toContain('rad'); }); it('nimmt jede Art aus der Liste, sobald der Fachkern keine mehr zulaesst', () => { steuerung.genuegt = () => false; const { wurzel } = zeichne(createStandardIntersectionProject()); const feld = feldDerZeile(wurzel, 'Fahrzeugart', 'K1'); // Uebrig bleibt allein der gespeicherte Wert - er darf nicht verschwinden, // sonst zeigte das Auswahlfeld eine andere Art an als die gerechnete. expect(werteDer(feld)).toEqual(['pkw']); expect(feld.options[0]?.textContent ?? '').toContain('unzulässig'); }); it('fragt auch `istKraftfahrzeugstrom` beim Fachkern nach', () => { steuerung.kfzStrom = () => false; const { wurzel } = zeichne(createStandardIntersectionProject()); // Kein Kraftfahrzeugstrom heisst: volle Liste, ohne jede Einschraenkung. expect(werteDer(feldDerZeile(wurzel, 'Fahrzeugart', 'K1'))).toContain('keine'); }); it('nimmt beim Wechsel der Verkehrsart dieselbe Schranke des Fachkerns', () => { steuerung.genuegt = () => true; const { wurzel, store } = zeichne(createStandardIntersectionProject()); const verkehrsart = feldDerZeile(wurzel, 'Verkehrsart', 'F1'); verkehrsart.value = 'kfz'; verkehrsart.dispatchEvent(new Event('change')); // Die Fussgaengergruppe traegt "keine"; genuegt der Zuschlag nach dem // Fachkern, bleibt die Art stehen - der Rueckfall auf die Standardart darf // nicht aus einer zweiten Schranke der Ansicht kommen. expect(store.getProject().signalGroups.find((g) => g.name === 'F1')?.vehicleClass).toBe( 'keine', ); }); });