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