Describe snapshot source revisions
This commit is contained in:
@@ -149,8 +149,14 @@ Upgrade-only-Verknüpfungen und Migrationsnachweise bleiben erhalten, obwohl sie
|
|||||||
nicht Bestandteil des logischen Snapshots sind.
|
nicht Bestandteil des logischen Snapshots sind.
|
||||||
Die Projektseite bindet diese APIs in einem einklappbaren Bereich
|
Die Projektseite bindet diese APIs in einem einklappbaren Bereich
|
||||||
„Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte
|
„Versionen und Sicherungspunkte“ ein. Dort können Benutzer Sicherungspunkte
|
||||||
benennen, nach expliziter Bestätigung wiederherstellen und die paginierte
|
benennen, jeden aufgeführten benannten oder automatischen Stand nach
|
||||||
Revisions-Timeline mit deutschen Quellen- und Änderungsbezeichnungen lesen.
|
expliziter Bestätigung wiederherstellen und die paginierte Revisions-Timeline
|
||||||
|
mit deutschen Quellen- und Änderungsbezeichnungen lesen. Die Snapshot-Liste
|
||||||
|
ordnet jede positive `sourceRevision` serverseitig ihren unveränderlichen
|
||||||
|
Revisionsmetadaten zu. Dadurch zeigt auch ein älterer automatischer Stand die
|
||||||
|
auslösende Änderung, ohne eine zweite frei formulierte Beschreibung zu
|
||||||
|
speichern oder von den zuletzt paginiert geladenen Timeline-Einträgen
|
||||||
|
abzuhängen. Revision null wird als Projektstart dargestellt.
|
||||||
Projektweites Rückgängig/Wiederholen bleibt im Kopf dieses Bereichs auch im
|
Projektweites Rückgängig/Wiederholen bleibt im Kopf dieses Bereichs auch im
|
||||||
eingeklappten Zustand erreichbar. Die Verfügbarkeit stammt direkt aus den
|
eingeklappten Zustand erreichbar. Die Verfügbarkeit stammt direkt aus den
|
||||||
persistierten Server-Stacks; nach einer Historienaktion oder einem Restore lädt
|
persistierten Server-Stacks; nach einer Historienaktion oder einem Restore lädt
|
||||||
|
|||||||
@@ -10,53 +10,53 @@ requirements and intended sequencing, not proof of implementation.
|
|||||||
|
|
||||||
## Project History
|
## Project History
|
||||||
|
|
||||||
- Every listed logical snapshot, including automatic snapshots, must be
|
- [x] Every listed logical snapshot, including automatic snapshots, must be
|
||||||
explicitly restorable from the project history UI.
|
explicitly restorable from the project history UI.
|
||||||
- Show which project change produced a snapshot. Prefer immutable revision
|
- [x] Show which project change produced a snapshot. Prefer immutable revision
|
||||||
metadata over storing a second free-form description of the same change.
|
metadata over storing a second free-form description of the same change.
|
||||||
- Keep restoration auditable as a new project revision and preserve persistent
|
- [x] Keep restoration auditable as a new project revision and preserve persistent
|
||||||
undo/redo.
|
undo/redo.
|
||||||
|
|
||||||
## Distribution Boards
|
## Distribution Boards
|
||||||
|
|
||||||
- Assign an optional project floor to a distribution board.
|
- [x] Assign an optional project floor to a distribution board.
|
||||||
- Store a supply classification for each distribution board:
|
- [x] Store a supply classification for each distribution board:
|
||||||
- `AV` (Allgemeine Stromversorgung)
|
- `AV` (Allgemeine Stromversorgung)
|
||||||
- `SV` (Sicherheitsstromversorgung)
|
- `SV` (Sicherheitsstromversorgung)
|
||||||
- `EV` (Ersatzstromversorgung)
|
- `EV` (Ersatzstromversorgung)
|
||||||
- `USV` (Unterbrechungsfreie Stromversorgung)
|
- `USV` (Unterbrechungsfreie Stromversorgung)
|
||||||
- `MSR` (Mess-, Steuerungs- und Regelungstechnik)
|
- `MSR` (Mess-, Steuerungs- und Regelungstechnik)
|
||||||
- `SiBe` (Sicherheitsbeleuchtung)
|
- `SiBe` (Sicherheitsbeleuchtung)
|
||||||
- Let project settings select which catalog supply types are used in a
|
- [x] Let project settings select which catalog supply types are used in a
|
||||||
project. Distribution-board dialogs offer only that selection; a type in
|
project. Distribution-board dialogs offer only that selection; a type in
|
||||||
use cannot be disabled.
|
use cannot be disabled.
|
||||||
- Include both fields in project snapshots and portable project
|
- [x] Include both fields in project snapshots and portable project
|
||||||
export/import.
|
export/import.
|
||||||
- Persist changes through project commands so they remain undoable after a
|
- [x] Persist changes through project commands so they remain undoable after a
|
||||||
restart.
|
restart.
|
||||||
|
|
||||||
## Device Voltage Derivation
|
## Device Voltage Derivation
|
||||||
|
|
||||||
- Derive voltage from the selected phase type and the project settings:
|
- [x] Derive voltage from the selected phase type and the project settings:
|
||||||
- single-phase uses the project's single-phase voltage;
|
- single-phase uses the project's single-phase voltage;
|
||||||
- three-phase uses the project's three-phase voltage.
|
- three-phase uses the project's three-phase voltage.
|
||||||
- Voltage is not editable on global devices, project devices or circuits.
|
- [x] Voltage is not editable on global devices, project devices or circuits.
|
||||||
Global devices store only their phase; copying one into a project derives
|
Global devices store only their phase; copying one into a project derives
|
||||||
the target project's voltage.
|
the target project's voltage.
|
||||||
- Project-device voltage follows its phase. Circuit voltage follows the
|
- [x] Project-device voltage follows its phase. Circuit voltage follows the
|
||||||
section phase; an unassigned circuit is three-phase only when all assigned
|
section phase; an unassigned circuit is three-phase only when all assigned
|
||||||
device rows with a valid phase are three-phase.
|
device rows with a valid phase are three-phase.
|
||||||
- Changing project voltage settings updates all project devices and circuits
|
- [x] Changing project voltage settings updates all project devices and circuits
|
||||||
atomically in the same persistent Undo/Redo step.
|
atomically in the same persistent Undo/Redo step.
|
||||||
- Database migration `0019` and snapshot schema version `5` normalize older
|
- [x] Database migration `0019` and snapshot schema version `5` normalize older
|
||||||
stored values. Version-four and older imports remain supported and are
|
stored values. Version-four and older imports remain supported and are
|
||||||
normalized while being upgraded.
|
normalized while being upgraded.
|
||||||
|
|
||||||
## Recommended Sequence
|
## Recommended Sequence
|
||||||
|
|
||||||
1. Distribution-board floor and supply fields.
|
1. [x] Distribution-board floor and supply fields.
|
||||||
2. Device voltage derivation without overrides.
|
2. [x] Device voltage derivation without overrides.
|
||||||
3. Snapshot-to-revision descriptions and history presentation.
|
3. [x] Snapshot-to-revision descriptions and history presentation.
|
||||||
|
|
||||||
The Revit/CSV/IFCGUID round-trip remains a separate jointly planned phase and
|
The Revit/CSV/IFCGUID round-trip remains a separate jointly planned phase and
|
||||||
must not be inferred from these tasks.
|
must not be inferred from these tasks.
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { and, asc, desc, eq, lt } from "drizzle-orm";
|
import { and, asc, desc, eq, inArray, lt } from "drizzle-orm";
|
||||||
import { deserializeProjectCommand } from "../../domain/models/project-command.model.js";
|
import { deserializeProjectCommand } from "../../domain/models/project-command.model.js";
|
||||||
import type {
|
import type {
|
||||||
ProjectHistoryCommand,
|
ProjectHistoryCommand,
|
||||||
ProjectHistoryDirection,
|
ProjectHistoryDirection,
|
||||||
ListProjectRevisionsInput,
|
ListProjectRevisionsInput,
|
||||||
ProjectRevisionPage,
|
ProjectRevisionPage,
|
||||||
|
ProjectRevisionSummary,
|
||||||
ProjectHistoryState,
|
ProjectHistoryState,
|
||||||
ProjectHistoryStore,
|
ProjectHistoryStore,
|
||||||
} from "../../domain/ports/project-history.store.js";
|
} from "../../domain/ports/project-history.store.js";
|
||||||
@@ -118,6 +119,64 @@ export class ProjectHistoryRepository implements ProjectHistoryStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listRevisionsByNumbers(
|
||||||
|
projectId: string,
|
||||||
|
revisionNumbers: number[]
|
||||||
|
): ProjectRevisionSummary[] {
|
||||||
|
for (const revisionNumber of revisionNumbers) {
|
||||||
|
if (
|
||||||
|
!Number.isSafeInteger(revisionNumber) ||
|
||||||
|
revisionNumber < 0
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Project revision numbers must be non-negative integers."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const normalizedRevisionNumbers = [
|
||||||
|
...new Set(revisionNumbers),
|
||||||
|
].filter((revisionNumber) => revisionNumber > 0);
|
||||||
|
if (!normalizedRevisionNumbers.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.database
|
||||||
|
.select({
|
||||||
|
revisionId: projectRevisions.id,
|
||||||
|
changeSetId: projectChangeSets.id,
|
||||||
|
revisionNumber: projectRevisions.revisionNumber,
|
||||||
|
createdAtIso: projectRevisions.createdAtIso,
|
||||||
|
actorId: projectRevisions.actorId,
|
||||||
|
source: projectRevisions.source,
|
||||||
|
description: projectRevisions.description,
|
||||||
|
commandType: projectChangeSets.commandType,
|
||||||
|
payloadSchemaVersion: projectChangeSets.payloadSchemaVersion,
|
||||||
|
})
|
||||||
|
.from(projectRevisions)
|
||||||
|
.innerJoin(
|
||||||
|
projectChangeSets,
|
||||||
|
eq(
|
||||||
|
projectChangeSets.projectRevisionId,
|
||||||
|
projectRevisions.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(projectRevisions.projectId, projectId),
|
||||||
|
inArray(
|
||||||
|
projectRevisions.revisionNumber,
|
||||||
|
normalizedRevisionNumbers
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(desc(projectRevisions.revisionNumber))
|
||||||
|
.all()
|
||||||
|
.map((row) => ({
|
||||||
|
...row,
|
||||||
|
source: row.source as ProjectRevisionSource,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
getNextCommand(
|
getNextCommand(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
direction: ProjectHistoryDirection
|
direction: ProjectHistoryDirection
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export interface ProjectHistoryStore {
|
|||||||
listRevisions(
|
listRevisions(
|
||||||
input: ListProjectRevisionsInput
|
input: ListProjectRevisionsInput
|
||||||
): ProjectRevisionPage | null;
|
): ProjectRevisionPage | null;
|
||||||
|
listRevisionsByNumbers(
|
||||||
|
projectId: string,
|
||||||
|
revisionNumbers: number[]
|
||||||
|
): ProjectRevisionSummary[];
|
||||||
getNextCommand(
|
getNextCommand(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
direction: ProjectHistoryDirection
|
direction: ProjectHistoryDirection
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
getProjectRevisionDescription,
|
getProjectRevisionDescription,
|
||||||
getProjectRevisionSourceLabel,
|
getProjectRevisionSourceLabel,
|
||||||
getProjectSnapshotKindLabel,
|
getProjectSnapshotKindLabel,
|
||||||
|
getProjectSnapshotRevisionDescription,
|
||||||
mergeProjectRevisionPages,
|
mergeProjectRevisionPages,
|
||||||
} from "../utils/project-version-history";
|
} from "../utils/project-version-history";
|
||||||
|
|
||||||
@@ -343,7 +344,7 @@ export function ProjectVersionHistory({
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Projektstand</th>
|
<th>Gesicherter Projektstand</th>
|
||||||
<th>Erstellt</th>
|
<th>Erstellt</th>
|
||||||
<th className="text-end">Aktion</th>
|
<th className="text-end">Aktion</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -368,7 +369,16 @@ export function ProjectVersionHistory({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</td>
|
</td>
|
||||||
<td>Revision {snapshot.sourceRevision}</td>
|
<td>
|
||||||
|
<strong>
|
||||||
|
{snapshot.sourceRevision === 0
|
||||||
|
? "Ausgangsstand"
|
||||||
|
: `Revision ${snapshot.sourceRevision}`}
|
||||||
|
</strong>
|
||||||
|
<div className="small text-secondary">
|
||||||
|
{getProjectSnapshotRevisionDescription(snapshot)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>{formatDateTime(snapshot.createdAtIso)}</td>
|
<td>{formatDateTime(snapshot.createdAtIso)}</td>
|
||||||
<td className="text-end">
|
<td className="text-end">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface ProjectSnapshotMetadataDto {
|
|||||||
id: string;
|
id: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
sourceRevision: number;
|
sourceRevision: number;
|
||||||
|
sourceRevisionMetadata: ProjectRevisionSummaryDto | null;
|
||||||
schemaVersion: number;
|
schemaVersion: number;
|
||||||
kind: "named" | "automatic";
|
kind: "named" | "automatic";
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -49,8 +49,15 @@ export function getProjectRevisionSourceLabel(
|
|||||||
export function getProjectRevisionDescription(
|
export function getProjectRevisionDescription(
|
||||||
revision: ProjectRevisionSummaryDto
|
revision: ProjectRevisionSummaryDto
|
||||||
) {
|
) {
|
||||||
|
const description = revision.description?.trim();
|
||||||
|
if (
|
||||||
|
description === `Undo ${revision.commandType}` ||
|
||||||
|
description === `Redo ${revision.commandType}`
|
||||||
|
) {
|
||||||
|
return commandTypeLabels[revision.commandType] || revision.commandType;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
revision.description?.trim() ||
|
description ||
|
||||||
commandTypeLabels[revision.commandType] ||
|
commandTypeLabels[revision.commandType] ||
|
||||||
revision.commandType
|
revision.commandType
|
||||||
);
|
);
|
||||||
@@ -62,6 +69,26 @@ export function getProjectSnapshotKindLabel(
|
|||||||
return kind === "automatic" ? "Automatisch" : "Benannt";
|
return kind === "automatic" ? "Automatisch" : "Benannt";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProjectSnapshotRevisionDescription(
|
||||||
|
snapshot: ProjectSnapshotMetadataDto
|
||||||
|
) {
|
||||||
|
if (snapshot.sourceRevision === 0) {
|
||||||
|
return "Projektstart";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!snapshot.sourceRevisionMetadata ||
|
||||||
|
snapshot.sourceRevisionMetadata.revisionNumber !==
|
||||||
|
snapshot.sourceRevision
|
||||||
|
) {
|
||||||
|
return "Änderungsdetails nicht verfügbar";
|
||||||
|
}
|
||||||
|
return `${getProjectRevisionSourceLabel(
|
||||||
|
snapshot.sourceRevisionMetadata.source
|
||||||
|
)}: ${getProjectRevisionDescription(
|
||||||
|
snapshot.sourceRevisionMetadata
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function mergeProjectRevisionPages(
|
export function mergeProjectRevisionPages(
|
||||||
current: ProjectRevisionSummaryDto[],
|
current: ProjectRevisionSummaryDto[],
|
||||||
next: ProjectRevisionSummaryDto[]
|
next: ProjectRevisionSummaryDto[]
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import {
|
|||||||
createNamedProjectSnapshotSchema,
|
createNamedProjectSnapshotSchema,
|
||||||
restoreProjectSnapshotSchema,
|
restoreProjectSnapshotSchema,
|
||||||
} from "../../shared/validation/project-snapshot.schemas.js";
|
} from "../../shared/validation/project-snapshot.schemas.js";
|
||||||
import { projectCommandService } from "../composition/project-command-stores.js";
|
import {
|
||||||
|
projectCommandService,
|
||||||
|
projectHistoryStore,
|
||||||
|
} from "../composition/project-command-stores.js";
|
||||||
import { projectSnapshotStore } from "../composition/project-snapshot-store.js";
|
import { projectSnapshotStore } from "../composition/project-snapshot-store.js";
|
||||||
|
|
||||||
export function listProjectSnapshots(req: Request, res: Response) {
|
export function listProjectSnapshots(req: Request, res: Response) {
|
||||||
@@ -18,7 +21,7 @@ export function listProjectSnapshots(req: Request, res: Response) {
|
|||||||
if (!snapshots) {
|
if (!snapshots) {
|
||||||
return res.status(404).json({ error: "Project not found" });
|
return res.status(404).json({ error: "Project not found" });
|
||||||
}
|
}
|
||||||
return res.json(snapshots);
|
return res.json(withSourceRevisionMetadata(projectId, snapshots));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createNamedProjectSnapshot(
|
export function createNamedProjectSnapshot(
|
||||||
@@ -43,7 +46,9 @@ export function createNamedProjectSnapshot(
|
|||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
return res.status(404).json({ error: "Project not found" });
|
return res.status(404).json({ error: "Project not found" });
|
||||||
}
|
}
|
||||||
return res.status(201).json(snapshot);
|
return res
|
||||||
|
.status(201)
|
||||||
|
.json(withSourceRevisionMetadata(projectId, [snapshot])[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ProjectRevisionConflictError) {
|
if (error instanceof ProjectRevisionConflictError) {
|
||||||
return res.status(409).json({
|
return res.status(409).json({
|
||||||
@@ -97,7 +102,9 @@ export function restoreProjectSnapshot(
|
|||||||
command: prepared.command,
|
command: prepared.command,
|
||||||
});
|
});
|
||||||
return res.json({
|
return res.json({
|
||||||
snapshot: prepared.snapshot,
|
snapshot: withSourceRevisionMetadata(projectId, [
|
||||||
|
prepared.snapshot,
|
||||||
|
])[0],
|
||||||
revision: result.revision,
|
revision: result.revision,
|
||||||
history: result.history,
|
history: result.history,
|
||||||
});
|
});
|
||||||
@@ -120,6 +127,29 @@ export function restoreProjectSnapshot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function withSourceRevisionMetadata<
|
||||||
|
TSnapshot extends { sourceRevision: number },
|
||||||
|
>(
|
||||||
|
projectId: string,
|
||||||
|
snapshots: TSnapshot[]
|
||||||
|
) {
|
||||||
|
const revisions = projectHistoryStore.listRevisionsByNumbers(
|
||||||
|
projectId,
|
||||||
|
snapshots.map((snapshot) => snapshot.sourceRevision)
|
||||||
|
);
|
||||||
|
const revisionByNumber = new Map(
|
||||||
|
revisions.map((revision) => [
|
||||||
|
revision.revisionNumber,
|
||||||
|
revision,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
return snapshots.map((snapshot) => ({
|
||||||
|
...snapshot,
|
||||||
|
sourceRevisionMetadata:
|
||||||
|
revisionByNumber.get(snapshot.sourceRevision) ?? null,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function getProjectId(req: Request, res: Response) {
|
function getProjectId(req: Request, res: Response) {
|
||||||
const { projectId } = req.params;
|
const { projectId } = req.params;
|
||||||
if (typeof projectId !== "string" || !projectId.trim()) {
|
if (typeof projectId !== "string" || !projectId.trim()) {
|
||||||
|
|||||||
@@ -177,6 +177,33 @@ describe("project history repository", () => {
|
|||||||
nextBeforeRevision: null,
|
nextBeforeRevision: null,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
history
|
||||||
|
.listRevisionsByNumbers("project-1", [3, 1, 3, 99])
|
||||||
|
.map((revision) => ({
|
||||||
|
revisionNumber: revision.revisionNumber,
|
||||||
|
description: revision.description,
|
||||||
|
})),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
revisionNumber: 3,
|
||||||
|
description: "Anzahl zurücknehmen",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
revisionNumber: 1,
|
||||||
|
description: "Stromkreis umbenennen",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
history.listRevisionsByNumbers("project-1", [0]),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
history.listRevisionsByNumbers("project-1", [-1]),
|
||||||
|
/non-negative integers/
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { renderToStaticMarkup } from "react-dom/server";
|
|||||||
import { ProjectVersionHistory } from "../src/frontend/components/project-version-history.js";
|
import { ProjectVersionHistory } from "../src/frontend/components/project-version-history.js";
|
||||||
import { ProjectDeviceModal } from "../src/frontend/components/project-device-modal.js";
|
import { ProjectDeviceModal } from "../src/frontend/components/project-device-modal.js";
|
||||||
import { ProjectSettingsModal } from "../src/frontend/components/project-settings-modal.js";
|
import { ProjectSettingsModal } from "../src/frontend/components/project-settings-modal.js";
|
||||||
import type { ProjectRevisionSummaryDto } from "../src/frontend/types.js";
|
import type {
|
||||||
|
ProjectRevisionSummaryDto,
|
||||||
|
ProjectSnapshotMetadataDto,
|
||||||
|
} from "../src/frontend/types.js";
|
||||||
import {
|
import {
|
||||||
createNamedProjectSnapshot,
|
createNamedProjectSnapshot,
|
||||||
getProjectHistory,
|
getProjectHistory,
|
||||||
@@ -19,6 +22,7 @@ import {
|
|||||||
getProjectRevisionDescription,
|
getProjectRevisionDescription,
|
||||||
getProjectRevisionSourceLabel,
|
getProjectRevisionSourceLabel,
|
||||||
getProjectSnapshotKindLabel,
|
getProjectSnapshotKindLabel,
|
||||||
|
getProjectSnapshotRevisionDescription,
|
||||||
mergeProjectRevisionPages,
|
mergeProjectRevisionPages,
|
||||||
} from "../src/frontend/utils/project-version-history.js";
|
} from "../src/frontend/utils/project-version-history.js";
|
||||||
|
|
||||||
@@ -40,6 +44,25 @@ function revision(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function snapshot(
|
||||||
|
sourceRevision: number,
|
||||||
|
sourceRevisionMetadata: ProjectRevisionSummaryDto | null
|
||||||
|
): ProjectSnapshotMetadataDto {
|
||||||
|
return {
|
||||||
|
id: `snapshot-${sourceRevision}`,
|
||||||
|
projectId: "project-1",
|
||||||
|
sourceRevision,
|
||||||
|
sourceRevisionMetadata,
|
||||||
|
schemaVersion: 5,
|
||||||
|
kind: "automatic",
|
||||||
|
name: `Revision ${sourceRevision}`,
|
||||||
|
description: null,
|
||||||
|
payloadSha256: "checksum",
|
||||||
|
createdAtIso: "2026-07-25T12:00:00.000Z",
|
||||||
|
createdByActorId: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("project version history presentation", () => {
|
describe("project version history presentation", () => {
|
||||||
it("renders a compact collapsed German entry point", () => {
|
it("renders a compact collapsed German entry point", () => {
|
||||||
const markup = renderToStaticMarkup(
|
const markup = renderToStaticMarkup(
|
||||||
@@ -82,6 +105,16 @@ describe("project version history presentation", () => {
|
|||||||
),
|
),
|
||||||
"future.command"
|
"future.command"
|
||||||
);
|
);
|
||||||
|
assert.equal(
|
||||||
|
getProjectRevisionDescription(
|
||||||
|
revision(4, {
|
||||||
|
source: "undo",
|
||||||
|
description: "Undo project.update-settings",
|
||||||
|
commandType: "project.update-settings",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
"Projekteinstellungen bearbeitet"
|
||||||
|
);
|
||||||
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
assert.equal(getProjectSnapshotKindLabel("named"), "Benannt");
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getProjectSnapshotKindLabel("automatic"),
|
getProjectSnapshotKindLabel("automatic"),
|
||||||
@@ -98,6 +131,28 @@ describe("project version history presentation", () => {
|
|||||||
[5, 4, 3]
|
[5, 4, 3]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("describes the immutable source revision of a snapshot", () => {
|
||||||
|
assert.equal(
|
||||||
|
getProjectSnapshotRevisionDescription(
|
||||||
|
snapshot(
|
||||||
|
25,
|
||||||
|
revision(25, {
|
||||||
|
description: "Verteilung UV-01 angelegt",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"Bearbeitung: Verteilung UV-01 angelegt"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
getProjectSnapshotRevisionDescription(snapshot(0, null)),
|
||||||
|
"Projektstart"
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
getProjectSnapshotRevisionDescription(snapshot(25, null)),
|
||||||
|
"Änderungsdetails nicht verfügbar"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("project settings presentation", () => {
|
describe("project settings presentation", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user