import { describe, expect, it } from 'vitest'; import { aspectAt, aspectSegments, aspectTotals, isGreenAt } from '@/domain/plan/aspects'; import type { PlannedSignalGroup } from '@/domain/plan/signalPlan'; /* * Befund F33: Reichte das Gelb des einen Freigabefensters ueber den Beginn des * naechsten hinweg, kuerzte aspectSegments die FREIGABE statt das Gelb. * * Der Anwender sah dadurch in derselben Tabellenzeile zwei einander * widersprechende Zahlen: In der Spalte "Freigabezeit (Summe)" stand die volle * Freigabezeit aus dem Plan, in den Spalten Rot-Gelb/Gelb/Rot die um die * Ueberschneidung gekuerzte Summe aus den Signalbildern - und der gezeichnete * Balken zeigte Gelb, wo die Simulation Freigabe meldete. */ type Pruefgruppe = Pick; /** Zwei Freigabefenster ohne Abstand; das Gelb des ersten faellt ins zweite. */ const OHNE_ABSTAND: Pruefgruppe = { greens: [ { start: 0, duration: 20 }, { start: 20, duration: 15 }, ], times: { minGreen: 5, yellow: 3, redYellow: 1 }, }; /** Drei Sekunden Abstand bei fuenf Sekunden Gelbzeit. */ const KUERZER_ALS_GELB: Pruefgruppe = { greens: [ { start: 0, duration: 20 }, { start: 23, duration: 15 }, ], times: { minGreen: 5, yellow: 5, redYellow: 1 }, }; const UMLAUF = 60; describe('Signalbilder bei dicht aufeinanderfolgenden Freigabefenstern', () => { it('zeigt die volle Freigabezeit, wenn zwei Fenster ohne Abstand aufeinanderfolgen', () => { const totals = aspectTotals(OHNE_ABSTAND, UMLAUF); const freigabe = OHNE_ABSTAND.greens.reduce((summe, g) => summe + g.duration, 0); expect(totals.gruen).toBeCloseTo(freigabe, 3); }); it('kuerzt das Gelb und nicht die Freigabe, wenn der Abstand kleiner als die Gelbzeit ist', () => { const totals = aspectTotals(KUERZER_ALS_GELB, UMLAUF); const freigabe = KUERZER_ALS_GELB.greens.reduce((summe, g) => summe + g.duration, 0); expect(totals.gruen).toBeCloseTo(freigabe, 3); // Von den 5 s Gelb des ersten Fensters bleiben die 3 s bis zum naechsten // Freigabebeginn; das zweite Fenster behaelt seine vollen 5 s. expect(totals.gelb).toBeCloseTo(8, 3); }); it('sagt dasselbe wie die Freigabezeitfenster - Signalbild gegen isGreenAt', () => { for (const gruppe of [OHNE_ABSTAND, KUERZER_ALS_GELB]) { for (let t = 0; t < UMLAUF; t += 0.5) { expect(aspectAt(gruppe, t, UMLAUF) === 'gruen', `t=${t}s`).toBe( isGreenAt(gruppe, t, UMLAUF), ); } } }); it('zerlegt den Umlauf weiterhin lueckenlos und ueberschneidungsfrei', () => { for (const gruppe of [OHNE_ABSTAND, KUERZER_ALS_GELB]) { const segmente = aspectSegments(gruppe, UMLAUF); let cursor = 0; for (const segment of segmente) { expect(segment.start).toBeCloseTo(cursor, 3); expect(segment.duration).toBeGreaterThan(0); cursor += segment.duration; } expect(cursor).toBeCloseTo(UMLAUF, 3); } }); });