250 lines
7.2 KiB
TypeScript
250 lines
7.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { ProjectVersionHistory } from "../src/frontend/components/project-version-history.js";
|
|
import { ProjectDeviceModal } from "../src/frontend/components/project-device-modal.js";
|
|
import { ProjectSettingsModal } from "../src/frontend/components/project-settings-modal.js";
|
|
import type { ProjectRevisionSummaryDto } from "../src/frontend/types.js";
|
|
import {
|
|
createNamedProjectSnapshot,
|
|
getProjectHistory,
|
|
listProjectRevisions,
|
|
listProjectSnapshots,
|
|
redoProjectCommand,
|
|
restoreProjectSnapshot,
|
|
undoProjectCommand,
|
|
} from "../src/frontend/utils/api.js";
|
|
import {
|
|
getProjectRevisionDescription,
|
|
getProjectRevisionSourceLabel,
|
|
getProjectSnapshotKindLabel,
|
|
mergeProjectRevisionPages,
|
|
} from "../src/frontend/utils/project-version-history.js";
|
|
|
|
function revision(
|
|
revisionNumber: number,
|
|
overrides: Partial<ProjectRevisionSummaryDto> = {}
|
|
): ProjectRevisionSummaryDto {
|
|
return {
|
|
revisionId: `revision-${revisionNumber}`,
|
|
changeSetId: `change-${revisionNumber}`,
|
|
revisionNumber,
|
|
createdAtIso: "2026-07-25T12:00:00.000Z",
|
|
actorId: null,
|
|
source: "user",
|
|
description: null,
|
|
commandType: "circuit.update",
|
|
payloadSchemaVersion: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("project version history presentation", () => {
|
|
it("renders a compact collapsed German entry point", () => {
|
|
const markup = renderToStaticMarkup(
|
|
createElement(ProjectVersionHistory, {
|
|
projectId: "project-1",
|
|
currentRevision: 42,
|
|
onProjectStateChange: () => undefined,
|
|
})
|
|
);
|
|
assert.match(markup, /Versionen und Sicherungspunkte/);
|
|
assert.match(markup, /Aktuelle Revision 42/);
|
|
assert.match(markup, /Rückgängig/);
|
|
assert.match(markup, /Wiederholen/);
|
|
assert.match(markup, /Anzeigen/);
|
|
assert.doesNotMatch(markup, /Stand speichern/);
|
|
});
|
|
|
|
it("uses German source and command descriptions", () => {
|
|
assert.equal(getProjectRevisionSourceLabel("user"), "Bearbeitung");
|
|
assert.equal(getProjectRevisionSourceLabel("undo"), "Rückgängig");
|
|
assert.equal(getProjectRevisionSourceLabel("redo"), "Wiederholt");
|
|
assert.equal(
|
|
getProjectRevisionSourceLabel("restore"),
|
|
"Wiederherstellung"
|
|
);
|
|
assert.equal(getProjectRevisionSourceLabel("migration"), "Migration");
|
|
assert.equal(
|
|
getProjectRevisionDescription(revision(1)),
|
|
"Stromkreis bearbeitet"
|
|
);
|
|
assert.equal(
|
|
getProjectRevisionDescription(
|
|
revision(2, { description: " Eigene Beschreibung " })
|
|
),
|
|
"Eigene Beschreibung"
|
|
);
|
|
assert.equal(
|
|
getProjectRevisionDescription(
|
|
revision(3, { commandType: "future.command" })
|
|
),
|
|
"future.command"
|
|
);
|
|
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
|
assert.equal(
|
|
getProjectSnapshotKindLabel("automatic"),
|
|
"Automatisch"
|
|
);
|
|
});
|
|
|
|
it("merges paginated revisions without duplicates in descending order", () => {
|
|
assert.deepEqual(
|
|
mergeProjectRevisionPages(
|
|
[revision(5), revision(4)],
|
|
[revision(4), revision(3)]
|
|
).map((entry) => entry.revisionNumber),
|
|
[5, 4, 3]
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("project settings presentation", () => {
|
|
it("shows all supply types and marks types already in use", () => {
|
|
const markup = renderToStaticMarkup(
|
|
createElement(ProjectSettingsModal, {
|
|
isSaving: false,
|
|
onClose: () => undefined,
|
|
onExport: async () => undefined,
|
|
onImport: async () => undefined,
|
|
onSave: async () => undefined,
|
|
project: {
|
|
id: "project-1",
|
|
name: "Projekt",
|
|
internalProjectNumber: null,
|
|
externalProjectNumber: null,
|
|
buildingOwner: null,
|
|
description: null,
|
|
singlePhaseVoltageV: 230,
|
|
threePhaseVoltageV: 400,
|
|
enabledDistributionBoardSupplyTypes: [
|
|
"AV",
|
|
"MSR",
|
|
"SiBe",
|
|
],
|
|
currentRevision: 0,
|
|
},
|
|
usedDistributionBoardSupplyTypes: ["SiBe"],
|
|
})
|
|
);
|
|
assert.match(markup, /MSR/);
|
|
assert.match(markup, /SiBe/);
|
|
assert.match(markup, /in Verwendung/);
|
|
});
|
|
});
|
|
|
|
describe("project device modal presentation", () => {
|
|
it("renders explicit German labels for every editable device field", () => {
|
|
const markup = renderToStaticMarkup(
|
|
createElement(ProjectDeviceModal, {
|
|
globalDevices: [],
|
|
isSaving: false,
|
|
onClose: () => undefined,
|
|
onImportGlobal: async () => undefined,
|
|
onSave: async () => undefined,
|
|
})
|
|
);
|
|
for (const label of [
|
|
"Projektgerät hinzufügen",
|
|
"Interner Name",
|
|
"Anzeigename",
|
|
"Kategorie",
|
|
"Anschlussart",
|
|
"Kostengruppe",
|
|
"Phasenart",
|
|
"Anzahl",
|
|
"Leistung je Stück [kW]",
|
|
"Gleichzeitigkeitsfaktor",
|
|
"Spannung [V]",
|
|
"Bemerkung",
|
|
]) {
|
|
assert.match(markup, new RegExp(label.replace("[", "\\[").replace("]", "\\]")));
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("project version history API", () => {
|
|
it("uses the revision-safe timeline and snapshot routes", async () => {
|
|
const requests: Array<{
|
|
url: string;
|
|
method: string;
|
|
body: unknown;
|
|
}> = [];
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async (input, init) => {
|
|
requests.push({
|
|
url: String(input),
|
|
method: init?.method ?? "GET",
|
|
body: init?.body ? JSON.parse(String(init.body)) : null,
|
|
});
|
|
return new Response(JSON.stringify({}), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
};
|
|
|
|
try {
|
|
await getProjectHistory("project-1");
|
|
await listProjectRevisions("project-1", {
|
|
limit: 10,
|
|
beforeRevision: 21,
|
|
});
|
|
await listProjectSnapshots("project-1");
|
|
await createNamedProjectSnapshot(
|
|
"project-1",
|
|
22,
|
|
"Planfreigabe",
|
|
"Vor Ausführung"
|
|
);
|
|
await restoreProjectSnapshot("project-1", "snapshot-1", 22);
|
|
await undoProjectCommand("project-1", 23);
|
|
await redoProjectCommand("project-1", 24);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
|
|
assert.deepEqual(requests, [
|
|
{
|
|
url: "/api/projects/project-1/history",
|
|
method: "GET",
|
|
body: null,
|
|
},
|
|
{
|
|
url: "/api/projects/project-1/history/revisions?limit=10&beforeRevision=21",
|
|
method: "GET",
|
|
body: null,
|
|
},
|
|
{
|
|
url: "/api/projects/project-1/snapshots",
|
|
method: "GET",
|
|
body: null,
|
|
},
|
|
{
|
|
url: "/api/projects/project-1/snapshots",
|
|
method: "POST",
|
|
body: {
|
|
expectedRevision: 22,
|
|
name: "Planfreigabe",
|
|
description: "Vor Ausführung",
|
|
},
|
|
},
|
|
{
|
|
url: "/api/projects/project-1/snapshots/snapshot-1/restore",
|
|
method: "POST",
|
|
body: { expectedRevision: 22 },
|
|
},
|
|
{
|
|
url: "/api/projects/project-1/history/undo",
|
|
method: "POST",
|
|
body: { expectedRevision: 23 },
|
|
},
|
|
{
|
|
url: "/api/projects/project-1/history/redo",
|
|
method: "POST",
|
|
body: { expectedRevision: 24 },
|
|
},
|
|
]);
|
|
});
|
|
});
|