Files
leistungsbilanz-ts/tests/revit-csv-frontend-api.test.ts

252 lines
8.9 KiB
TypeScript

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",
}
);
});
});