mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-26 23:43:40 +00:00
406 lines
13 KiB
TypeScript
406 lines
13 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { useWorkspaceStore } from "../../stores/workspaceStore";
|
|
import { useTimelineStore } from "../../stores/timelineStore";
|
|
import { useGraphStore } from "../../stores/graphStore";
|
|
import { resetWorkspaceStore } from "../../test-utils/test-helpers";
|
|
import type { LabelConfig } from "../../types";
|
|
|
|
describe("Tangible Cascade Cleanup Integration Tests", () => {
|
|
let documentId: string;
|
|
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
resetWorkspaceStore();
|
|
documentId = useWorkspaceStore.getState().createDocument("Test Doc");
|
|
});
|
|
|
|
describe("Label deletion cascade", () => {
|
|
it("should remove deleted label from tangible filterLabels", () => {
|
|
const { addLabelToDocument, deleteLabelFromDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
// Add labels
|
|
const label1: LabelConfig = {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
};
|
|
const label2: LabelConfig = {
|
|
id: "label-2",
|
|
name: "Label 2",
|
|
color: "#111",
|
|
appliesTo: "both",
|
|
};
|
|
|
|
addLabelToDocument(documentId, label1);
|
|
addLabelToDocument(documentId, label2);
|
|
|
|
// Add tangible with both labels
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "Filter Tangible",
|
|
mode: "filter",
|
|
filterLabels: ["label-1", "label-2"],
|
|
});
|
|
|
|
// Delete label-1
|
|
deleteLabelFromDocument(documentId, "label-1");
|
|
|
|
// Check tangible only has label-2
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
const tangible = doc?.tangibles?.[0];
|
|
|
|
expect(tangible?.filterLabels).toEqual(["label-2"]);
|
|
});
|
|
|
|
it("should not delete tangible when label is removed from filterLabels", () => {
|
|
const { addLabelToDocument, deleteLabelFromDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "Filter Tangible",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
});
|
|
|
|
deleteLabelFromDocument(documentId, "label-1");
|
|
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(1);
|
|
expect(doc?.tangibles?.[0].filterLabels).toEqual([]);
|
|
});
|
|
|
|
it("should not affect state mode tangibles when label is deleted", () => {
|
|
const { addLabelToDocument, deleteLabelFromDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
const timelineStore = useTimelineStore.getState();
|
|
const stateId = timelineStore.createState("Test State", undefined, false);
|
|
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "State Tangible",
|
|
mode: "state",
|
|
stateId: stateId,
|
|
});
|
|
|
|
deleteLabelFromDocument(documentId, "label-1");
|
|
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(1);
|
|
expect(doc?.tangibles?.[0].mode).toBe("state");
|
|
expect(doc?.tangibles?.[0].stateId).toBe(stateId);
|
|
});
|
|
});
|
|
|
|
describe("State deletion cascade", () => {
|
|
it("should delete tangible when referenced state is deleted", () => {
|
|
const timelineStore = useTimelineStore.getState();
|
|
|
|
// Create a new state
|
|
const stateId = timelineStore.createState("Test State", undefined, false);
|
|
|
|
// Add tangible referencing this state
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "State Tangible",
|
|
mode: "state",
|
|
stateId: stateId,
|
|
});
|
|
|
|
// Verify tangible exists
|
|
let doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(1);
|
|
|
|
// Switch to root state (can't delete current state)
|
|
const timeline = timelineStore.timelines.get(documentId);
|
|
timelineStore.switchToState(timeline!.rootStateId);
|
|
|
|
// Delete the state
|
|
timelineStore.deleteState(stateId);
|
|
|
|
// Tangible should be deleted
|
|
doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(0);
|
|
});
|
|
|
|
it("should delete stateDial tangibles when state is deleted", () => {
|
|
const timelineStore = useTimelineStore.getState();
|
|
|
|
const stateId = timelineStore.createState("Dial State", undefined, false);
|
|
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "Dial Tangible",
|
|
mode: "stateDial",
|
|
stateId: stateId,
|
|
});
|
|
|
|
const timeline = timelineStore.timelines.get(documentId);
|
|
timelineStore.switchToState(timeline!.rootStateId);
|
|
timelineStore.deleteState(stateId);
|
|
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(0);
|
|
});
|
|
|
|
it("should not delete filter mode tangibles when state is deleted", () => {
|
|
const timelineStore = useTimelineStore.getState();
|
|
|
|
const stateId = timelineStore.createState("Test State", undefined, false);
|
|
|
|
const { addLabelToDocument, addTangibleToDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "Filter Tangible",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
});
|
|
|
|
const timeline = timelineStore.timelines.get(documentId);
|
|
timelineStore.switchToState(timeline!.rootStateId);
|
|
timelineStore.deleteState(stateId);
|
|
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(1);
|
|
});
|
|
|
|
it("should handle multiple tangibles referencing same state", () => {
|
|
const timelineStore = useTimelineStore.getState();
|
|
|
|
const stateId = timelineStore.createState(
|
|
"Shared State",
|
|
undefined,
|
|
false,
|
|
);
|
|
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
addTangibleToDocument(documentId, {
|
|
name: "State Tangible 1",
|
|
mode: "state",
|
|
stateId: stateId,
|
|
id: "", // Placeholder, will be auto-generated
|
|
});
|
|
addTangibleToDocument(documentId, {
|
|
name: "State Tangible 2",
|
|
mode: "state",
|
|
stateId: stateId,
|
|
id: "", // Placeholder, will be auto-generated
|
|
});
|
|
addTangibleToDocument(documentId, {
|
|
name: "Different State",
|
|
mode: "state",
|
|
stateId: "other-state",
|
|
id: "", // Placeholder, will be auto-generated
|
|
});
|
|
|
|
const timeline = timelineStore.timelines.get(documentId);
|
|
timelineStore.switchToState(timeline!.rootStateId);
|
|
timelineStore.deleteState(stateId);
|
|
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
// Only tangible with ID 'different-state' (auto-generated from name) should remain
|
|
expect(doc?.tangibles).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("Document loading and persistence", () => {
|
|
it("should sync tangibles to graphStore when added to active document", () => {
|
|
const { addTangibleToDocument, addLabelToDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
// Add label first (required for filter mode)
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
// Add tangibles to the active document
|
|
addTangibleToDocument(documentId, {
|
|
name: "Test Tangible 1",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
hardwareId: "token-001",
|
|
id: "",
|
|
});
|
|
addTangibleToDocument(documentId, {
|
|
name: "Test Tangible 2",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
hardwareId: "token-002",
|
|
id: "",
|
|
});
|
|
|
|
// Tangibles should be in graphStore (synced by addTangibleToDocument)
|
|
const graphTangibles = useGraphStore.getState().tangibles;
|
|
expect(graphTangibles).toHaveLength(2);
|
|
expect(graphTangibles[0].name).toBe("Test Tangible 1");
|
|
expect(graphTangibles[1].name).toBe("Test Tangible 2");
|
|
});
|
|
|
|
it("should persist tangibles in document storage", () => {
|
|
const { addTangibleToDocument, addLabelToDocument, saveDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
// Add label first (required for filter mode)
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
// Add tangible
|
|
addTangibleToDocument(documentId, {
|
|
name: "Persistent Tangible",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
hardwareId: "token-persistent",
|
|
id: "",
|
|
});
|
|
|
|
// Save document (though addTangibleToDocument already saves to storage)
|
|
saveDocument(documentId);
|
|
|
|
// Verify tangible is persisted in document
|
|
const doc = useWorkspaceStore.getState().documents.get(documentId);
|
|
expect(doc?.tangibles).toHaveLength(1);
|
|
expect(doc?.tangibles?.[0].hardwareId).toBe("token-persistent");
|
|
expect(doc?.tangibles?.[0].name).toBe("Persistent Tangible");
|
|
|
|
// Verify tangible is also in graphStore (synced by addTangibleToDocument)
|
|
const graphTangibles = useGraphStore.getState().tangibles;
|
|
expect(graphTangibles).toHaveLength(1);
|
|
expect(graphTangibles[0].hardwareId).toBe("token-persistent");
|
|
});
|
|
|
|
it("should mark document as dirty when tangible is added", () => {
|
|
const { addTangibleToDocument, addLabelToDocument } =
|
|
useWorkspaceStore.getState();
|
|
|
|
// Add label first
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
// Clear dirty flag set by label addition
|
|
const metadata = useWorkspaceStore
|
|
.getState()
|
|
.documentMetadata.get(documentId);
|
|
if (metadata) {
|
|
metadata.isDirty = false;
|
|
}
|
|
|
|
// Add tangible
|
|
addTangibleToDocument(documentId, {
|
|
name: "Test Tangible",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
id: "",
|
|
});
|
|
|
|
// Document should be marked as dirty
|
|
const updatedMetadata = useWorkspaceStore
|
|
.getState()
|
|
.documentMetadata.get(documentId);
|
|
expect(updatedMetadata?.isDirty).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Cross-store synchronization", () => {
|
|
it("should sync tangibles to graphStore when label is deleted", () => {
|
|
const {
|
|
addLabelToDocument,
|
|
deleteLabelFromDocument,
|
|
addTangibleToDocument,
|
|
} = useWorkspaceStore.getState();
|
|
|
|
addLabelToDocument(documentId, {
|
|
id: "label-1",
|
|
name: "Label 1",
|
|
color: "#000",
|
|
appliesTo: "both",
|
|
});
|
|
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "Filter Tangible",
|
|
mode: "filter",
|
|
filterLabels: ["label-1"],
|
|
});
|
|
|
|
// Before deletion
|
|
let graphState = useGraphStore.getState();
|
|
expect(graphState.tangibles[0].filterLabels).toEqual(["label-1"]);
|
|
|
|
// Delete label
|
|
deleteLabelFromDocument(documentId, "label-1");
|
|
|
|
// After deletion - graphStore should be synced
|
|
graphState = useGraphStore.getState();
|
|
expect(graphState.tangibles[0].filterLabels).toEqual([]);
|
|
});
|
|
|
|
it("should remove tangibles from graphStore when state is deleted", () => {
|
|
const timelineStore = useTimelineStore.getState();
|
|
const { addTangibleToDocument } = useWorkspaceStore.getState();
|
|
|
|
const stateId = timelineStore.createState("Test State", undefined, false);
|
|
|
|
addTangibleToDocument(documentId, {
|
|
id: "tangible-1",
|
|
name: "State Tangible",
|
|
mode: "state",
|
|
stateId: stateId,
|
|
});
|
|
|
|
// Before deletion
|
|
let graphState = useGraphStore.getState();
|
|
expect(graphState.tangibles).toHaveLength(1);
|
|
|
|
// Delete state
|
|
const timeline = timelineStore.timelines.get(documentId);
|
|
timelineStore.switchToState(timeline!.rootStateId);
|
|
timelineStore.deleteState(stateId);
|
|
|
|
// After deletion - graphStore should be synced
|
|
graphState = useGraphStore.getState();
|
|
expect(graphState.tangibles).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|