import assert from "node:assert/strict"; import { describe, it } from "node:test"; import { allColumns, buildCircuitEditPatch, buildDeviceRowEditPatch, formatValue, getBlockSortValue, getCellKind, getCircuitValue, getDeviceValue, parseNumeric, } from "../src/frontend/utils/circuit-grid-model.js"; import type { CircuitTreeCircuitDto, CircuitTreeDeviceRowDto } from "../src/frontend/types.js"; const device: CircuitTreeDeviceRowDto = { id: "row-1", sortOrder: 10, name: "Technical light", displayName: "Office light", phaseType: "single_phase", roomNumberSnapshot: "1.01", roomNameSnapshot: "Office", quantity: 2, powerPerUnit: 0.05, simultaneityFactor: 0.8, rowTotalPower: 0.08, remark: "Device remark", }; const circuit: CircuitTreeCircuitDto = { id: "circuit-1", circuitListId: "list-1", sectionId: "section-1", equipmentIdentifier: "-1F1", displayName: "Lighting circuit", sortOrder: 10, protectionType: "MCB", protectionRatedCurrent: 16, protectionCharacteristic: "B", cableType: "NYM-J", cableCrossSection: "1.5 mm²", cableLength: 20, voltage: 230, controlRequirement: "DALI", isReserve: false, remark: "Circuit remark", circuitTotalPower: 0.08, deviceRows: [device], }; describe("circuit grid model", () => { it("keeps the equipment identifier locked as the first column", () => { assert.equal(allColumns[0].key, "equipmentIdentifier"); assert.equal(allColumns[0].locked, true); }); it("maps shared fields to the correct level for every row shape", () => { assert.equal(getCellKind("circuitCompact", "displayName"), "deviceField"); assert.equal(getCellKind("circuitCompact", "equipmentIdentifier"), "circuitField"); assert.equal(getCellKind("circuitSummary", "displayName"), "circuitField"); assert.equal(getCellKind("deviceRow", "displayName"), "deviceField"); assert.equal(getCellKind("reserveCircuit", "remark"), "deviceField"); assert.equal(getCellKind("placeholder", "displayName"), "deviceField"); assert.equal(getCellKind("deviceRow", "equipmentIdentifier"), "readonly"); assert.equal(getCellKind("circuitSummary", "controlRequirement"), "circuitField"); assert.equal(getCellKind("deviceRow", "controlRequirement"), "readonly"); assert.equal(getCellKind("circuitCompact", "rowTotalPower"), "computed"); }); it("projects circuit and device values without mixing ownership", () => { assert.equal(getDeviceValue(device, "roomSummary"), "1.01 Office"); assert.equal(getDeviceValue(device, "rowTotalPower"), 0.08); assert.equal(getCircuitValue(circuit, "protectionSummary"), "MCB 16A B"); assert.equal(getCircuitValue(circuit, "voltage"), 230); assert.equal(getCircuitValue(circuit, "controlRequirement"), "DALI"); assert.equal(getCircuitValue(circuit, "cableSummary"), "NYM-J, 1.5 mm², 20 m"); }); it("formats displayed numeric values without changing their stored precision", () => { assert.equal(formatValue(0.123456, "powerPerUnit"), "0,123"); assert.equal(formatValue(0.87654, "simultaneityFactor"), "0,88"); assert.equal(formatValue(230.4, "voltage"), "230"); assert.equal(formatValue(16, "protectionRatedCurrent"), "16"); assert.equal(formatValue("single_phase", "phaseType"), "1-phasig"); assert.equal(formatValue("three_phase", "phaseType"), "3-phasig"); }); it("uses circuit display values for block sorting and falls back to the first device", () => { assert.equal(getBlockSortValue(circuit, "displayName"), "Lighting circuit"); assert.equal(getBlockSortValue({ ...circuit, displayName: undefined }, "displayName"), "Office light"); assert.equal(getBlockSortValue(circuit, "quantity"), 2); }); it("parses numeric drafts and rejects invalid values", () => { assert.equal(parseNumeric("quantity", " 2.5 "), 2.5); assert.equal(parseNumeric("quantity", ""), undefined); assert.throws(() => parseNumeric("quantity", "two"), /Ungültiger Zahlenwert/); }); it("builds nullable circuit command patches from grid drafts", () => { assert.deepEqual(buildCircuitEditPatch("voltage", ""), { voltage: null, }); assert.deepEqual( buildCircuitEditPatch("controlRequirement", " DALI "), { controlRequirement: "DALI" } ); assert.deepEqual(buildCircuitEditPatch("isReserve", "ja"), { isReserve: true, }); }); it("builds device command patches with explicit clearing semantics", () => { assert.deepEqual(buildDeviceRowEditPatch("roomSummary", ""), { roomNumberSnapshot: null, roomNameSnapshot: null, }); assert.deepEqual( buildDeviceRowEditPatch("roomSummary", "1.01 Büro"), { roomNumberSnapshot: "1.01", roomNameSnapshot: "Büro", } ); assert.deepEqual(buildDeviceRowEditPatch("technicalName", " Leuchte "), { name: "Leuchte", }); assert.throws( () => buildDeviceRowEditPatch("quantity", ""), /darf nicht leer sein/ ); }); });