import { describe, expect, it } from 'vitest'; import { buildProjectPdf, DEFAULT_PDF_OPTIONS, type PdfOptions } from '@/services/export/pdf'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { validateProject } from '@/domain/validation'; import { createArm, createEmptyProject, createPhase, createSignalGroup, } from '@/domain/model/factory'; import type { Project, SignalGroup } from '@/domain/model/project'; /* * BEFUND 39 (Fassung 5.11.0): Die Zwischenzeitenmatrix breitete ihre n-mal-n * Zellen als Argumentliste in Math.max aus. * * Was der Anwender sonst erlebt: Er liest eine Projektdatei mit 356 oder mehr * Signalgruppen ein - weder `parseSignalGroups` noch `addSignalGroup` kennen * eine Obergrenze - und erzeugt die Planunterlage. V8 nimmt nur rund 125.000 * Argumente an; darueber bricht der Aufruf mit "RangeError: Maximum call stack * size exceeded" ab. In der Ansicht "Ausgabe" steht dann nur "Das PDF konnte * nicht erzeugt werden: Maximum call stack size exceeded", und es entsteht * keine Unterlage - auch der Pruefbericht nicht. * * Betroffen sind beide Satzarten, denn beide breiten `zellen.flat()` aus: * `matrixPasstAufEinBlatt` bei eindeutigen Namen und `matrixSatz` bei doppelten * (dort greift die fruehe Rueckgabe aus `matrixMarkenSindEindeutig`). */ const FEST = new Date('2026-01-01T12:00:00Z'); /** 355^2 = 126.025 Werte - gerade oberhalb der gemessenen Schranke von V8. */ const GRUPPEN = 356; const NUR_MATRIX: PdfOptions = { ...DEFAULT_PDF_OPTIONS, includeSignalGroups: false, includePhases: false, includePlan: false, includePerformance: false, includeReport: false, includeCalculationDetails: false, includeSources: false, }; /** * Eine grosse Anlage ohne jede Konfliktbeziehung: Die Matrix ist voll besetzt * mit dem Punkt fuer "vertraeglich" - n^2 Zellen, aber nichts zu rechnen. */ function grosseAnlage(n: number, name: (i: number) => string): Project { const basis = createEmptyProject('Grosse Anlage', FEST, 'knotenpunkt'); const arme = ['Nord', 'Ost', 'Süd', 'West'].map((r) => createArm(r, r, 2, 50)); const groups: SignalGroup[] = []; for (let i = 0; i < n; i += 1) { groups.push( createSignalGroup({ name: name(i), mode: 'kfz', armId: arme[i % 4]?.id ?? null, index: i, }), ); } // Alle Gruppen in einer Phase: Sonst meldet die Pruefung "keiner Phase // zugeordnet" und die Ausgabe waere in der Oberflaeche gesperrt. const phase = createPhase( 'Phase 1', groups.map((g) => g.id), ); return { ...basis, intersection: { ...basis.intersection, arms: arme }, signalGroups: groups, conflicts: [], phases: [phase], program: { ...basis.program, phaseOrder: [phase.id] }, }; } function unterlage(project: Project): Uint8Array { const plan = buildSignalPlan(project); return buildProjectPdf(project, plan, validateProject(project, plan, FEST), NUR_MATRIX, FEST); } describe('Befund 39 - Zwischenzeitenmatrix bei sehr vielen Signalgruppen', () => { it(`erzeugt die Unterlage bei ${GRUPPEN} Signalgruppen mit eindeutigen Namen`, () => { const bytes = unterlage(grosseAnlage(GRUPPEN, (i) => `K${i + 1}`)); expect(bytes.byteLength).toBeGreaterThan(0); }, 120_000); it(`erzeugt die Unterlage bei ${GRUPPEN} Signalgruppen mit doppelten Namen`, () => { // Doppelte Namen fuehren ueber matrixMarkenSindEindeutig in den // Ausschnittsatz - dort steht die zweite Ausbreitung. const bytes = unterlage(grosseAnlage(GRUPPEN, () => 'K')); expect(bytes.byteLength).toBeGreaterThan(0); }, 120_000); });