/** * Unit-Tests für die Dach-Darstellung im Grundriss (generatePlan → addRoof): * `project.roofs` wird nach Geschoss + sichtbarer Kategorie gefiltert (analog * Decken) und pro Dach in Grundriss-Linien übersetzt (Traufe/First/Grat/Knick, * aus `roofGeometry`). Reine Linien-Darstellung ohne Poché — die Form (flach/ * sattel/walm/mansarde) entscheidet, welche der vier Linienarten auftauchen. */ import { describe, it, expect } from "vitest"; import { generatePlan } from "./generatePlan"; import type { Project, Roof } from "../model/types"; /** Minimalprojekt ohne Wände: nur das Geschoss + die Dach-Kategorie „35". */ function baseProject(roofs?: Roof[]): Project { return { id: "t", name: "T", lineStyles: [], hatches: [], components: [], wallTypes: [], drawingLevels: [ { id: "eg", name: "EG", kind: "floor", visible: true, locked: false, floorHeight: 2.6, cutHeight: 1.0, baseElevation: 0, }, ], layers: [{ code: "35", name: "Dächer", color: "#8a4b32", lw: 0.25, visible: true, locked: false }], walls: [], doors: [], openings: [], ceilings: [], stairs: [], rooms: [], drawings2d: [], context: [], ...(roofs !== undefined ? { roofs } : {}), }; } const rectOutline: Roof["outline"] = [ { x: 0, y: 0 }, { x: 6, y: 0 }, { x: 6, y: 4 }, { x: 0, y: 4 }, ]; const roof = (id: string, shape: Roof["shape"], extra: Partial = {}): Roof => ({ id, type: "roof", floorId: "eg", categoryCode: "35", outline: rectOutline, shape, pitchDeg: 30, overhang: 0.5, ridgeAxis: "x", thickness: 0.3, ...extra, }); const visible = new Set(["35"]); /** Filtert die Grundriss-Primitive nach `kind:"line"` + gegebener Klasse. */ function linesOf(primitives: ReturnType["primitives"], cls: string) { return primitives.filter( (p): p is Extract => p.kind === "line" && p.cls === cls, ); } describe("generatePlan — Dächer (Dachaufsicht)", () => { it("Satteldach: Traufe + First, aber kein Grat/Knick", () => { const plan = generatePlan(baseProject([roof("R1", "sattel")]), "eg", visible); expect(linesOf(plan.primitives, "roof-eaves").length).toBeGreaterThan(0); expect(linesOf(plan.primitives, "roof-ridge").length).toBeGreaterThan(0); expect(linesOf(plan.primitives, "roof-hip").length).toBe(0); expect(linesOf(plan.primitives, "roof-break").length).toBe(0); }); it("Walmdach: Traufe + First + Grat", () => { const plan = generatePlan(baseProject([roof("R2", "walm")]), "eg", visible); expect(linesOf(plan.primitives, "roof-eaves").length).toBeGreaterThan(0); expect(linesOf(plan.primitives, "roof-ridge").length).toBeGreaterThan(0); expect(linesOf(plan.primitives, "roof-hip").length).toBeGreaterThan(0); }); it("Mansarddach: Knicklinien gestrichelt", () => { const plan = generatePlan(baseProject([roof("R3", "mansarde")]), "eg", visible); const breaks = linesOf(plan.primitives, "roof-break"); expect(breaks.length).toBeGreaterThan(0); for (const b of breaks) { expect(b.dash).toBeTruthy(); expect(b.dash?.length).toBeGreaterThan(0); } }); it("Flachdach: nur Traufe, weder First noch Grat noch Knick", () => { const plan = generatePlan(baseProject([roof("R4", "flach")]), "eg", visible); expect(linesOf(plan.primitives, "roof-eaves").length).toBeGreaterThan(0); expect(linesOf(plan.primitives, "roof-ridge").length).toBe(0); expect(linesOf(plan.primitives, "roof-hip").length).toBe(0); expect(linesOf(plan.primitives, "roof-break").length).toBe(0); }); it("First-Linie ist kräftiger als die Traufe (First > Traufe > Knick)", () => { const plan = generatePlan(baseProject([roof("R5", "mansarde")]), "eg", visible); const eaves = linesOf(plan.primitives, "roof-eaves")[0]; const ridge = linesOf(plan.primitives, "roof-ridge")[0]; const brk = linesOf(plan.primitives, "roof-break")[0]; expect(ridge.weightMm).toBeGreaterThan(eaves.weightMm); expect(eaves.weightMm).toBeGreaterThan(brk.weightMm); }); it("löst die Strichfarbe über roof.color, sonst die Kategorie-Farbe auf", () => { const withColor = generatePlan( baseProject([roof("R6", "sattel", { color: "#ff0000" })]), "eg", visible, ); expect(linesOf(withColor.primitives, "roof-eaves")[0].color).toBe("#ff0000"); const withoutColor = generatePlan(baseProject([roof("R7", "sattel")]), "eg", visible); expect(linesOf(withoutColor.primitives, "roof-eaves")[0].color).toBe("#8a4b32"); }); it("Rückwärtskompatibilität: ohne Dächer (bzw. Dach auf anderem Geschoss) bleiben die Primitive unverändert", () => { const planNoField = generatePlan(baseProject(undefined), "eg", visible); const planEmpty = generatePlan(baseProject([]), "eg", visible); const planOtherFloor = generatePlan( baseProject([roof("R8", "sattel", { floorId: "og" })]), "eg", visible, ); expect(planEmpty.primitives).toEqual(planNoField.primitives); expect(planOtherFloor.primitives).toEqual(planNoField.primitives); expect(planNoField.primitives.some((p) => p.kind === "line" && p.cls.startsWith("roof-"))).toBe( false, ); }); });