import { cleanup, fireEvent, render, screen, waitFor, } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; import { defineToolcraft } from "../../schema/define-toolcraft"; import { ToolcraftRoot } from "./toolcraft-root"; import { useToolcraft } from "./use-toolcraft"; const previousToolcraftNamespace = ["creative", "apps", "kit"].join("-"); function Probe() { const { dispatch, state } = useToolcraft(); return ( ); } function ResetProbe() { const { dispatch, state } = useToolcraft(); return (
); } function PanelPersistenceProbe() { const { dispatch, state } = useToolcraft(); return ( ); } function createPersistentSchema() { return defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { opacity: { defaultValue: 75, target: "selectedLayer.opacity", type: "slider", }, }, }, ], title: "Controls", }, }, persistence: { include: ["values", "panels"], key: "toolcraft:root-test:state:v1", storage: "localStorage", version: 1, }, }); } function createHistorySchema(history = true) { return defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { opacity: { defaultValue: 75, target: "selectedLayer.opacity", type: "slider", }, }, }, ], title: "Controls", }, }, toolbar: { history }, }); } afterEach(() => { cleanup(); window.localStorage.clear(); }); describe("ToolcraftRoot", () => { it("provides editor state and dispatch to children", async () => { render( , ); fireEvent.click(screen.getByRole("button", { name: "75" })); expect(await screen.findByRole("button", { name: "50" })).toBeTruthy(); }); it("restores persisted values before first render", () => { const schema = createPersistentSchema(); window.localStorage.setItem( "toolcraft:root-test:state:v1", JSON.stringify({ state: { values: { "selectedLayer.opacity": 33 } }, version: 1, }), ); render( , ); expect(screen.getByRole("button", { name: "33" })).toBeTruthy(); }); it("migrates persisted values from the previous storage namespace", async () => { const schema = createPersistentSchema(); const previousStorageKey = `${previousToolcraftNamespace}:root-test:state:v1`; window.localStorage.setItem( previousStorageKey, JSON.stringify({ state: { values: { "selectedLayer.opacity": 44 } }, version: 1, }), ); render( , ); expect(screen.getByRole("button", { name: "44" })).toBeTruthy(); await waitFor(() => { expect( JSON.parse( window.localStorage.getItem("toolcraft:root-test:state:v1") ?? "{}", ), ).toMatchObject({ state: { values: { "selectedLayer.opacity": 44 } }, version: 1, }); expect(window.localStorage.getItem(previousStorageKey)).toBeNull(); }); }); it("persists runtime values and reset results through schema policy", async () => { const schema = createPersistentSchema(); render( , ); fireEvent.click(screen.getByRole("button", { name: "75" })); await waitFor(() => { expect( JSON.parse(window.localStorage.getItem("toolcraft:root-test:state:v1") ?? "{}"), ).toMatchObject({ state: { values: { "selectedLayer.opacity": 50 } }, version: 1, }); }); fireEvent.click(screen.getByRole("button", { name: "Reset" })); await waitFor(() => { expect( JSON.parse(window.localStorage.getItem("toolcraft:root-test:state:v1") ?? "{}"), ).toMatchObject({ state: { values: { "selectedLayer.opacity": 75 } }, version: 1, }); }); }); it("restores dragged panel offsets from the app persistence policy before first render", async () => { const schema = createPersistentSchema(); const firstRender = render( , ); fireEvent.click(screen.getByRole("button", { name: "0,0" })); await waitFor(() => { expect( JSON.parse(window.localStorage.getItem("toolcraft:root-test:state:v1") ?? "{}"), ).toMatchObject({ state: { panels: { controls: { offset: { x: 72, y: -24 } } } }, version: 1, }); }); firstRender.unmount(); render( , ); expect(screen.getByRole("button", { name: "72,-24" })).toBeTruthy(); }); it("binds runtime undo and redo to keyboard shortcuts when history is enabled", async () => { render( , ); fireEvent.click(screen.getByRole("button", { name: "75" })); expect(await screen.findByRole("button", { name: "50" })).toBeTruthy(); fireEvent.keyDown(document, { key: "z", metaKey: true }); expect(screen.getByRole("button", { name: "75" })).toBeTruthy(); fireEvent.keyDown(document, { key: "z", metaKey: true, shiftKey: true }); expect(screen.getByRole("button", { name: "50" })).toBeTruthy(); fireEvent.keyDown(document, { key: "z", ctrlKey: true }); expect(screen.getByRole("button", { name: "75" })).toBeTruthy(); fireEvent.keyDown(document, { key: "y", ctrlKey: true }); expect(screen.getByRole("button", { name: "50" })).toBeTruthy(); }); it("binds runtime history shortcuts from focused non-text inputs", async () => { render(
, ); fireEvent.click(screen.getByRole("button", { name: "75" })); expect(await screen.findByRole("button", { name: "50" })).toBeTruthy(); const slider = screen.getByRole("slider", { name: "Opacity slider" }); const checkbox = screen.getByRole("checkbox", { name: "Include background" }); fireEvent.keyDown(slider, { key: "z", metaKey: true }); expect(screen.getByRole("button", { name: "75" })).toBeTruthy(); fireEvent.keyDown(slider, { key: "z", metaKey: true, shiftKey: true }); expect(screen.getByRole("button", { name: "50" })).toBeTruthy(); fireEvent.keyDown(checkbox, { key: "z", ctrlKey: true }); expect(screen.getByRole("button", { name: "75" })).toBeTruthy(); fireEvent.keyDown(checkbox, { key: "y", ctrlKey: true }); expect(screen.getByRole("button", { name: "50" })).toBeTruthy(); }); it("does not hijack undo shortcuts while the user is typing into editable controls", async () => { render(