The distribution-board search also matches board number and location as soon as the boards carry them (distribution-board info card of feature/print-view); the fields are optional in the filter, so the branch works with and without them. Deleting a project device now asks for confirmation like the other delete actions, since the row action is a small trash icon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
271 lines
7.9 KiB
TypeScript
271 lines
7.9 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { describe, it } from "node:test";
|
||
import {
|
||
buildCostGroupFilterOptions,
|
||
buildFloorFilterOptions,
|
||
buildSupplyTypeFilterOptions,
|
||
emptyDistributionBoardFilters,
|
||
emptyProjectDeviceFilters,
|
||
filterDistributionBoards,
|
||
filterProjectDevices,
|
||
isDistributionBoardFilterActive,
|
||
isProjectDeviceFilterActive,
|
||
retainAvailableFilterValues,
|
||
toggleFilterValue,
|
||
withoutCostGroupFilterId,
|
||
withoutFloorFilterId,
|
||
withoutSupplyTypeFilterId,
|
||
} from "../src/frontend/utils/overview-filters.js";
|
||
import type {
|
||
DistributionBoardDto,
|
||
FloorDto,
|
||
ProjectDeviceDto,
|
||
} from "../src/frontend/types.js";
|
||
|
||
type TestBoard = Pick<
|
||
DistributionBoardDto,
|
||
"id" | "name" | "floorId" | "supplyType"
|
||
>;
|
||
|
||
function board(
|
||
id: string,
|
||
name: string,
|
||
floorId: string | null,
|
||
supplyType: DistributionBoardDto["supplyType"]
|
||
): TestBoard {
|
||
return { id, name, floorId, supplyType };
|
||
}
|
||
|
||
function device(
|
||
id: string,
|
||
overrides: Partial<ProjectDeviceDto>
|
||
): ProjectDeviceDto {
|
||
return {
|
||
id,
|
||
projectId: "project-1",
|
||
name: `device-${id}`,
|
||
displayName: `Gerät ${id}`,
|
||
phaseType: "single_phase",
|
||
connectionKind: null,
|
||
costGroup: null,
|
||
category: "single_phase",
|
||
quantity: 1,
|
||
powerPerUnit: 1,
|
||
simultaneityFactor: 1,
|
||
totalPower: 1,
|
||
cosPhi: null,
|
||
remark: null,
|
||
voltageV: null,
|
||
...overrides,
|
||
};
|
||
}
|
||
|
||
const floors: FloorDto[] = [
|
||
{ id: "floor-og", projectId: "project-1", name: "1. OG", sortOrder: 2 },
|
||
{ id: "floor-eg", projectId: "project-1", name: "EG", sortOrder: 1 },
|
||
];
|
||
const floorNameById = new Map(floors.map((floor) => [floor.id, floor.name]));
|
||
|
||
const boards = [
|
||
board("b1", "UV-EG-01", "floor-eg", "AV"),
|
||
board("b2", "UV-OG-01", "floor-og", "SV"),
|
||
board("b3", "HV", null, "AV"),
|
||
board("b4", "SiBe-Zentrale", null, null),
|
||
];
|
||
|
||
describe("overview filters: distribution boards", () => {
|
||
it("returns every board without an active filter", () => {
|
||
assert.equal(isDistributionBoardFilterActive(emptyDistributionBoardFilters), false);
|
||
assert.deepEqual(
|
||
filterDistributionBoards(boards, emptyDistributionBoardFilters, floorNameById),
|
||
boards
|
||
);
|
||
});
|
||
|
||
it("also searches board number and location when the boards carry them", () => {
|
||
const withInfo = [
|
||
{ ...boards[0]!, boardNumber: "UV 0.1", location: "Raum 003 Technik" },
|
||
{ ...boards[1]!, boardNumber: null, location: null },
|
||
];
|
||
assert.deepEqual(
|
||
filterDistributionBoards(
|
||
withInfo,
|
||
{ ...emptyDistributionBoardFilters, query: "uv 0.1" },
|
||
floorNameById
|
||
).map((entry) => entry.id),
|
||
[withInfo[0]!.id]
|
||
);
|
||
assert.deepEqual(
|
||
filterDistributionBoards(
|
||
withInfo,
|
||
{ ...emptyDistributionBoardFilters, query: "technik" },
|
||
floorNameById
|
||
).map((entry) => entry.id),
|
||
[withInfo[0]!.id]
|
||
);
|
||
});
|
||
|
||
it("searches board and floor names case-insensitively and ignores blank queries", () => {
|
||
const byFloorName = filterDistributionBoards(
|
||
boards,
|
||
{ ...emptyDistributionBoardFilters, query: " og " },
|
||
floorNameById
|
||
);
|
||
assert.deepEqual(byFloorName.map((entry) => entry.id), ["b2"]);
|
||
const byBoardName = filterDistributionBoards(
|
||
boards,
|
||
{ ...emptyDistributionBoardFilters, query: "sibe" },
|
||
floorNameById
|
||
);
|
||
assert.deepEqual(byBoardName.map((entry) => entry.id), ["b4"]);
|
||
assert.equal(
|
||
isDistributionBoardFilterActive({ ...emptyDistributionBoardFilters, query: " " }),
|
||
false
|
||
);
|
||
});
|
||
|
||
it("filters floors with the dedicated option for boards without floor", () => {
|
||
const result = filterDistributionBoards(
|
||
boards,
|
||
{ ...emptyDistributionBoardFilters, floorIds: ["floor-eg", withoutFloorFilterId] },
|
||
floorNameById
|
||
);
|
||
assert.deepEqual(result.map((entry) => entry.id), ["b1", "b3", "b4"]);
|
||
});
|
||
|
||
it("combines supply type with the other criteria", () => {
|
||
const filters = {
|
||
query: "",
|
||
floorIds: [withoutFloorFilterId],
|
||
supplyTypes: ["AV"],
|
||
};
|
||
assert.equal(isDistributionBoardFilterActive(filters), true);
|
||
assert.deepEqual(
|
||
filterDistributionBoards(boards, filters, floorNameById).map((entry) => entry.id),
|
||
["b3"]
|
||
);
|
||
assert.deepEqual(
|
||
filterDistributionBoards(
|
||
boards,
|
||
{ ...emptyDistributionBoardFilters, supplyTypes: [withoutSupplyTypeFilterId] },
|
||
floorNameById
|
||
).map((entry) => entry.id),
|
||
["b4"]
|
||
);
|
||
});
|
||
|
||
it("offers floors in configured order followed by 'Ohne Etage'", () => {
|
||
assert.deepEqual(buildFloorFilterOptions(floors), [
|
||
{ id: "floor-eg", label: "EG" },
|
||
{ id: "floor-og", label: "1. OG" },
|
||
{ id: withoutFloorFilterId, label: "Ohne Etage" },
|
||
]);
|
||
});
|
||
|
||
it("offers enabled and used supply types in catalog order", () => {
|
||
const options = buildSupplyTypeFilterOptions(
|
||
["AV", "SV", "EV", "USV"],
|
||
["USV", "AV"],
|
||
boards,
|
||
{ AV: "AV – Allgemein", SV: "SV – Sicherheit" }
|
||
);
|
||
assert.deepEqual(options, [
|
||
{ id: "AV", label: "AV – Allgemein" },
|
||
{ id: "SV", label: "SV – Sicherheit" },
|
||
{ id: "USV", label: "USV" },
|
||
{ id: withoutSupplyTypeFilterId, label: "Nicht festgelegt" },
|
||
]);
|
||
});
|
||
});
|
||
|
||
describe("overview filters: project devices", () => {
|
||
const devices = [
|
||
device("d1", {
|
||
displayName: "Steckdose Büro",
|
||
costGroup: "444",
|
||
connectionKind: "Steckdose",
|
||
}),
|
||
device("d2", {
|
||
displayName: "Durchlauferhitzer",
|
||
phaseType: "three_phase",
|
||
category: "three_phase",
|
||
costGroup: " 445 ",
|
||
}),
|
||
device("d3", {
|
||
displayName: "Downlight",
|
||
category: "lighting",
|
||
costGroup: " ",
|
||
}),
|
||
device("d4", { displayName: "Kaffeemaschine", costGroup: null }),
|
||
];
|
||
|
||
it("searches names, connection kind, cost group and the German category label", () => {
|
||
assert.deepEqual(
|
||
filterProjectDevices(devices, { ...emptyProjectDeviceFilters, query: "STECK" }).map(
|
||
(entry) => entry.id
|
||
),
|
||
["d1"]
|
||
);
|
||
assert.deepEqual(
|
||
filterProjectDevices(
|
||
devices,
|
||
{ ...emptyProjectDeviceFilters, query: "beleucht" },
|
||
{ lighting: "Beleuchtung" }
|
||
).map((entry) => entry.id),
|
||
["d3"]
|
||
);
|
||
});
|
||
|
||
it("filters category, phase type and cost group together", () => {
|
||
const filters = {
|
||
query: "",
|
||
categories: ["single_phase", "three_phase"],
|
||
phaseTypes: ["three_phase"],
|
||
costGroups: ["445"],
|
||
};
|
||
assert.equal(isProjectDeviceFilterActive(filters), true);
|
||
assert.deepEqual(
|
||
filterProjectDevices(devices, filters).map((entry) => entry.id),
|
||
["d2"]
|
||
);
|
||
});
|
||
|
||
it("treats blank cost groups as 'Ohne Kostengruppe'", () => {
|
||
assert.deepEqual(
|
||
filterProjectDevices(devices, {
|
||
...emptyProjectDeviceFilters,
|
||
costGroups: [withoutCostGroupFilterId],
|
||
}).map((entry) => entry.id),
|
||
["d3", "d4"]
|
||
);
|
||
assert.deepEqual(buildCostGroupFilterOptions(devices), [
|
||
{ id: "444", label: "444" },
|
||
{ id: "445", label: "445" },
|
||
{ id: withoutCostGroupFilterId, label: "Ohne Kostengruppe" },
|
||
]);
|
||
});
|
||
|
||
it("returns an empty list when the filters hide every device", () => {
|
||
assert.deepEqual(
|
||
filterProjectDevices(devices, { ...emptyProjectDeviceFilters, query: "gibt es nicht" }),
|
||
[]
|
||
);
|
||
assert.equal(isProjectDeviceFilterActive(emptyProjectDeviceFilters), false);
|
||
});
|
||
});
|
||
|
||
describe("overview filters: selection helpers", () => {
|
||
it("toggles values without mutating the selection", () => {
|
||
const selected = ["a"];
|
||
assert.deepEqual(toggleFilterValue(selected, "b"), ["a", "b"]);
|
||
assert.deepEqual(toggleFilterValue(selected, "a"), []);
|
||
assert.deepEqual(selected, ["a"]);
|
||
});
|
||
|
||
it("drops selected values that are no longer offered", () => {
|
||
assert.deepEqual(
|
||
retainAvailableFilterValues(["floor-eg", "deleted-floor"], buildFloorFilterOptions(floors)),
|
||
["floor-eg"]
|
||
);
|
||
});
|
||
});
|