import { describe, expect, it } from "vitest"; import { defineToolcraft } from "../schema/define-toolcraft"; import { createToolcraftState } from "./create-template-state"; import { toolcraftReducer } from "./reducer"; function createState() { const schema = defineToolcraft({ canvas: { enabled: true, sizing: { mode: "editable-output" }, size: { height: 720, unit: "px", width: 1280 }, }, panels: { controls: { sections: [], title: "Controls" }, }, }); return createToolcraftState(schema); } function createInfinityDefaultState( initialState: Parameters[1] = {}, ) { const schema = defineToolcraft({ canvas: { enabled: true, sizing: { defaultMode: "infinite", mode: "editable-output", }, size: { height: 720, unit: "px", width: 1280 }, }, panels: { controls: { sections: [], title: "Controls" }, }, }); return createToolcraftState(schema, initialState); } function createBackgroundState( initialState: Parameters[1] = {}, includeBackgroundDefault = true, ) { const schema = defineToolcraft({ canvas: { enabled: true, sizing: { mode: "editable-output" }, size: { height: 720, unit: "px", width: 1280 }, }, panels: { controls: { sections: [ { controls: { includeBackground: { defaultValue: includeBackgroundDefault, target: "export.includeBackground", type: "switch", }, background: { defaultValue: "#D4CECA", target: "appearance.background", type: "color", }, }, title: "Background", }, ], title: "Controls", }, }, }); return createToolcraftState(schema, initialState); } describe("canvas-owned control targets", () => { it("starts from the schema canvas mode while preserving an explicit restored mode", () => { expect(createInfinityDefaultState().canvas.mode).toBe("infinite"); expect( createInfinityDefaultState({ canvas: { mode: "finite" } }).canvas.mode, ).toBe("finite"); }); it("sets Infinity mode without copying it into values", () => { const state = toolcraftReducer(createState(), { target: "canvas.infinity", type: "controls.setValue", value: true, }); expect(state.canvas.mode).toBe("infinite"); expect(state.values).not.toHaveProperty("canvas.infinity"); expect(state.history.undo.at(-1)?.label).toBe("Enable Infinity canvas"); }); it("ignores stale finite-size writes while infinite", () => { const infinite = toolcraftReducer(createState(), { target: "canvas.infinity", type: "controls.setValue", value: true, }); const resized = toolcraftReducer(infinite, { target: "canvas.size.width", type: "controls.setValue", value: 999, }); expect(resized).toBe(infinite); expect(resized.canvas.size.width).toBe(1280); }); it("restores finite mode and centers through undo and redo", () => { const infinite = toolcraftReducer(createState(), { target: "canvas.infinity", type: "controls.setValue", value: true, }); const panned = toolcraftReducer(infinite, { offset: { x: 30, y: -20 }, type: "canvas.setOffset", }); const finite = toolcraftReducer(panned, { target: "canvas.infinity", type: "controls.setValue", value: false, }); const undone = toolcraftReducer(finite, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(finite.canvas).toMatchObject({ mode: "finite", offset: { x: 0, y: 0 } }); expect(undone.canvas).toMatchObject({ mode: "infinite", offset: { x: 30, y: -20 }, }); expect(redone.canvas).toMatchObject({ mode: "finite", offset: { x: 0, y: 0 } }); }); it("resets only Infinity mode without discarding the last finite size", () => { const infinite = toolcraftReducer(createState(), { target: "canvas.infinity", type: "controls.setValue", value: true, }); const reset = toolcraftReducer(infinite, { targets: ["canvas.infinity"], type: "controls.resetTargets", }); expect(reset.canvas.mode).toBe("finite"); expect(reset.canvas.size).toEqual(infinite.canvas.size); }); it("resets a finite canvas to an app-selected Infinity default", () => { const finite = toolcraftReducer(createInfinityDefaultState(), { target: "canvas.infinity", type: "controls.setValue", value: false, }); const reset = toolcraftReducer(finite, { targets: ["canvas.infinity"], type: "controls.resetTargets", }); expect(finite.canvas.mode).toBe("finite"); expect(reset.canvas.mode).toBe("infinite"); expect(reset.canvas.size).toEqual(finite.canvas.size); }); it("turns an infinite canvas finite in the same history transition when Background is disabled", () => { const infinite = toolcraftReducer(createBackgroundState(), { target: "canvas.infinity", type: "controls.setValue", value: true, }); const panned = toolcraftReducer(infinite, { offset: { x: 30, y: -20 }, type: "canvas.setOffset", }); const backgroundOff = toolcraftReducer(panned, { target: "export.includeBackground", type: "controls.setValue", value: false, }); const undone = toolcraftReducer(backgroundOff, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(backgroundOff.values["export.includeBackground"]).toBe(false); expect(backgroundOff.canvas).toMatchObject({ mode: "finite", offset: { x: 0, y: 0 }, }); expect(backgroundOff.history.undo.at(-1)?.after).toMatchObject({ "canvas.mode": "finite", "canvas.offset": { x: 0, y: 0 }, "export.includeBackground": false, }); expect(undone.values["export.includeBackground"]).toBe(true); expect(undone.canvas).toMatchObject({ mode: "infinite", offset: { x: 30, y: -20 }, }); expect(redone.values["export.includeBackground"]).toBe(false); expect(redone.canvas.mode).toBe("finite"); }); it("rejects Infinity while Background is disabled and does not enable it when Background returns", () => { const backgroundOff = toolcraftReducer(createBackgroundState(), { target: "export.includeBackground", type: "controls.setValue", value: false, }); const rejectedInfinity = toolcraftReducer(backgroundOff, { target: "canvas.infinity", type: "controls.setValue", value: true, }); const backgroundOn = toolcraftReducer(rejectedInfinity, { target: "export.includeBackground", type: "controls.setValue", value: true, }); expect(rejectedInfinity).toBe(backgroundOff); expect(rejectedInfinity.canvas.mode).toBe("finite"); expect(backgroundOn.values["export.includeBackground"]).toBe(true); expect(backgroundOn.canvas.mode).toBe("finite"); }); it("keeps imported canvas settings finite while Background is disabled", () => { const backgroundOff = toolcraftReducer(createBackgroundState(), { target: "export.includeBackground", type: "controls.setValue", value: false, }); const imported = toolcraftReducer(backgroundOff, { mode: "infinite", size: { height: 900, unit: "px", width: 1600 }, type: "canvas.applySettings", }); expect(imported.canvas.mode).toBe("finite"); expect(imported.canvas.size).toEqual({ height: 900, unit: "px", width: 1600, }); }); it("resets Background to off without leaving Infinity active", () => { const defaultOff = createBackgroundState({}, false); const backgroundOn = toolcraftReducer(defaultOff, { target: "export.includeBackground", type: "controls.setValue", value: true, }); const infinite = toolcraftReducer(backgroundOn, { target: "canvas.infinity", type: "controls.setValue", value: true, }); const reset = toolcraftReducer(infinite, { targets: ["export.includeBackground"], type: "controls.resetTargets", }); expect(reset.values["export.includeBackground"]).toBe(false); expect(reset.canvas.mode).toBe("finite"); }); });