import assert from "node:assert/strict"; import { describe, it } from "node:test"; import type { BuildingDto, FloorDto, RoomDto, } from "../src/frontend/types.js"; import { ALL_SELECTION, UNASSIGNED_SELECTION, countFloorsByBuilding, countRoomsByFloor, defaultBuildingIdForNewFloor, defaultFloorIdForNewRoom, describeRoomLocation, filterFloorsByBuilding, filterRooms, groupFloorsByBuilding, hasFloorsWithoutBuilding, hasRoomsWithoutFloor, normalizeBuildingSelection, normalizeFloorSelection, showsRoomsWithoutFloorEntry, summarizeRoomAreas, } from "../src/frontend/utils/building-structure.js"; function building(id: string, name: string, sortOrder: number): BuildingDto { return { id, projectId: "project-1", name, sortOrder }; } function floor( id: string, name: string, buildingId: string | null, sortOrder = 0 ): FloorDto { return { id, projectId: "project-1", name, sortOrder, buildingId }; } function room( id: string, floorId: string | null, roomNumber = id, areaM2: number | null = null ): RoomDto { return { id, projectId: "project-1", floorId, roomNumber, roomName: `Raum ${roomNumber}`, areaM2, }; } const buildings = [building("b-a", "Haus A", 0), building("b-b", "Haus B", 1)]; const floors = [ floor("f-free", "Außenanlage", null, 0), floor("f-a-eg", "EG", "b-a", 1), floor("f-b-eg", "EG", "b-b", 2), floor("f-a-og", "1. OG", "b-a", 3), floor("f-orphan", "Keller", "b-deleted", 4), ]; const rooms = [ room("r-a-eg", "f-a-eg"), room("r-a-og", "f-a-og"), room("r-b-eg", "f-b-eg"), room("r-free", "f-free"), room("r-orphan-floor", "f-orphan"), room("r-none", null), room("r-missing-floor", "f-deleted"), ]; const ids = (entries: ReadonlyArray<{ id: string }>) => entries.map((entry) => entry.id); describe("building structure view", () => { it("groups floors in building order and puts unassigned floors last", () => { const groups = groupFloorsByBuilding(floors, buildings); assert.deepEqual( groups.map((group) => ({ buildingId: group.buildingId, label: group.label, floors: ids(group.floors), })), [ { buildingId: "b-a", label: "Haus A", floors: ["f-a-eg", "f-a-og"] }, { buildingId: "b-b", label: "Haus B", floors: ["f-b-eg"] }, { buildingId: null, label: "Ohne Gebäude", floors: ["f-free", "f-orphan"], }, ] ); }); it("omits empty groups", () => { const groups = groupFloorsByBuilding( [floor("f-1", "EG", "b-b")], buildings ); assert.deepEqual( groups.map((group) => group.buildingId), ["b-b"] ); assert.deepEqual(groupFloorsByBuilding([], buildings), []); }); it("filters floors by building selection", () => { assert.deepEqual( ids(filterFloorsByBuilding(floors, buildings, ALL_SELECTION)), ids(floors) ); assert.deepEqual( ids(filterFloorsByBuilding(floors, buildings, "b-a")), ["f-a-eg", "f-a-og"] ); assert.deepEqual( ids(filterFloorsByBuilding(floors, buildings, UNASSIGNED_SELECTION)), ["f-free", "f-orphan"] ); }); it("detects floors without building and rooms without floor", () => { assert.equal(hasFloorsWithoutBuilding(floors, buildings), true); assert.equal( hasFloorsWithoutBuilding([floor("f-1", "EG", "b-a")], buildings), false ); assert.equal(hasRoomsWithoutFloor(rooms, floors), true); assert.equal(hasRoomsWithoutFloor([room("r-1", "f-a-eg")], floors), false); }); it("filters rooms for building and floor selections", () => { assert.deepEqual( ids(filterRooms(rooms, floors, buildings, ALL_SELECTION, ALL_SELECTION)), ids(rooms) ); assert.deepEqual( ids(filterRooms(rooms, floors, buildings, "b-a", ALL_SELECTION)), ["r-a-eg", "r-a-og"] ); assert.deepEqual( ids(filterRooms(rooms, floors, buildings, "b-a", "f-a-og")), ["r-a-og"] ); assert.deepEqual( ids(filterRooms(rooms, floors, buildings, ALL_SELECTION, "f-b-eg")), ["r-b-eg"] ); assert.deepEqual( ids( filterRooms( rooms, floors, buildings, UNASSIGNED_SELECTION, ALL_SELECTION ) ), ["r-free", "r-orphan-floor", "r-none", "r-missing-floor"] ); assert.deepEqual( ids( filterRooms( rooms, floors, buildings, ALL_SELECTION, UNASSIGNED_SELECTION ) ), ["r-none", "r-missing-floor"] ); assert.deepEqual( ids(filterRooms(rooms, floors, buildings, "b-a", UNASSIGNED_SELECTION)), [] ); }); it("offers 'Ohne Etage' only where rooms without floor can belong", () => { assert.equal(showsRoomsWithoutFloorEntry(rooms, floors, ALL_SELECTION), true); assert.equal( showsRoomsWithoutFloorEntry(rooms, floors, UNASSIGNED_SELECTION), true ); assert.equal(showsRoomsWithoutFloorEntry(rooms, floors, "b-a"), false); assert.equal( showsRoomsWithoutFloorEntry([room("r-1", "f-a-eg")], floors, ALL_SELECTION), false ); }); it("falls back to 'Alle' for vanished or hidden selections", () => { assert.equal(normalizeBuildingSelection("b-a", floors, buildings), "b-a"); assert.equal( normalizeBuildingSelection("b-deleted", floors, buildings), ALL_SELECTION ); assert.equal( normalizeBuildingSelection(UNASSIGNED_SELECTION, floors, buildings), UNASSIGNED_SELECTION ); assert.equal( normalizeBuildingSelection( UNASSIGNED_SELECTION, [floor("f-1", "EG", "b-a")], buildings ), ALL_SELECTION ); assert.equal( normalizeFloorSelection("f-a-og", rooms, floors, buildings, "b-a"), "f-a-og" ); assert.equal( normalizeFloorSelection("f-b-eg", rooms, floors, buildings, "b-a"), ALL_SELECTION ); assert.equal( normalizeFloorSelection("f-deleted", rooms, floors, buildings, ALL_SELECTION), ALL_SELECTION ); assert.equal( normalizeFloorSelection( UNASSIGNED_SELECTION, rooms, floors, buildings, ALL_SELECTION ), UNASSIGNED_SELECTION ); assert.equal( normalizeFloorSelection(UNASSIGNED_SELECTION, rooms, floors, buildings, "b-a"), ALL_SELECTION ); }); it("preselects the selected building and floor for new entries", () => { assert.equal(defaultBuildingIdForNewFloor("b-a"), "b-a"); assert.equal(defaultBuildingIdForNewFloor(ALL_SELECTION), ""); assert.equal(defaultBuildingIdForNewFloor(UNASSIGNED_SELECTION), ""); assert.equal(defaultFloorIdForNewRoom("f-a-eg"), "f-a-eg"); assert.equal(defaultFloorIdForNewRoom(ALL_SELECTION), ""); assert.equal(defaultFloorIdForNewRoom(UNASSIGNED_SELECTION), ""); }); it("counts floors per building and rooms per floor", () => { assert.deepEqual( [...countFloorsByBuilding(floors, buildings)], [ [null, 2], ["b-a", 2], ["b-b", 1], ] ); assert.deepEqual( [...countRoomsByFloor(rooms, floors)], [ ["f-a-eg", 1], ["f-a-og", 1], ["f-b-eg", 1], ["f-free", 1], ["f-orphan", 1], [null, 2], ] ); }); it("sums known areas and counts missing areas per floor and building", () => { const areaRooms = [ room("a", "f-a-eg", "a", 20.25), room("b", "f-a-eg"), room("c", "f-free", "c", 5.5), room("d", null, "d", 3), room("e", "f-deleted"), ]; const summaries = summarizeRoomAreas(areaRooms, floors, buildings); assert.deepEqual(summaries.total, { areaM2: 28.75, roomCount: 5, missingAreaCount: 2, }); assert.deepEqual(summaries.byFloor.get("f-a-eg"), { areaM2: 20.25, roomCount: 2, missingAreaCount: 1, }); assert.deepEqual(summaries.byFloor.get(null), { areaM2: 3, roomCount: 2, missingAreaCount: 1, }); assert.deepEqual(summaries.byBuilding.get("b-a"), { areaM2: 20.25, roomCount: 2, missingAreaCount: 1, }); assert.deepEqual(summaries.byBuilding.get(null), { areaM2: 8.5, roomCount: 3, missingAreaCount: 1, }); }); it("describes a room location with floor and building", () => { assert.equal( describeRoomLocation(room("r", "f-a-og"), floors, buildings), "1. OG · Haus A" ); assert.equal( describeRoomLocation(room("r", "f-free"), floors, buildings), "Außenanlage" ); assert.equal( describeRoomLocation(room("r", "f-orphan"), floors, buildings), "Keller" ); assert.equal( describeRoomLocation(room("r", null), floors, buildings), "Ohne Etage" ); }); });