import { act, cleanup, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it } from "vitest"; import { defineToolcraft } from "../../schema/define-toolcraft"; import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store"; import { ToolcraftRoot } from "./toolcraft-root"; import { useToolcraftStore } from "./toolcraft-store-context"; import { useToolcraftEvaluatedValue, useToolcraftEvaluatedValues, useToolcraftValue, } from "./use-toolcraft"; const valuesSchema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { opacity: { defaultValue: 0, target: "shape.opacity", type: "slider", }, scale: { defaultValue: 10, target: "shape.scale", type: "slider", }, }, }, ], title: "Controls", }, timeline: { mode: "keyframes" }, }, }); const initialState = { timeline: { currentTimeSeconds: 0, keyframeGroups: [ { controlId: "shape.opacity", keyframes: [ { controlId: "shape.opacity", controlLabel: "Opacity", id: "shape.opacity::0", timeSeconds: 0, value: 0, valueLabel: "0", }, { controlId: "shape.opacity", controlLabel: "Opacity", id: "shape.opacity::4", timeSeconds: 4, value: 100, valueLabel: "100", }, ], label: "Opacity", }, ], }, }; function CaptureStore({ capture, }: { capture: (store: ToolcraftExternalStore) => void; }) { capture(useToolcraftStore()); return null; } afterEach(cleanup); describe("Toolcraft value selector hooks", () => { it("useToolcraftValue rerenders only when its target changes", () => { let store: ToolcraftExternalStore | undefined; let renderCount = 0; function Probe() { const value = useToolcraftValue("shape.opacity"); renderCount += 1; return {String(value)}; } render( { store = value; }} /> , ); act(() => { store?.dispatch({ target: "shape.scale", type: "controls.setValue", value: 20, }); store?.dispatchTransient({ offset: { x: 12, y: -6 }, type: "canvas.setViewport", zoom: 125, }); }); expect(renderCount).toBe(1); act(() => { store?.dispatch({ target: "shape.opacity", type: "controls.setValue", value: 25, }); }); expect(screen.getByTestId("value").textContent).toBe("25"); expect(renderCount).toBe(2); }); it("evaluates one target from only its value, matching keyframes, and implicit playback", () => { let store: ToolcraftExternalStore | undefined; let implicitRenderCount = 0; let explicitRenderCount = 0; function ImplicitProbe() { const value = useToolcraftEvaluatedValue("shape.opacity"); implicitRenderCount += 1; return {String(value)}; } function ExplicitProbe() { const value = useToolcraftEvaluatedValue("shape.opacity", 0); explicitRenderCount += 1; return {String(value)}; } render( { store = value; }} /> , ); act(() => { store?.dispatch({ target: "shape.scale", type: "controls.setValue", value: 20, }); store?.dispatchTransient({ offset: { x: 12, y: -6 }, type: "canvas.setViewport", zoom: 125, }); }); expect(implicitRenderCount).toBe(1); expect(explicitRenderCount).toBe(1); act(() => { store?.dispatchTransient({ currentTimeSeconds: 4, type: "timeline.setCurrentTime", }); }); expect(screen.getByTestId("implicit-value").textContent).toBe("100"); expect(screen.getByTestId("explicit-value").textContent).toBe("0"); expect(implicitRenderCount).toBe(2); expect(explicitRenderCount).toBe(1); act(() => { store?.dispatch({ controlId: "shape.scale", controlLabel: "Scale", type: "timeline.upsertControlKeyframe", value: 20, valueLabel: "20", }); }); expect(implicitRenderCount).toBe(2); expect(explicitRenderCount).toBe(1); act(() => { store?.dispatch({ controlId: "shape.opacity", controlLabel: "Opacity", timeSeconds: 4, type: "timeline.upsertControlKeyframe", value: 80, valueLabel: "80", }); }); expect(screen.getByTestId("implicit-value").textContent).toBe("80"); expect(screen.getByTestId("explicit-value").textContent).toBe("0"); expect(implicitRenderCount).toBe(3); expect(explicitRenderCount).toBe(2); }); it("evaluates all values without subscribing to viewport, panels, or explicit-time playback", () => { let store: ToolcraftExternalStore | undefined; const implicitResults: Array> = []; const explicitResults: Array> = []; function ImplicitProbe() { const values = useToolcraftEvaluatedValues(); implicitResults.push(values); return {String(values["shape.opacity"])}; } function ExplicitProbe() { const values = useToolcraftEvaluatedValues(0); explicitResults.push(values); return {String(values["shape.opacity"])}; } render( { store = value; }} /> , ); const initialImplicitResult = implicitResults[0]; const initialExplicitResult = explicitResults[0]; act(() => { store?.dispatchTransient({ offset: { x: 18, y: 9 }, type: "canvas.setViewport", zoom: 140, }); store?.dispatch({ offset: { x: 20, y: 10 }, panelId: "controls", type: "panels.setOffset", }); }); expect(implicitResults).toHaveLength(1); expect(explicitResults).toHaveLength(1); expect(implicitResults[0]).toBe(initialImplicitResult); expect(explicitResults[0]).toBe(initialExplicitResult); act(() => { store?.dispatchTransient({ currentTimeSeconds: 4, type: "timeline.setCurrentTime", }); }); expect(screen.getByTestId("implicit-values").textContent).toBe("100"); expect(screen.getByTestId("explicit-values").textContent).toBe("0"); expect(implicitResults).toHaveLength(2); expect(explicitResults).toHaveLength(1); act(() => { store?.dispatch({ target: "shape.scale", type: "controls.setValue", value: 30, }); }); expect(implicitResults).toHaveLength(3); expect(explicitResults).toHaveLength(2); }); });