189 lines
5.6 KiB
TypeScript
189 lines
5.6 KiB
TypeScript
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 empty sections and their free placeholders when no filter is active", () => {
|
|
const emptySections: CircuitTreeSectionDto[] = [
|
|
{
|
|
id: "empty-lighting",
|
|
key: "lighting",
|
|
displayName: "Lighting",
|
|
prefix: "-1F",
|
|
sortOrder: 10,
|
|
circuits: [],
|
|
},
|
|
{
|
|
id: "empty-single-phase",
|
|
key: "single_phase",
|
|
displayName: "Single-phase circuits",
|
|
prefix: "-2F",
|
|
sortOrder: 20,
|
|
circuits: [],
|
|
},
|
|
];
|
|
|
|
const projectedSections = filterAndSortCircuitSections(emptySections, {}, null);
|
|
const rows = buildVisibleGridRows(projectedSections);
|
|
|
|
assert.equal(projectedSections.length, 2);
|
|
assert.deepEqual(
|
|
rows.map((row) => row.rowKey),
|
|
[
|
|
"section:empty-lighting",
|
|
"placeholder:empty-lighting",
|
|
"section:empty-single-phase",
|
|
"placeholder:empty-single-phase",
|
|
]
|
|
);
|
|
});
|
|
|
|
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-"
|
|
);
|
|
const circuitSummary = rows.find((row) => row.rowType === "circuitSummary");
|
|
assert.equal(
|
|
circuitSummary?.cells.find((cell) => cell.cellKey === "rowTotalPower")?.value,
|
|
multiDeviceCircuit.circuitTotalPower
|
|
);
|
|
assert.equal(
|
|
circuitSummary?.cells.some((cell) => cell.cellKey === "circuitTotalPower"),
|
|
false
|
|
);
|
|
});
|
|
|
|
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"));
|
|
});
|
|
});
|