Files
leistungsbilanz-ts/tests/circuit-grid-model.test.ts
T

197 lines
6.6 KiB
TypeScript

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
allColumns,
buildCircuitEditPatch,
buildDeviceRowEditPatch,
formatValue,
getCircuitSectionLabel,
getProjectColumnLayoutStorageKey,
isGridEditorControlTarget,
getBlockSortValue,
getCellKind,
getCircuitValue,
getDeviceValue,
parseStoredColumnLayout,
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("does not route clicks from active editor controls back to the grid cell", () => {
const inputTarget = {
closest: (selector: string) =>
selector === "input, select, textarea" ? {} : null,
};
const cellTarget = { closest: () => null };
assert.equal(isGridEditorControlTarget(inputTarget), true);
assert.equal(isGridEditorControlTarget(cellTarget), false);
assert.equal(isGridEditorControlTarget(null), false);
});
it("keeps the equipment identifier locked as the first column", () => {
assert.equal(allColumns[0].key, "equipmentIdentifier");
assert.equal(allColumns[0].label, "SK-Nr.");
assert.equal(
allColumns[0].fullLabel,
"Stromkreisnummer / Betriebsmittelkennzeichen"
);
assert.equal(allColumns[0].locked, true);
});
it("stores valid column layouts under a project-specific key", () => {
assert.equal(
getProjectColumnLayoutStorageKey("project-1"),
"circuitTreeEditor.columnLayout.v2.project-1"
);
const layout = parseStoredColumnLayout(
JSON.stringify({
order: ["displayName", "equipmentIdentifier"],
visible: [
"displayName",
"equipmentIdentifier",
"unknown",
],
})
);
assert.ok(layout);
assert.equal(layout.order[0], "equipmentIdentifier");
assert.deepEqual(layout.visible, [
"displayName",
"equipmentIdentifier",
]);
assert.equal(parseStoredColumnLayout("{invalid"), null);
});
it("shows stable circuit sections with German labels", () => {
assert.equal(
getCircuitSectionLabel({
key: "lighting",
displayName: "Lighting",
}),
"Licht"
);
assert.equal(
getCircuitSectionLabel({
key: "custom",
displayName: "Sonderbereich",
}),
"Sonderbereich"
);
});
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/
);
});
});