import { describe, expect, it } from 'vitest'; import { signalGroupsCsv } from '@/services/export/csv'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import type { Project } from '@/domain/model/project'; /* * Befund 55 zur Fassung 5.9.0. * * Der Kopfkommentar von src/services/export/csv.ts sagt zu: "Trennzeichen ist * das Semikolon und Dezimaltrennzeichen das Komma - so oeffnet Excel in * deutscher Einstellung die Datei ohne Importdialog." Jede Zahl der Tabelle * laeuft ueber src/ui/format.ts, nur die Fahrstreifenzahl ging als * `String(group.lanes)` hinaus. * * `num()` in schema.ts rundet `lanes` seit der Fassung 5.10.0 auf eine * ganze Zahl AB und meldet die Ersetzung (`ganzzahlAbrunden`; Wache * tests/domain/fahrstreifenzahl.test.ts) - aus einer Projektdatei kommt * eine 2,5 also nicht mehr durch. Der Fall laesst sich hier trotzdem bauen: * Diese Datei setzt das Projekt unmittelbar zusammen, ohne `parseProject`, und * `SignalGroup.lanes` traegt im Typ keine Ganzzahlschranke. Bewacht wird die * Schreibweise der Zelle und nicht der Weg, auf dem der Wert entsteht: In * einer deutsch eingestellten Tabellenkalkulation ist der Punkt Tausender- * bzw. Datumstrenner - die Zelle wird dort nicht als Zahl gelesen. * * Gegen den Altstand schlaegt der erste Fall fehl; der zweite haelt fest, dass * sich an ganzzahligen Werten nichts aendert. */ const STICHTAG = new Date('2026-01-01T12:00:00Z'); function tabelle(csv: string): { kopf: string[]; zeilen: string[][] } { const zerlege = (zeile: string): string[] => (zeile.match(/("([^"]|"")*"|[^;]*)(;|$)/g) ?? []).map((feld) => feld.replace(/;$/, '').replace(/^"|"$/g, '').replace(/""/g, '"'), ); const [kopfzeile, ...rest] = csv.split('\r\n'); return { kopf: zerlege((kopfzeile ?? '').replace(/^/, '')), zeilen: rest.filter((z) => z.length > 0).map(zerlege), }; } /** Knotenpunkt, dessen erste Signalgruppe eine gebrochene Fahrstreifenzahl traegt. */ function mitGebrochenerFahrstreifenzahl(): Project { const basis = createStandardIntersectionProject('Fahrstreifen', STICHTAG); return { ...basis, signalGroups: basis.signalGroups.map((g, index) => (index === 0 ? { ...g, lanes: 2.5 } : g)), }; } describe('Befund 55 - die Fahrstreifenspalte trägt das deutsche Dezimalkomma', () => { it('schreibt 2,5 statt 2.5', () => { const projekt = mitGebrochenerFahrstreifenzahl(); const { kopf, zeilen } = tabelle(signalGroupsCsv(projekt, buildSignalPlan(projekt))); const spalte = kopf.indexOf('Fahrstreifen'); const zeile = zeilen.find((z) => z[0] === projekt.signalGroups[0]!.name)!; expect(spalte).toBeGreaterThanOrEqual(0); expect(zeile[spalte]).toBe('2,5'); // Kein englischer Dezimalpunkt - die Zusage des Kopfkommentars gilt auch // fuer diese Zelle. (Der Punkt als Tausendertrenner steht anderswo // absichtlich, etwa in "4.784,69".) expect(zeile[spalte]).not.toContain('.'); }); it('lässt ganze Fahrstreifenzahlen unverändert', () => { const projekt = createStandardIntersectionProject('Fahrstreifen', STICHTAG); const { kopf, zeilen } = tabelle(signalGroupsCsv(projekt, buildSignalPlan(projekt))); const spalte = kopf.indexOf('Fahrstreifen'); for (const gruppe of projekt.signalGroups) { const zeile = zeilen.find((z) => z[0] === gruppe.name)!; expect(zeile[spalte], gruppe.name).toBe(String(gruppe.lanes)); } }); });