import { describe, expect, it } from 'vitest'; import { distributeGreenTimes } from '@/domain/rilsa/greenSplit'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import * as actions from '@/app/actions'; import type { Project } from '@/domain/model/project'; /* * Befund 41, behoben in Fassung 5.10.0 - eine Obergrenze von 0 s galt als * "keine Obergrenze". * * `safeMax` gab fuer jeden Wert, der nicht groesser als 0 ist, * Number.POSITIVE_INFINITY zurueck. Eine Phase mit maxGreen = 0 galt damit als * Phase OHNE Obergrenze - das Gegenteil dessen, was eingetragen wurde. Der * Weg dahin: Das Feld "Feste Freigabezeit der N. Phase" laesst 0 s * ausdruecklich zu (FREIGABE_GRENZEN min 0, unterschieden von "leer = * verteilen"), und an einer Phase ohne Signalgruppe ist die * Mindestfreigabezeit 0, die Vorgabe wird also nicht als zu kurz abgelehnt; * `buildSignalPlan` setzt dann minGreen = maxGreen = 0. * * Folge: Die Phase zog die gesamte Zeit an sich, die die uebrigen Phasen wegen * ihrer Hoechstfreigabezeit nicht aufnehmen konnten - waehrend der Hinweis * 'freigabezeit-rest-umgelegt' woertlich zusagt, "eine von Hand vorgegebene * Freigabezeit bleibt dabei unangetastet". * * Unbegrenzt heisst `undefined` (so der Typ: "Optionale Obergrenze"), nicht 0. * `safeMin` daneben behandelt 0 bereits richtig. * * GEGEN DEN ALTSTAND schlagen die ersten drei Faelle fehl. Der vierte ist die * Gegenprobe fuer die weggelassene Obergrenze und besteht auch dort. */ const DATUM = new Date('2026-01-01T00:00:00Z'); /** Standardknotenpunkt mit fester Umlaufzeit und einer leeren Phase mit 0 s. */ function leerePhaseMitNullVorgabe(): Project { const roh = createStandardIntersectionProject('Feste Null', DATUM); const mitPhase = actions.addPhase(roh); const leer = mitPhase.phases[2]!; const gesetzt = actions.updatePhase(mitPhase, leer.id, { manualGreen: 0 }); return { ...gesetzt, program: { ...gesetzt.program, method: 'manuell', manualCycleTime: 300 }, }; } describe('Befund 41 - Hoechstfreigabezeit 0 s ist eine Obergrenze', () => { it('haelt eine Phase mit der Obergrenze 0 s bei 0 s fest', () => { const ergebnis = distributeGreenTimes( 300, [ { id: 'a', weight: 0.5, minGreen: 5, maxGreen: 90 }, { id: 'leer', weight: 0, minGreen: 0, maxGreen: 0 }, ], [0, 0], ); expect(ergebnis.phases.find((p) => p.id === 'leer')?.green).toBe(0); // Die nicht unterzubringende Zeit laeuft in den bereits vorhandenen Zweig // mit eigener Warnung - sie verschwindet nicht. expect(ergebnis.notes.some((n) => n.code === 'freigabezeit-rest')).toBe(true); expect(ergebnis.phases.reduce((s, p) => s + p.green, 0)).toBe(300); }); it('gibt ihr auch ohne Verkehrsstaerken nichts ab', () => { // Ohne Gewichtung werden alle Gewichte auf 1 gesetzt; bis dahin bekam die // auf 0 s gesetzte Phase die Haelfte der Umlaufzeit. const ergebnis = distributeGreenTimes( 60, [ { id: 'a', weight: 0, minGreen: 5, maxGreen: 120 }, { id: 'leer', weight: 0, minGreen: 0, maxGreen: 0 }, ], [0, 0], ); expect(ergebnis.phases.find((p) => p.id === 'leer')?.green).toBe(0); expect(ergebnis.phases.find((p) => p.id === 'a')?.green).toBe(60); }); it('haelt die Vorgabe auch im vollstaendigen Signalzeitenplan ein', () => { const plan = buildSignalPlan(leerePhaseMitNullVorgabe()); expect(plan.phases[2]?.duration).toBe(0); // Der Hinweis, der "unangetastet" zusagt, entsteht damit gar nicht mehr: // Es gibt keine Phase mit Spielraum, auf die umgelegt werden koennte. expect(plan.notes.some((n) => n.code === 'freigabezeit-rest-umgelegt')).toBe(false); }); it('laesst eine weggelassene Obergrenze weiterhin unbegrenzt', () => { // Gegenprobe: "unbegrenzt" wird durch das Weglassen des Feldes // ausgedrueckt, nicht durch die Null. const ergebnis = distributeGreenTimes( 300, [ { id: 'a', weight: 0.5, minGreen: 5, maxGreen: 90 }, { id: 'offen', weight: 0.1, minGreen: 5 }, ], [0, 0], ); expect(ergebnis.phases.find((p) => p.id === 'offen')?.green).toBe(210); }); });