import { describe, expect, it } from "vitest"; import { defineToolcraft } from "../schema/define-toolcraft"; import { createToolcraftState } from "./create-template-state"; import { toolcraftReducer } from "../composition/public-state"; function createState( initialState: Parameters[1] = {}, ) { const schema = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft App" }, canvas: { enabled: true, sizing: { mode: "editable-output" }, size: { height: 720, unit: "px", width: 1280 }, }, panels: { controls: { sections: [], title: "Controls" } }, }, modules: [], }); return createToolcraftState(schema, initialState); } function createInfinityDefaultState( initialState: Parameters[1] = {}, ) { const schema = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft App" }, canvas: { enabled: true, sizing: { defaultMode: "infinite", mode: "editable-output", }, size: { height: 720, unit: "px", width: 1280 }, }, panels: { controls: { sections: [], title: "Controls" } }, }, modules: [], }); return createToolcraftState(schema, initialState); } function createBackgroundState( initialState: Parameters[1] = {}, includeBackgroundDefault = true, ) { const schema = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft App" }, canvas: { enabled: true, sizing: { mode: "editable-output" }, size: { height: 720, unit: "px", width: 1280 }, }, panels: { controls: { sections: [ { id: "test-section-1", controls: { includeBackground: { applicability: { mode: "always" as const }, defaultValue: includeBackgroundDefault, target: "export.includeBackground", type: "switch", }, background: { applicability: { mode: "always" as const }, defaultValue: "#D4CECA", target: "appearance.background", type: "color", }, }, title: "Background", }, ], title: "Controls", }, }, }, modules: [], }); 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("changes only mode across Infinity transitions and history", () => { const initial = createState({ canvas: { offset: { x: 37, y: -19 }, size: { height: 617, unit: "px", width: 1031 }, zoom: 135, }, }); const infinite = toolcraftReducer(initial, { target: "canvas.infinity", type: "controls.setValue", value: true, }); const finite = toolcraftReducer(infinite, { target: "canvas.infinity", type: "controls.setValue", value: false, }); const undone = toolcraftReducer(finite, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(infinite.canvas).toEqual({ ...initial.canvas, mode: "infinite" }); expect(finite.canvas).toEqual(initial.canvas); expect(undone.canvas).toEqual(infinite.canvas); expect(redone.canvas).toEqual(initial.canvas); expect(infinite.history.undo.at(-1)?.after).toEqual({ "canvas.mode": "infinite", }); expect(finite.history.undo.at(-1)?.after).toEqual({ "canvas.mode": "finite", }); }); it("resets only Infinity mode without discarding the last finite size", () => { const initial = createState({ canvas: { offset: { x: 41, y: -23 }, zoom: 145, }, }); const infinite = toolcraftReducer(initial, { target: "canvas.infinity", type: "controls.setValue", value: true, }); const reset = toolcraftReducer(infinite, { targets: ["canvas.infinity"], type: "controls.resetTargets", }); const undone = toolcraftReducer(reset, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(reset.canvas).toEqual(initial.canvas); expect(reset.canvas.size).toEqual(infinite.canvas.size); expect(reset.history.undo.at(-1)?.after).toEqual({ "canvas.mode": "finite", }); expect(undone.canvas).toEqual(infinite.canvas); expect(redone.canvas).toEqual(initial.canvas); }); it("globally resets Infinity mode without changing offset or zoom", () => { const initial = createState({ canvas: { offset: { x: -54, y: 29 }, zoom: 165, }, }); const infinite = toolcraftReducer(initial, { target: "canvas.infinity", type: "controls.setValue", value: true, }); const reset = toolcraftReducer(infinite, { type: "controls.reset" }); const patch = reset.history.undo.at(-1); const undone = toolcraftReducer(reset, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(reset.canvas).toEqual(initial.canvas); expect(patch?.after).not.toHaveProperty("canvas.offset"); expect(patch?.before).not.toHaveProperty("canvas.offset"); expect(patch?.after).not.toHaveProperty("canvas.zoom"); expect(patch?.before).not.toHaveProperty("canvas.zoom"); expect(undone.canvas).toEqual(infinite.canvas); expect(redone.canvas).toEqual(initial.canvas); }); 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 initial = createBackgroundState({ canvas: { offset: { x: 37, y: -19 }, size: { height: 617, unit: "px", width: 1031 }, zoom: 135, }, }); const infinite = toolcraftReducer(initial, { target: "canvas.infinity", type: "controls.setValue", value: true, }); const backgroundOff = toolcraftReducer(infinite, { 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).toEqual(initial.canvas); expect(backgroundOff.history.undo.at(-1)?.after).toEqual({ "canvas.mode": "finite", "export.includeBackground": false, }); expect(undone.values["export.includeBackground"]).toBe(true); expect(undone.canvas).toEqual(infinite.canvas); expect(redone.values["export.includeBackground"]).toBe(false); expect(redone.canvas).toEqual(initial.canvas); }); 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 initial = createBackgroundState({ canvas: { offset: { x: 31, y: -17 }, zoom: 155, }, }); const backgroundOff = toolcraftReducer(initial, { target: "export.includeBackground", type: "controls.setValue", value: false, }); const imported = toolcraftReducer(backgroundOff, { mode: "infinite", size: { height: 900, unit: "px", width: 1600 }, type: "canvas.applySettings", }); const patch = imported.history.undo.at(-1); const undone = toolcraftReducer(imported, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(imported.canvas.mode).toBe("finite"); expect(imported.canvas.offset).toEqual(initial.canvas.offset); expect(imported.canvas.zoom).toBe(initial.canvas.zoom); expect(imported.canvas.size).toEqual({ height: 900, unit: "px", width: 1600, }); expect(patch?.after).not.toHaveProperty("canvas.offset"); expect(patch?.before).not.toHaveProperty("canvas.offset"); expect(patch?.after).not.toHaveProperty("canvas.zoom"); expect(patch?.before).not.toHaveProperty("canvas.zoom"); expect(undone.canvas).toEqual(backgroundOff.canvas); expect(redone.canvas).toEqual(imported.canvas); }); it("resets Background to off without leaving Infinity active", () => { const initial = createBackgroundState( { canvas: { offset: { x: -46, y: 28 }, zoom: 125, }, }, false, ); const backgroundOn = toolcraftReducer(initial, { 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", }); const undone = toolcraftReducer(reset, { type: "history.undo" }); const redone = toolcraftReducer(undone, { type: "history.redo" }); expect(reset.values["export.includeBackground"]).toBe(false); expect(reset.canvas).toEqual(initial.canvas); expect(reset.history.undo.at(-1)?.after).toEqual({ "canvas.mode": "finite", "export.includeBackground": false, }); expect(undone.canvas).toEqual(infinite.canvas); expect(redone.canvas).toEqual(initial.canvas); }); });