import { describe, expect, it } from 'vitest'; import { createEmptyProject } from '@/domain/model/factory'; import * as actions from '@/app/actions'; import { parseProject } from '@/domain/model/schema'; import { serializeProject } from '@/services/storage'; import { CURRENT_SCHEMA_VERSION, type Project } from '@/domain/model/project'; import { fahrlinienVon } from '@/domain/geometrie/lageplan'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import type { Signalgeber } from '@/domain/geometrie/lageplan'; /* * STUFE 3 DES SIGNALLAGEPLAN-ENTWURFS: DER SIGNALGEBER WIRD GEZEICHNET UND * RECHNET NICHT MIT. * * Der zweite Teil des Satzes ist der wichtigere, und er ist die Bedingung, * unter der es das Objekt ueberhaupt gibt. Aus einem Signalgeberstandort * LIESSE sich rechnen - der Abstand zur Haltlinie, eine Sichtweite, ein * Raeumweganfang -, und jede dieser Groessen beruehrt die Zwischenzeit. Was * Zwischenzeiten verkuerzt, ist die gefaehrliche Richtung; solange die * Regelwerke zu Aufstellung und Abstaenden nicht im Original vorliegen, wird * gezeichnet und nicht gerechnet. * * Diese Datei haelt das fest, damit es nicht spaeter still in eine Ableitung * wandert. Dasselbe tut `tests/domain/arbeitsbereich.test.ts` fuer das Band - * und dort war es die richtige Entscheidung, wie der Vergleich zeigt. */ const DATUM = new Date('2026-09-12T10:00:00Z'); const GEBER: Signalgeber = { id: 'sg-1', name: 'K1', punkt: { x: 120, y: 80 }, ausrichtung: 90, signalGroupId: null, }; function projektMitGeber(geber: Signalgeber = GEBER): Project { const basis = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig'); return actions.addSignalgeber(basis, geber); } describe('Signalgeber im Lageplan', () => { it('legt an, ändert und löscht über reine Funktionen', () => { const mit = projektMitGeber(); expect(mit.lageplan.signalgeber).toHaveLength(1); const umbenannt = actions.setSignalgeber(mit, 'sg-1', { name: 'K2' }); expect(umbenannt.lageplan.signalgeber[0]!.name).toBe('K2'); // Nichts wird an Ort und Stelle veraendert. expect(mit.lageplan.signalgeber[0]!.name).toBe('K1'); expect(actions.removeSignalgeber(mit, 'sg-1').lageplan.signalgeber).toHaveLength(0); }); it('erscheint in keiner Fahrlinie und erzeugt keine Konfliktbeziehung', () => { /* * DIE ENTSCHEIDENDE PRUEFUNG. `fahrlinienVon` ist die Stelle, an der aus * der Zeichnung Vermessung wird - Kreuzungspunkte, Konfliktbereiche, * Raeum- und Einfahrwege. Was dort auftaucht, rechnet mit. */ const projekt = projektMitGeber(); expect(fahrlinienVon(projekt.lageplan)).toHaveLength(0); expect(projekt.conflicts).toHaveLength(0); }); it('ändert keine einzige Zahl des Signalzeitenplans', () => { const ohne = createEmptyProject('Arbeitsstelle', DATUM, 'einstreifig'); const mit = actions.addSignalgeber(ohne, GEBER); const planOhne = buildSignalPlan(ohne); const planMit = buildSignalPlan(mit); expect(planMit.cycleTime).toBe(planOhne.cycleTime); expect(planMit.notes.map((n) => n.code)).toEqual(planOhne.notes.map((n) => n.code)); }); it('überlebt Speichern und Einlesen unverändert', () => { const projekt = projektMitGeber({ ...GEBER, ausrichtung: 217 }); const gelesen = parseProject(JSON.parse(serializeProject(projekt))); expect(gelesen.project.schemaVersion).toBe(CURRENT_SCHEMA_VERSION); expect(gelesen.project.lageplan.signalgeber).toEqual(projekt.lageplan.signalgeber); expect(gelesen.issues).toEqual([]); }); it('zieht die Ausrichtung beim Einlesen auf 0 bis unter 360 Grad', () => { // Ein Wert von 400 oder -20 aus fremder Datei ist kein Fehler, sondern // dieselbe Richtung anders geschrieben. Eine Meldung darueber waere Laerm. const roh = JSON.parse(serializeProject(projektMitGeber())) as Record; const lageplan = (roh['lageplan'] as Record) ?? {}; (lageplan['signalgeber'] as Record[])[0]!['ausrichtung'] = 400; const gelesen = parseProject(roh); expect(gelesen.project.lageplan.signalgeber[0]!.ausrichtung).toBe(40); }); it('behält den Standort, wenn die Signalgruppe gelöscht wird', () => { /* * Der Standort IST die Aussage des Objekts, die Verknuepfung nur seine * Beschriftung. Faellt die Gruppe fort, faellt der Verweis - nicht der * gezeichnete Punkt, den jemand vermessen hat. */ const basis = createEmptyProject('Knoten', DATUM); const mitGruppe = actions.addSignalGroup(basis, 'kfz'); const gruppe = mitGruppe.signalGroups[0]!; const mitGeber = actions.addSignalgeber(mitGruppe, { ...GEBER, signalGroupId: gruppe.id }); const ohneGruppe = actions.removeSignalGroup(mitGeber, gruppe.id); expect(ohneGruppe.lageplan.signalgeber).toHaveLength(1); expect(ohneGruppe.lageplan.signalgeber[0]!.punkt).toEqual(GEBER.punkt); expect(ohneGruppe.lageplan.signalgeber[0]!.signalGroupId).toBeNull(); }); it('hebt einen Verweis auf eine nicht vorhandene Signalgruppe beim Einlesen auf', () => { const roh = JSON.parse(serializeProject(projektMitGeber())) as Record; const lageplan = (roh['lageplan'] as Record) ?? {}; (lageplan['signalgeber'] as Record[])[0]!['signalGroupId'] = 'gibt-es-nicht'; const gelesen = parseProject(roh); expect(gelesen.project.lageplan.signalgeber[0]!.signalGroupId).toBeNull(); expect(gelesen.project.lageplan.signalgeber[0]!.punkt).toEqual(GEBER.punkt); expect(gelesen.issues.some((i) => i.path.includes('signalgeber'))).toBe(true); }); it('lässt einen Signalgeber ohne brauchbaren Standort aus und sagt es', () => { // Ein Punkt ohne Koordinaten ist kein Standort. Ihn auf 0,0 zu setzen // hiesse, eine Aufstellung an der Bildecke zu behaupten. const roh = JSON.parse(serializeProject(projektMitGeber())) as Record; const lageplan = (roh['lageplan'] as Record) ?? {}; (lageplan['signalgeber'] as Record[])[0]!['punkt'] = { x: 'weit', y: null }; const gelesen = parseProject(roh); expect(gelesen.project.lageplan.signalgeber).toHaveLength(0); expect(gelesen.issues.some((i) => i.message.includes('Standort'))).toBe(true); }); it('gibt einer Altdatei aus Schema 18 eine leere Liste', () => { // Das ist die Wahrheit ueber sie: Dort war nichts erfasst. Ein Nachtrag // waere eine erfundene Aufstellung. const roh = JSON.parse(serializeProject(createEmptyProject('Alt', DATUM))) as Record< string, unknown >; roh['schemaVersion'] = 18; delete (roh['lageplan'] as Record)['signalgeber']; const gelesen = parseProject(roh); expect(gelesen.project.lageplan.signalgeber).toEqual([]); }); });