import { describe, expect, test } from "bun:test"; import { createInitialAppState } from "@editor/initial-state"; import { generationStartJobCommand, generationSucceedJobCommand } from "./generation"; import { createAppStore } from "@editor/store"; import { documentAddArtboardCommand } from "./document"; import { historyCommands } from "./history"; import { commandIds } from "./ids"; import { createCommandRegistry } from "./registry"; import { transformCommands } from "./transform"; const registry = createCommandRegistry([documentAddArtboardCommand, generationStartJobCommand, generationSucceedJobCommand, ...historyCommands, ...transformCommands]); describe("history commands", () => { test("records document changes and undoes/redoes them", () => { const store = createAppStore(createInitialAppState("Test"), registry); store.dispatch(commandIds.documentAddArtboard, { id: "a1", name: "Artboard", bounds: { x: 0, y: 0, w: 100, h: 100 } }); expect(store.getState().document.artboards.map((artboard) => artboard.id)).toEqual(["a1"]); expect(store.getState().history.past).toHaveLength(1); store.dispatch(commandIds.historyUndo, undefined); expect(store.getState().document.artboards).toEqual([]); expect(store.getState().history.future).toHaveLength(1); store.dispatch(commandIds.historyRedo, undefined); expect(store.getState().document.artboards.map((artboard) => artboard.id)).toEqual(["a1"]); }); test("records one history entry for a transform drag", () => { const store = createAppStore(artboardState(), registry); store.dispatch(commandIds.transformBegin, { target: { type: "artboard", id: "a1" }, handle: "body", point: { x: 0, y: 0 }, initialBounds: { x: 0, y: 0, w: 100, h: 80 }, }); store.dispatch(commandIds.transformUpdate, { point: { x: 5, y: 10 } }); store.dispatch(commandIds.transformUpdate, { point: { x: 10, y: 20 } }); store.dispatch(commandIds.transformUpdate, { point: { x: 15, y: 25 } }); expect(store.getState().document.artboards[0]?.bounds).toEqual({ x: 15, y: 25, w: 100, h: 80 }); expect(store.getState().history.past).toHaveLength(0); store.dispatch(commandIds.transformEnd, undefined); expect(store.getState().editor.transformSession).toBeUndefined(); expect(store.getState().history.past).toHaveLength(1); expect(store.getState().history.past[0]?.document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 100, h: 80 }); expect(store.getState().history.past[0]?.editor.transformSession).toBeUndefined(); store.dispatch(commandIds.historyUndo, undefined); expect(store.getState().document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 100, h: 80 }); expect(store.getState().editor.transformSession).toBeUndefined(); expect(store.getState().history.future).toHaveLength(1); }); test("keeps direct transform bounds edits normally undoable", () => { const store = createAppStore(artboardState(), registry); store.dispatch(commandIds.transformSetBounds, { target: { type: "artboard", id: "a1" }, bounds: { x: 12, y: 24, w: 120, h: 90 } }); expect(store.getState().document.artboards[0]?.bounds).toEqual({ x: 12, y: 24, w: 120, h: 90 }); expect(store.getState().history.past).toHaveLength(1); store.dispatch(commandIds.historyUndo, undefined); expect(store.getState().document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 100, h: 80 }); }); test("does not record history for transform update or end without a session", () => { const store = createAppStore(artboardState(), registry); store.dispatch(commandIds.transformUpdate, { point: { x: 10, y: 20 } }); store.dispatch(commandIds.transformEnd, undefined); expect(store.getState().document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 100, h: 80 }); expect(store.getState().history.past).toHaveLength(0); expect(store.getState().history.future).toHaveLength(0); }); test("does not record history for a transform session with no document update", () => { const store = createAppStore(artboardState(), registry); store.dispatch(commandIds.transformBegin, { target: { type: "artboard", id: "a1" }, handle: "body", point: { x: 0, y: 0 }, initialBounds: { x: 0, y: 0, w: 100, h: 80 }, }); store.dispatch(commandIds.transformEnd, undefined); expect(store.getState().editor.transformSession).toBeUndefined(); expect(store.getState().document.artboards[0]?.bounds).toEqual({ x: 0, y: 0, w: 100, h: 80 }); expect(store.getState().history.past).toHaveLength(0); }); test("does not rewind generation job lifecycle during document undo", () => { const store = createAppStore(createInitialAppState("Test"), registry); store.dispatch(commandIds.documentAddArtboard, { id: "a1", name: "Artboard", bounds: { x: 0, y: 0, w: 100, h: 100 } }); store.dispatch(commandIds.generationStartJob, { jobId: "job-1", kind: "generate", label: "Generating", startedAt: 100 }); store.dispatch(commandIds.generationSucceedJob, { jobId: "job-1", finishedAt: 150 }); store.dispatch(commandIds.historyUndo, undefined); expect(store.getState().editor.generation.jobs[0]?.status).toBe("succeeded"); }); }); function artboardState() { return documentAddArtboardCommand.execute( { state: createInitialAppState("Test") }, { id: "a1", name: "Artboard", bounds: { x: 0, y: 0, w: 100, h: 80 } }, ); }