Files
image-studio/commands/history.test.ts
syntaxbullet 5e4b548ad4 feat: add ComfyUI integration for image generation and management
- Implemented ComfyGenerateRequest type and associated functions for generating images using various architectures and modes.
- Added functions for listing generation options and handling image uploads.
- Created workflows for different generation modes including SDXL, Z-Image, Z-Image Turbo, and Anima.
- Introduced GenerationJobStatus component to display the status of ongoing generation jobs.
- Developed MaskControls for managing mask operations and displaying mask analysis.
- Created palette items for tool selection, layer management, and generation settings.
2026-07-10 23:15:02 +02:00

120 lines
5.3 KiB
TypeScript

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