import { act, cleanup, render, screen } from "@testing-library/react"; import * as React from "react"; import { renderToString } from "react-dom/server"; import { afterEach, describe, expect, it, vi } from "vitest"; import { defineToolcraft } from "../../schema/define-toolcraft"; import { createToolcraftState } from "../../state/create-template-state"; import { createToolcraftExternalStore } from "../../state/toolcraft-external-store"; import type { ToolcraftExternalStore } from "../../state/toolcraft-external-store"; import { ToolcraftStoreContext } from "./toolcraft-store-context"; import { useToolcraftSelector } from "./use-toolcraft"; function createSelectorStore() { const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [ { controls: { first: { defaultValue: 1, target: "value.first", type: "slider", }, second: { defaultValue: 2, target: "value.second", type: "slider", }, }, }, ], title: "Controls", }, }, }); return createToolcraftExternalStore(createToolcraftState(schema)); } function StoreProvider({ children, store, }: { children: React.ReactNode; store: ToolcraftExternalStore; }) { return ( {children} ); } afterEach(cleanup); describe("useToolcraftSelector", () => { it("keeps the committed selector active when a different selector suspends and is interrupted", async () => { const store = createSelectorStore(); const suspendedRender = new Promise(() => undefined); const attemptedTargets: string[] = []; function Probe({ shouldSuspend, target }: { shouldSuspend: boolean; target: string }) { const value = useToolcraftSelector((state) => state.values[target]); attemptedTargets.push(target); if (shouldSuspend) { throw suspendedRender; } return {String(value)}; } const view = render( Pending}> , ); await act(async () => { React.startTransition(() => { view.rerender( Pending}> , ); }); }); expect(attemptedTargets).toContain("value.second"); expect(screen.getByTestId("selector-value").textContent).toBe("1"); act(() => { store.dispatch({ target: "value.first", type: "controls.setValue", value: 11, }); }); expect(screen.getByTestId("selector-value").textContent).toBe("11"); }); it("keeps one subscription across parent, playback, and selector-prop renders", () => { const store = createSelectorStore(); const subscriptionStarts = vi.fn(); const subscriptionStops = vi.fn(); const subscribe = store.subscribe; const subscribeSelector = store.subscribeSelector; store.subscribe = ((listener) => { subscriptionStarts(); const unsubscribe = subscribe(listener); return () => { subscriptionStops(); unsubscribe(); }; }) as ToolcraftExternalStore["subscribe"]; store.subscribeSelector = ((selector, listener, equality) => { subscriptionStarts(); const unsubscribe = subscribeSelector(selector, listener, equality); return () => { subscriptionStops(); unsubscribe(); }; }) as ToolcraftExternalStore["subscribeSelector"]; function Probe({ label, target }: { label: string; target: "playback" | "value.second" }) { const value = useToolcraftSelector((state) => target === "playback" ? state.timeline.currentTimeSeconds : state.values[target], ); return {label}:{String(value)}; } const view = render( , ); view.rerender( , ); act(() => { store.dispatchTransient({ currentTimeSeconds: 1, type: "timeline.setCurrentTime", }); }); expect(screen.getByTestId("selector-value").textContent).toBe("parent:1"); view.rerender( , ); act(() => { store.dispatch({ target: "value.second", type: "controls.setValue", value: 20, }); }); expect(screen.getByTestId("selector-value").textContent).toBe("switched:20"); expect(subscriptionStarts).toHaveBeenCalledTimes(1); expect(subscriptionStops).not.toHaveBeenCalled(); view.unmount(); expect(subscriptionStops).toHaveBeenCalledTimes(1); }); it("uses custom equality without rerendering for an equivalent selection", () => { const store = createSelectorStore(); let renderCount = 0; function Probe() { const offset = useToolcraftSelector( (state) => ({ ...state.canvas.offset }), (previous, next) => previous.x === next.x && previous.y === next.y, ); renderCount += 1; return {offset.x},{offset.y}; } render( , ); act(() => { store.dispatchTransient({ offset: { x: 0, y: 0 }, type: "canvas.setViewport", zoom: 150, }); }); expect(renderCount).toBe(1); }); it("uses a stable server snapshot", () => { const store = createSelectorStore(); function Probe() { return ( {String(useToolcraftSelector((state) => state.values["value.first"]))} ); } expect( renderToString( , ), ).toContain("1"); }); });