Files
leistungsbilanz-ts/tests/project-version-history.test.ts
T

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 },
},
]);
});
});