import { afterEach, describe, expect, it } from 'vitest'; import { ProjectStore } from '@/app/store'; import type { Haltlinie, Lageplan, Planlinie } from '@/domain/geometrie/lageplan'; import { REGELBREITE } from '@/domain/geometrie/vermessung'; import { createEmptyProject } from '@/domain/model/factory'; import type { Project } from '@/domain/model/project'; import { lageplanView } from '@/ui/views/lageplanView'; /** * Die Spalte "Winkel" gab `winkelGrad` stur als Gradzahl aus. * * WAS DER ANWENDER SONST ERLEBT * * Zwei Stroeme laufen ein Stueck nebeneinander her, statt sich in einem Punkt * zu kreuzen - eine Furt entlang einer Fahrlinie, zwei Fahrbeziehungen auf * derselben Flaeche. Der Konfliktbereich stammt dann aus dem Laengslauf, und * `Konfliktbereich.winkel` traegt 0: Das heisst "laengs", nicht "null Grad" * (siehe vermessung.ts). Die Tabelle druckte daraus "0°" - also einen Schnitt * unter null Grad und damit die Aussage, die beiden Linien kreuzten sich gar * nicht. Genau daneben stehen Raeumweg und Einfahrweg derselben Beziehung. * * `winkelGrad` geht in keine Zwischenzeit ein; es ist kein Rechenfehler, * sondern eine irrefuehrende Anzeige. */ const FIXED_DATE = new Date('2026-01-01T00:00:00Z'); /** 1 Bildpunkt = 1 m, damit sich die Masse unmittelbar ablesen lassen. */ const KALIBRIERUNG = { von: { x: 0, y: 0 }, bis: { x: 100, y: 0 }, laengeMeter: 100, herkunft: 'gemessen', } as const; const HL_NORD = 'hl-nord'; const HL_OST = 'hl-ost'; const HALTLINIEN: readonly Haltlinie[] = [ { id: HL_NORD, name: 'Zufahrt Nord', art: 'zufahrt', armId: 'arm-nord', von: { x: 0, y: 0 }, bis: { x: 20, y: 0 }, }, { id: HL_OST, name: 'Zufahrt Ost', art: 'zufahrt', armId: 'arm-ost', von: { x: 0, y: 10 }, bis: { x: 20, y: 10 }, }, ]; const linie = ( id: string, name: string, punkte: readonly { x: number; y: number }[], patch: Partial = {}, ): Planlinie => ({ id, name, mode: 'kfz', movement: 'geradeaus', breiteMeter: REGELBREITE.kfz, punkte, haltlinieId: HL_NORD, startT: 0.5, signalGroupId: null, ...patch, }); /** * Zwei Stroeme verschiedener Zufahrten, die 2 m nebeneinander herlaufen. * * Bei 3,25 m Korridorbreite je Strom ueberlappen sich die Korridore auf ganzer * Laenge; einen Punktschnitt gibt es nicht. */ const LAENGS_A = linie('l-a', 'Nord geradeaus', [ { x: 10, y: 0 }, { x: 10, y: 60 }, ]); const LAENGS_B = linie( 'l-b', 'Ost geradeaus', [ { x: 12, y: 10 }, { x: 12, y: 60 }, ], { haltlinieId: HL_OST }, ); /** Gegenprobe: ein echter Punktschnitt, quer zur Nordzufahrt. */ const QUER = linie( 'l-quer', 'Querung Ost', [ { x: 0, y: 30 }, { x: 40, y: 30 }, ], { mode: 'fuss', movement: 'querung', breiteMeter: REGELBREITE.fuss, haltlinieId: HL_OST, startT: 0, }, ); function plan(linien: readonly Planlinie[]): Lageplan { return { bild: null, kalibrierung: KALIBRIERUNG, haltlinien: HALTLINIEN, linien, arbeitsbereiche: [], signalgeber: [], }; } function projekt(lageplan: Lageplan): Project { return { ...createEmptyProject('Winkel', FIXED_DATE), lageplan }; } interface Ansicht { readonly wurzel: HTMLElement; readonly aufraeumen: () => void; } function baueAnsicht(lageplan: Lageplan): Ansicht { const store = new ProjectStore(projekt(lageplan)); const wurzel = document.createElement('div'); document.body.append(wurzel); const schliessen = lageplanView.render(wurzel, { store, refresh: () => {}, navigate: () => {}, target: null, }); return { wurzel, aufraeumen: () => { schliessen(); wurzel.remove(); }, }; } afterEach(() => { document.body.replaceChildren(); }); /** Die Spalte "Winkel" aller Zeilen der Tabelle "Vermessene Beziehungen". */ function winkelspalte(ansicht: Ansicht): string[] { const tabelle = [...ansicht.wurzel.querySelectorAll('table')].find((t) => (t.querySelector('thead')?.textContent ?? '').includes('Winkel'), ); if (tabelle === undefined) throw new Error('Keine Tabelle mit einer Spalte "Winkel"'); const kopf = [...(tabelle.querySelector('thead tr')?.children ?? [])]; const spalte = kopf.findIndex((z) => (z.textContent ?? '').trim() === 'Winkel'); if (spalte < 0) throw new Error('Keine Spalte "Winkel"'); return [...tabelle.querySelectorAll('tbody tr')].map( (zeile) => zeile.children[spalte]?.textContent?.trim() ?? '', ); } describe('Spalte „Winkel“ der vermessenen Beziehungen', () => { it('weist ein Bereichsende aus dem Längslauf als „längs“ aus statt als 0 Grad', () => { const ansicht = baueAnsicht(plan([LAENGS_A, LAENGS_B])); try { const winkel = winkelspalte(ansicht); // Grundlage: Die beiden Stroeme sind ueberhaupt als feindlich vermessen. expect(winkel.length, 'die Überlagerung wurde nicht vermessen').toBeGreaterThan(0); // Der Kern der Aenderung: "0°" behauptet einen Schnitt unter null Grad - // also gar keine Kreuzung - neben einem Räum- und einem Einfahrweg. expect(winkel).not.toContain('0°'); expect(winkel.every((w) => w === 'längs')).toBe(true); } finally { ansicht.aufraeumen(); } }); it('gibt einen echten Punktschnitt weiter in Grad an', () => { // Gegenprobe gegen die Ueberkorrektur: Wo ein Schnitt den Raeumweg // bestimmt, gehoert seine Gradzahl weiter in die Spalte. const ansicht = baueAnsicht(plan([LAENGS_A, QUER])); try { const winkel = winkelspalte(ansicht); expect(winkel.length, 'die Kreuzung wurde nicht vermessen').toBeGreaterThan(0); expect(winkel.every((w) => w.endsWith('°'))).toBe(true); expect(winkel).not.toContain('längs'); } finally { ansicht.aufraeumen(); } }); });