import { describe, expect, test } from "bun:test"; import { createInitialAppState } from "@editor/initial-state"; import type { AppState } from "@editor/state"; import { canvasRenderFramesEqual, selectCanvasRenderFrame } from "./renderFrame"; describe("canvas render frame selection", () => { test("ignores non-visual tool and history state", () => { const state = createInitialAppState("Test"); const next: AppState = { ...state, editor: { ...state.editor, tools: { ...state.editor.tools, generate: { ...state.editor.tools.generate, prompt: "new prompt", steps: state.editor.tools.generate.steps + 1, }, chromaKey: { ...state.editor.tools.chromaKey, tolerance: state.editor.tools.chromaKey.tolerance + 1, }, magicWand: { ...state.editor.tools.magicWand, contiguous: !state.editor.tools.magicWand.contiguous, }, }, }, history: { past: [{ document: state.document, editor: state.editor }], future: [] }, }; expect(canvasRenderFramesEqual(selectCanvasRenderFrame(state), selectCanvasRenderFrame(next))).toBe(true); }); test("detects document changes", () => { const state = createInitialAppState("Test"); const next = { ...state, document: { ...state.document, name: "Changed", }, }; expect(canvasRenderFramesEqual(selectCanvasRenderFrame(state), selectCanvasRenderFrame(next))).toBe(false); }); test("compares stable visual editor fields structurally", () => { const state = createInitialAppState("Test"); const sameSelectionValues = { ...state, editor: { ...state.editor, selection: { artboardId: state.editor.selection.artboardId, layerIds: [...state.editor.selection.layerIds], }, }, }; const changedBrush = { ...state, editor: { ...state.editor, tools: { ...state.editor.tools, brush: { ...state.editor.tools.brush, size: state.editor.tools.brush.size + 1, }, }, }, }; const changedPreview = { ...state, editor: { ...state.editor, brushPreview: { position: { x: 10, y: 20 } }, }, }; expect(canvasRenderFramesEqual(selectCanvasRenderFrame(state), selectCanvasRenderFrame(sameSelectionValues))).toBe(true); expectFrameChanged(state, changedBrush); expectFrameChanged(state, changedPreview); }); for (const [name, change] of visualEditorChanges) { test(`detects ${name} changes`, () => { const state = createInitialAppState("Test"); expectFrameChanged(state, change(state)); }); } }); const visualEditorChanges: Array<[string, (state: AppState) => AppState]> = [ [ "viewport", (state) => ({ ...state, editor: { ...state.editor, viewport: { ...state.editor.viewport, center: { x: state.editor.viewport.center.x + 1, y: state.editor.viewport.center.y }, }, }, }), ], [ "selection", (state) => ({ ...state, editor: { ...state.editor, selection: { artboardId: "artboard", layerIds: ["layer"] }, }, }), ], [ "transform session", (state) => ({ ...state, editor: { ...state.editor, transformSession: { target: { type: "artboard", id: "artboard" }, handle: "body", startPoint: { x: 1, y: 2 }, initialBounds: { x: 0, y: 0, w: 100, h: 80 }, }, }, }), ], [ "mask edit", (state) => ({ ...state, editor: { ...state.editor, maskEdit: { targetLayerId: "target", maskLayerId: "mask", viewMode: "overlay" }, }, }), ], [ "brush stroke preview", (state) => ({ ...state, editor: { ...state.editor, brushStrokePreview: { layerId: "layer", assetId: "asset", source: "preview" }, }, }), ], [ "generation candidate preview", (state) => ({ ...state, editor: { ...state.editor, generation: { candidates: [ { id: "candidate", source: "data:image/png;base64,candidate", mimeType: "image/png", intrinsicSize: { w: 64, h: 64 }, mode: "text-to-image", settings: state.editor.tools.generate, seed: 12, width: 64, height: 64, placement: { artboardId: "artboard", layerName: "Candidate", transform: { position: { x: 1, y: 2 }, scale: { x: 1, y: 1 }, rotation: 0 }, }, }, ], selectedCandidateId: "candidate", }, }, }), ], [ "active tool", (state) => ({ ...state, editor: { ...state.editor, tools: { ...state.editor.tools, activeTool: "brush" }, }, }), ], [ "interaction mode", (state) => ({ ...state, editor: { ...state.editor, tools: { ...state.editor.tools, interactionMode: { type: "temporary-pan", previousTool: "select" } }, }, }), ], ]; function expectFrameChanged(previous: AppState, next: AppState) { expect(canvasRenderFramesEqual(selectCanvasRenderFrame(previous), selectCanvasRenderFrame(next))).toBe(false); }