import { act, cleanup, render } from "@testing-library/react"; import * as React from "react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const instrumentation = vi.hoisted(() => ({ ownerFactories: vi.fn(), runtimeInitializers: vi.fn(), })); vi.mock("../../rendering/renderer-pipeline-runtime", async (importOriginal) => { const actual = await importOriginal< typeof import("../../rendering/renderer-pipeline-runtime") >(); return { ...actual, createToolcraftRendererPipelineRuntime: ( ...args: Parameters ) => { instrumentation.runtimeInitializers(); return actual.createToolcraftRendererPipelineRuntime(...args); }, }; }); vi.mock("../../rendering", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, createToolcraftRendererPipelineRuntimeOwner: ( ...args: Parameters< typeof actual.createToolcraftRendererPipelineRuntimeOwner > ) => { instrumentation.ownerFactories(); return actual.createToolcraftRendererPipelineRuntimeOwner(...args); }, }; }); import { registerToolcraftRendererPipeline, type ToolcraftRendererPipelinePassContract, } from "../../rendering/renderer-pipeline-registration"; import type { ToolcraftRendererPipelineClient } from "../../rendering/renderer-pipeline-runtime"; import { createToolcraftRendererPipelineRuntimeOwner } from "../../rendering/renderer-pipeline-runtime-owner"; import { defineToolcraft } from "../../schema/define-toolcraft"; import { ToolcraftApp } from "./toolcraft-app"; import { useToolcraftPipeline } from "./use-toolcraft-pipeline"; afterEach(cleanup); beforeEach(() => { instrumentation.ownerFactories.mockClear(); instrumentation.runtimeInitializers.mockClear(); }); const schema = defineToolcraft({ canvas: { enabled: true }, panels: { controls: { sections: [], title: "Controls", }, }, }); function createRegistration(runtimeId = "pipeline-initialization-test-v1") { return registerToolcraftRendererPipeline<{ preview: ToolcraftRendererPipelinePassContract; }>()({ interactionInvalidation: [], passes: [ { id: "preview", inputs: [], invalidatedBy: [], kind: "composite", lifecycle: { cache: "none", resourceScope: "call" }, output: "preview", quality: "full", runsOn: "main", }, ], runtimeId, }); } describe("Toolcraft pipeline initialization", () => { it("reads a stable uninitialized snapshot without constructing a runtime", async () => { const owner = createToolcraftRendererPipelineRuntimeOwner(createRegistration()); const first = owner.client.getSnapshot(); const second = owner.client.getSnapshot(); expect(instrumentation.runtimeInitializers).not.toHaveBeenCalled(); expect(second).toBe(first); expect(first).toMatchObject({ disposed: false, runtimeId: "pipeline-initialization-test-v1", }); expect(first.passes.preview.executions).toBe(0); expect(Object.isFrozen(first)).toBe(true); expect(Object.isFrozen(first.passes)).toBe(true); expect(Object.isFrozen(first.passes.preview)).toBe(true); await owner.dispose(); const disposed = owner.client.getSnapshot(); expect(instrumentation.runtimeInitializers).not.toHaveBeenCalled(); expect(disposed.disposed).toBe(true); expect(owner.client.getSnapshot()).toBe(disposed); }); it("initializes one real runtime after a StrictMode commit and disposes it", async () => { const observedRuntimes: ToolcraftRendererPipelineClient[] = []; function RuntimeProbe(): null { const runtime = useToolcraftPipeline(); if (runtime) { runtime.getSnapshot(); observedRuntimes.push(runtime); } return null; } const view = render( } rendererPipelineRegistration={createRegistration()} schema={schema} /> , ); await act(() => Promise.resolve()); expect(instrumentation.runtimeInitializers).toHaveBeenCalledTimes(1); const runtime = observedRuntimes.at(-1)!; expect(runtime.getSnapshot().disposed).toBe(false); view.unmount(); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(runtime.getSnapshot().disposed).toBe(true); }); it("allocates no pipeline provider owner or runtime for a neutral app", () => { let runtime: ToolcraftRendererPipelineClient | null | undefined; function RuntimeProbe(): null { runtime = useToolcraftPipeline(); return null; } render(} schema={schema} />); expect(runtime).toBeNull(); expect(instrumentation.ownerFactories).not.toHaveBeenCalled(); expect(instrumentation.runtimeInitializers).not.toHaveBeenCalled(); }); it("keeps independently mounted app runtimes isolated", async () => { const runtimes: ToolcraftRendererPipelineClient[] = []; function RuntimeProbe(): null { const runtime = useToolcraftPipeline(); if (runtime && !runtimes.includes(runtime)) { runtimes.push(runtime); } return null; } const view = render( <> } rendererPipelineRegistration={createRegistration()} schema={schema} /> } rendererPipelineRegistration={createRegistration()} schema={schema} /> , ); await act(() => Promise.resolve()); expect(instrumentation.runtimeInitializers).toHaveBeenCalledTimes(2); expect(runtimes).toHaveLength(2); expect(runtimes[0]).not.toBe(runtimes[1]); view.unmount(); await act(() => Promise.resolve()); expect(runtimes.every((runtime) => runtime.getSnapshot().disposed)).toBe(true); }); it("reopens the same provider facade after an Activity effect reconnect", async () => { const registration = createRegistration("activity-reconnect-v1"); const clients: ToolcraftRendererPipelineClient[] = []; function RuntimeProbe(): null { const client = useToolcraftPipeline(); if (client) { clients.push(client); } return null; } const renderActivity = (mode: "hidden" | "visible") => ( } rendererPipelineRegistration={registration} schema={schema} /> ); const view = render(renderActivity("visible")); await act(() => Promise.resolve()); const client = clients.at(-1)!; view.rerender(renderActivity("hidden")); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(client.getSnapshot().disposed).toBe(true); view.rerender(renderActivity("visible")); await act(() => Promise.resolve()); expect(clients.at(-1)).toBe(client); expect(client.getSnapshot().disposed).toBe(false); expect(instrumentation.runtimeInitializers).toHaveBeenCalledTimes(2); view.unmount(); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); expect(client.getSnapshot().disposed).toBe(true); }); it("replaces the owner when registration identity changes", async () => { const firstRegistration = createRegistration("pipeline-first-v1"); const secondRegistration = createRegistration("pipeline-second-v1"); const clients: ToolcraftRendererPipelineClient[] = []; function RuntimeProbe(): null { const client = useToolcraftPipeline(); if (client && !clients.includes(client)) { clients.push(client); } return null; } const view = render( } rendererPipelineRegistration={firstRegistration} schema={schema} />, ); await act(() => Promise.resolve()); const firstClient = clients[0]!; view.rerender( } rendererPipelineRegistration={secondRegistration} schema={schema} />, ); await act(async () => { await Promise.resolve(); await Promise.resolve(); }); const secondClient = clients[1]!; expect(secondClient).not.toBe(firstClient); expect(firstClient.getSnapshot().disposed).toBe(true); await expect( firstClient.runPass(firstRegistration.getPass("preview"), undefined, () => "stale"), ).rejects.toThrow( 'Renderer pipeline runtime owner "pipeline-first-v1" is disposed.', ); expect(secondClient.getSnapshot()).toMatchObject({ disposed: false, runtimeId: "pipeline-second-v1", }); await expect( secondClient.runPass(firstRegistration.getPass("preview"), undefined, () => "stale"), ).rejects.toThrow( 'Renderer pipeline pass "preview" does not belong to registration "pipeline-second-v1".', ); expect(instrumentation.runtimeInitializers).toHaveBeenCalledTimes(2); }); });