import { afterEach, describe, expect, it, vi } from 'vitest'; import { ProjectStore } from '@/app/store'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import type { Project } from '@/domain/model/project'; import { vergessVergleichsprojekt, vergleichView } from '@/ui/views/vergleichView'; import { openProjectFromFile } from '@/services/storage'; /** * Die Ansicht "Vergleich". * * DIE BEDINGUNG DES BEFUNDS: Die Ansicht bleibt LESEND. Kein Knopf, der einen * Wert von der anderen Seite uebernimmt. Eine Zwischenzeit gehoert zu den * Wegen, aus denen sie entstanden ist; herueberkopiert waere sie eine Zahl * ohne Rechenweg - und der Ausdruck fuehrte sie als nachgewiesen. */ vi.mock('@/services/storage', async (importOriginal) => { const echt = await importOriginal(); return { ...echt, openProjectFromFile: vi.fn() }; }); const STICHTAG = new Date('2026-01-01T12:00:00Z'); function basis(): Project { return createStandardIntersectionProject('Hauptstraße / Bahnhofstraße', STICHTAG); } function zeichne(project: Project): { wurzel: HTMLElement; store: ProjectStore; aufraeumen: () => void; } { const wurzel = document.createElement('div'); document.body.append(wurzel); const store = new ProjectStore(project); const ende = vergleichView.render(wurzel, { store, refresh: () => {}, navigate: () => {}, target: null, }); return { wurzel, store, aufraeumen: () => { ende(); wurzel.remove(); }, }; } function knopf(wurzel: ParentNode, text: string): HTMLButtonElement { const treffer = [...wurzel.querySelectorAll('button')].find( (b) => b.textContent?.trim() === text, ); if (!treffer) throw new Error(`Schaltfläche "${text}" nicht gefunden.`); return treffer; } /** Laesst den naechsten "Öffnen"-Klick dieses Projekt liefern. */ function liefere(project: Project, optionen: { migriert?: boolean; fassung?: string } = {}): void { vi.mocked(openProjectFromFile).mockResolvedValue({ ok: true, canceled: false, filePath: 'C:/Plaene/Bestand.lsap', message: '', result: { project, issues: [], migrated: optionen.migriert ?? false, repaired: false, sourceVersion: optionen.fassung ?? '16', intergreenComparison: [], }, }); } async function warte(): Promise { await new Promise((fertig) => setTimeout(fertig, 0)); } afterEach(() => { vergessVergleichsprojekt(); vi.mocked(openProjectFromFile).mockReset(); }); describe('Ansicht Vergleich', () => { it('zeigt ohne Vergleichsprojekt einen Leerhinweis und keine Zahlen', () => { const { wurzel, aufraeumen } = zeichne(basis()); try { expect(wurzel.textContent).toContain('Noch kein Vergleichsprojekt geladen'); expect(wurzel.querySelectorAll('table')).toHaveLength(0); expect(knopf(wurzel, 'Vergleichsprojekt öffnen')).toBeTruthy(); // Die Zusage steht auf der Seite und nicht nur im Quelltext. expect(wurzel.textContent).toContain('Die Datei wird nur gelesen'); } finally { aufraeumen(); } }); it('stellt nach dem Öffnen beide Planfälle gegenüber', async () => { const roh = basis(); const kuerzer: Project = { ...roh, conflicts: roh.conflicts.map((c) => ({ ...c, clearingDistance: Math.max(0, c.clearingDistance - 5), })), }; // Geladen wird der Bestand mit den LAENGEREN Wegen; das offene Projekt hat // die kuerzeren - also muss die Ansicht Verkuerzungen melden. liefere(roh); const { wurzel, aufraeumen } = zeichne(kuerzer); try { knopf(wurzel, 'Vergleichsprojekt öffnen').click(); await warte(); expect(wurzel.textContent).toContain('KÜRZER als im Vergleichsprojekt'); expect(wurzel.querySelectorAll('table').length).toBeGreaterThanOrEqual(2); expect(wurzel.textContent).toContain('Bestand.lsap'); } finally { aufraeumen(); } }); it('bietet keinen Weg, einen Wert zu übernehmen', async () => { /* * DER WICHTIGSTE FALL DIESER DATEI. Geprueft wird nicht das Fehlen eines * bestimmten Knopfes, sondern dass die Ansicht ueberhaupt nur zwei * Schaltflaechen hat - und beide betreffen das Laden der Vergleichsdatei. * Ein neuer Knopf, der etwas mit dem Projekt tut, macht diesen Fall rot. */ liefere(basis()); const roh = basis(); const { wurzel, store, aufraeumen } = zeichne(roh); try { knopf(wurzel, 'Vergleichsprojekt öffnen').click(); await warte(); const beschriftungen = [...wurzel.querySelectorAll('button')].map((b) => (b.textContent ?? '').trim(), ); expect(beschriftungen).toEqual(['Anderes Projekt öffnen', 'Vergleich schließen']); expect(wurzel.querySelectorAll('input, select, textarea')).toHaveLength(0); // Und das Projekt selbst ist unberuehrt geblieben. expect(store.getProject()).toBe(roh); } finally { aufraeumen(); } }); it('nennt den Vorbehalt zu einem hochgezogenen Altprojekt', async () => { liefere(basis(), { migriert: true, fassung: '4.3.3' }); const { wurzel, aufraeumen } = zeichne(basis()); try { knopf(wurzel, 'Vergleichsprojekt öffnen').click(); await warte(); expect(wurzel.textContent).toContain('NEU GERECHNET'); } finally { aufraeumen(); } }); it('lässt den Vergleich wieder schließen', async () => { liefere(basis()); const { wurzel, aufraeumen } = zeichne(basis()); try { knopf(wurzel, 'Vergleichsprojekt öffnen').click(); await warte(); knopf(wurzel, 'Vergleich schließen').click(); expect(wurzel.textContent).toContain('Noch kein Vergleichsprojekt geladen'); expect(wurzel.querySelectorAll('table')).toHaveLength(0); } finally { aufraeumen(); } }); });