Extract circuit grid projection

This commit is contained in:
2026-07-22 20:46:32 +02:00
parent 009152b406
commit 6c45c97ffd
5 changed files with 343 additions and 171 deletions
+144
View File
@@ -0,0 +1,144 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import {
buildVisibleGridRows,
filterAndSortCircuitSections,
getDistinctFilterValues,
} from "../src/frontend/utils/circuit-grid-projection.js";
import type {
CircuitTreeCircuitDto,
CircuitTreeDeviceRowDto,
CircuitTreeSectionDto,
} from "../src/frontend/types.js";
function makeDevice(
id: string,
displayName: string,
roomNumberSnapshot: string,
sortOrder: number
): CircuitTreeDeviceRowDto {
return {
id,
sortOrder,
name: displayName,
displayName,
roomNumberSnapshot,
quantity: 1,
powerPerUnit: 0.1,
simultaneityFactor: 1,
rowTotalPower: 0.1,
};
}
function makeCircuit(
id: string,
equipmentIdentifier: string,
displayName: string,
deviceRows: CircuitTreeDeviceRowDto[],
sortOrder: number
): CircuitTreeCircuitDto {
return {
id,
circuitListId: "list-1",
sectionId: "section-1",
equipmentIdentifier,
displayName,
sortOrder,
isReserve: deviceRows.length === 0,
circuitTotalPower: deviceRows.reduce((total, row) => total + row.rowTotalPower, 0),
deviceRows,
};
}
const multiDeviceCircuit = makeCircuit(
"circuit-multi",
"-1F2",
"Office lighting",
[makeDevice("device-office", "Office light", "1.01", 10), makeDevice("device-store", "Store light", "1.02", 20)],
20
);
const compactCircuit = makeCircuit(
"circuit-compact",
"-1F1",
"Corridor lighting",
[makeDevice("device-corridor", "Corridor light", "1.03", 10)],
10
);
const reserveCircuit = makeCircuit("circuit-reserve", "-1F3", "Reserve", [], 30);
const sections: CircuitTreeSectionDto[] = [
{
id: "section-1",
key: "lighting",
displayName: "Lighting",
prefix: "-1F",
sortOrder: 10,
circuits: [compactCircuit, multiDeviceCircuit, reserveCircuit],
},
];
describe("circuit grid projection", () => {
it("keeps every device row in a circuit block when one device matches a filter", () => {
const result = filterAndSortCircuitSections(sections, { roomNumberSnapshot: ["1.01"] }, null);
assert.deepEqual(result.map((section) => section.circuits.map((circuit) => circuit.id)), [["circuit-multi"]]);
assert.deepEqual(result[0].circuits[0].deviceRows.map((row) => row.id), ["device-office", "device-store"]);
});
it("combines column filters across the complete circuit block", () => {
const result = filterAndSortCircuitSections(
sections,
{ equipmentIdentifier: ["-1F2"], roomNumberSnapshot: ["1.02"] },
null
);
assert.deepEqual(result[0].circuits.map((circuit) => circuit.id), ["circuit-multi"]);
});
it("treats missing device values as missing without matching populated circuits", () => {
const result = filterAndSortCircuitSections(sections, { roomNumberSnapshot: ["-"] }, null);
assert.deepEqual(result[0].circuits.map((circuit) => circuit.id), ["circuit-reserve"]);
});
it("sorts complete circuit blocks without changing device row order", () => {
const result = filterAndSortCircuitSections(sections, {}, { key: "equipmentIdentifier", direction: "desc" });
assert.deepEqual(result[0].circuits.map((circuit) => circuit.id), [
"circuit-reserve",
"circuit-multi",
"circuit-compact",
]);
assert.deepEqual(result[0].circuits[1].deviceRows.map((row) => row.id), ["device-office", "device-store"]);
});
it("builds the compact, grouped, reserve and placeholder row shapes", () => {
const rows = buildVisibleGridRows(sections);
assert.deepEqual(rows.map((row) => row.rowType), [
"section",
"circuitCompact",
"circuitSummary",
"deviceRow",
"deviceRow",
"reserveCircuit",
"placeholder",
]);
assert.equal(rows.at(-1)?.rowKey, "placeholder:section-1");
assert.equal(
rows.at(-1)?.cells.find((cell) => cell.cellKey === "equipmentIdentifier")?.value,
"-frei-"
);
});
it("collects filter options from both circuit and device values", () => {
const values = getDistinctFilterValues(sections, [
{ key: "displayName", label: "Display name" },
{ key: "roomNumberSnapshot", label: "Room number" },
]);
assert.ok(values.displayName.includes("Office lighting"));
assert.ok(values.displayName.includes("Store light"));
assert.ok(values.roomNumberSnapshot.includes("1.03"));
});
});