import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { createRevitProjectDevicePrefill, RevitCsvImportPlan, RevitCsvModal, } from "../src/frontend/components/revit-csv-modal.js"; import { ProjectDeviceModal } from "../src/frontend/components/project-device-modal.js"; import { RoomModal } from "../src/frontend/components/room-modal.js"; import { applyExternalInitialImport, getExternalCsvConfiguration, getExternalCircuitListObjects, planExternalInitialImport, previewExternalCsv, updateExternalCsvConfiguration, } from "../src/frontend/utils/api.js"; import type { ExternalInitialImportPlanDto } from "../src/frontend/types.js"; import { createDefaultExternalCsvConfiguration } from "../src/external-model/csv/external-csv-contracts.js"; import { externalCsvTestColumns } from "./fixtures/revit-csv-fixtures.js"; describe("Revit CSV frontend API", () => { it("uses the project-scoped configuration, preview and import endpoints", async () => { const requests: Array<{ url: string; method: string; body: unknown }> = []; const originalFetch = globalThis.fetch; globalThis.fetch = async (input, init) => { requests.push({ url: String(input), method: init?.method ?? "GET", body: init?.body ? JSON.parse(String(init.body)) : null, }); return new Response(JSON.stringify({ configuration: null }), { status: 200, headers: { "Content-Type": "application/json" }, }); }; const configuration = createDefaultExternalCsvConfiguration(externalCsvTestColumns); try { await getExternalCsvConfiguration("project-1"); await getExternalCircuitListObjects("project-1", "list-1"); await updateExternalCsvConfiguration("project-1", 7, configuration); await previewExternalCsv("project-1", "revit.csv", "YWJj"); await planExternalInitialImport("project-1", "revit.csv", "YWJj"); await applyExternalInitialImport("project-1", { expectedRevision: 8, expectedConfigurationVersion: 2, expectedSha256: "sha", fileName: "revit.csv", contentBase64: "YWJj", sourceName: "Revit-Gesamtmodell", roomDecisions: [{ sourceRoomKey: "101|büro", roomId: "room-1", defaultDistributionBoardId: "board-1" }], familyProjectDeviceDecisions: [{ familyAndType: "Steckdose : Standard", projectDeviceId: "device-1" }], }); } finally { globalThis.fetch = originalFetch; } assert.deepEqual(requests, [ { url: "/api/projects/project-1/external-csv/configuration", method: "GET", body: null, }, { url: "/api/projects/project-1/circuit-lists/list-1/external-objects", method: "GET", body: null, }, { url: "/api/projects/project-1/external-csv/configuration", method: "PUT", body: { expectedRevision: 7, configuration }, }, { url: "/api/projects/project-1/external-csv/preview", method: "POST", body: { fileName: "revit.csv", contentBase64: "YWJj" }, }, { url: "/api/projects/project-1/external-csv/initial-import/plan", method: "POST", body: { fileName: "revit.csv", contentBase64: "YWJj" }, }, { url: "/api/projects/project-1/external-csv/initial-import/apply", method: "POST", body: { expectedRevision: 8, expectedConfigurationVersion: 2, expectedSha256: "sha", fileName: "revit.csv", contentBase64: "YWJj", sourceName: "Revit-Gesamtmodell", roomDecisions: [{ sourceRoomKey: "101|büro", roomId: "room-1", defaultDistributionBoardId: "board-1" }], familyProjectDeviceDecisions: [{ familyAndType: "Steckdose : Standard", projectDeviceId: "device-1" }], }, }, ]); }); }); describe("Revit CSV modal presentation", () => { it("uses the viewport-wide dialog variant", () => { const markup = renderToStaticMarkup( createElement(RevitCsvModal, { currentRevision: 0, onClose: () => undefined, onCreateProjectDevice: async () => ({} as never), onCreateRoom: async () => ({} as never), onRevisionChange: () => undefined, projectId: "project-1", }) ); assert.match(markup, /max-width:calc\(100vw - 2rem\)/); assert.doesNotMatch(markup, /modal-lg/); }); it("renders the initial import decisions without individual object rows", () => { const plan: ExternalInitialImportPlanDto = { configurationVersion: 2, fileName: "revit.csv", sha256: "sha", byteLength: 100, rowCount: 2, objectCount: 1, passthroughCount: 1, suspectObjectCount: 0, suspectRowNumbers: [], sourceRooms: [{ sourceRoomKey: "101|büro", roomNumber: "101", roomName: "Büro", objectCount: 1, suggestedRoomId: "room-1", matchStatus: "exact-number", candidateRoomIds: ["room-1"], }], familyGroups: [{ familyAndType: "Steckdose : Standard", objectCount: 1, classified: true, category: "single_phase", connectionKind: null, internalDeviceType: "socket", }], issueCounts: { unknownFamilyAndType: 0, invalidPower: 0, invalidQuantity: 0, missingRoom: 0 }, objects: [], existing: { floors: [{ id: "floor-1", name: "EG", sortOrder: 0 }], rooms: [{ id: "room-1", floorId: "floor-1", roomNumber: "101", roomName: "Büro" }], distributionBoards: [{ id: "board-1", name: "UV-01", floorId: "floor-1" }], projectDevices: [{ id: "device-1", name: "Steckdose", displayName: "Steckdose", category: "single_phase", connectionKind: null, powerPerUnit: 100 }], }, }; const markup = renderToStaticMarkup(createElement(RevitCsvImportPlan, { familyLinks: { "Steckdose : Standard": "device-1" }, isApplying: false, onApply: () => undefined, onCreateProjectDevice: () => undefined, onCreateRoom: () => undefined, onFamilyLinkChange: () => undefined, onRoomDecisionChange: () => undefined, plan, roomDecisions: { "101|büro": { roomId: "room-1", defaultDistributionBoardId: "board-1" } }, setSourceName: () => undefined, sourceName: "Revit-Gesamtmodell", })); assert.match(markup, /Erstimport vorbereiten/); assert.match(markup, /101 · Büro/); assert.match(markup, /Steckdose : Standard/); assert.match(markup, /Erstimport übernehmen/); assert.match(markup, /Raum 101 neu anlegen/); assert.match(markup, /ProjectDevice für Steckdose : Standard neu anlegen/); assert.doesNotMatch(markup, /IFC-GUID/); }); it("prefills the existing room and ProjectDevice forms from Revit decisions", () => { const roomMarkup = renderToStaticMarkup(createElement(RoomModal, { floors: [{ id: "floor-1", projectId: "project-1", name: "EG", sortOrder: 0 }], initialValues: { roomNumber: "101", roomName: "Büro" }, isSaving: false, onClose: () => undefined, onSave: async () => undefined, })); const deviceMarkup = renderToStaticMarkup(createElement(ProjectDeviceModal, { globalDevices: [], initialValues: { name: "socket", displayName: "Steckdose", category: "single_phase", quantity: 2, powerPerUnit: 0.1, simultaneityFactor: 1, }, isSaving: false, onClose: () => undefined, onImportGlobal: async () => undefined, onSave: async () => undefined, })); assert.match(roomMarkup, /value="101"/); assert.match(roomMarkup, /value="Büro"/); assert.match(deviceMarkup, /value="socket"/); assert.match(deviceMarkup, /value="Steckdose"/); assert.match(deviceMarkup, /value="2"/); }); it("converts mapped Revit watts into ProjectDevice kilowatts", () => { const plan = { familyGroups: [{ familyAndType: "Steckdose : Standard", internalDeviceType: "socket", category: "single_phase", connectionKind: "230 V", }], objects: [{ sourceValues: { familyAndType: "Steckdose : Standard" }, suggestedPlanningValues: { displayName: "Arbeitsplatzsteckdose", effectiveQuantity: 4, powerPerUnitW: 250, simultaneityFactor: 0.8, cosPhi: 0.95, costGroup: "440", remark: "Aus Revit", }, }], } as ExternalInitialImportPlanDto; assert.deepEqual( createRevitProjectDevicePrefill(plan, "Steckdose : Standard"), { name: "socket", displayName: "Arbeitsplatzsteckdose", connectionKind: "230 V", category: "single_phase", quantity: 4, powerPerUnit: 0.25, simultaneityFactor: 0.8, cosPhi: 0.95, costGroup: "440", remark: "Aus Revit", } ); }); });