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 ;
}
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 ;
}
function ExplicitProbe() {
const value = useToolcraftEvaluatedValue("shape.opacity", 0);
explicitRenderCount += 1;
return ;
}
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 ;
}
function ExplicitProbe() {
const values = useToolcraftEvaluatedValues(0);
explicitResults.push(values);
return ;
}
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);
});
});