import type * as React from "react"; import { describe, expect, expectTypeOf, it } from "vitest"; import * as ToolcraftRuntime from "../../index"; import type { ToolcraftHistoryPatch } from "../../index"; import type { ToolcraftRendererPipelineClient } from "../../rendering"; import type { ToolcraftCommand } from "../../state/types"; import * as ToolcraftReact from "../index"; import { isToolcraftLayerVisibleInTree, useToolcraftMediaPresentationUrls, useToolcraftProductSceneFrame, } from "../index"; import { CanvasShell } from "../canvas/canvas-shell"; import { ControlsPanel } from "../controls-panel/controls-panel"; import type { ToolcraftPanelActionContext } from "../controls-panel/actions/controls-panel-actions"; import { ToolcraftApp, type ToolcraftAppComposition, } from "./toolcraft-app"; import { useToolcraftStore } from "./toolcraft-store-context"; import { ToolcraftRoot, type ToolcraftContextValue, } from "./toolcraft-root"; import { runtimePublicApiSchema } from "./runtime-public-api-test-support"; import { LayersPanel } from "../layers/layers-panel"; import { PanelContainer, PanelHost, PanelStage } from "../panel-host/panel-host"; import { TimelinePanel } from "../timeline/timeline-panel"; import { ToolbarPanel } from "./toolbar-panel"; import { useToolcraftDispatch, useToolcraftSelector, } from "./use-toolcraft"; import { useToolcraftPipeline } from "./use-toolcraft-pipeline"; import { useToolcraftPipelinePass } from "./use-toolcraft-pipeline-pass"; describe("Toolcraft template runtime public API", () => { it("exports selector hooks without exposing the internal store context", () => { expect(ToolcraftRuntime).not.toHaveProperty( "toolcraftControlsResetHistorySource", ); expect(ToolcraftRuntime.TOOLCRAFT_SVG_NAMESPACE).toBe( "http://www.w3.org/2000/svg", ); expect(ToolcraftRuntime).not.toHaveProperty("exportToolcraftSvgArtifact"); expect(ToolcraftRuntime).not.toHaveProperty("createToolcraftSvgDocument"); expect(ToolcraftRuntime).not.toHaveProperty( "serializeToolcraftSvgDocument", ); expect(ToolcraftReact.useToolcraftDispatch).toBe(useToolcraftDispatch); expect(ToolcraftReact.useToolcraftSelector).toBe(useToolcraftSelector); expect(ToolcraftReact.useToolcraftPipeline).toBe(useToolcraftPipeline); expect(ToolcraftReact.useToolcraftPipelinePass).toBe( useToolcraftPipelinePass, ); expect(ToolcraftReact.useToolcraftMediaPresentationUrls).toBe( useToolcraftMediaPresentationUrls, ); expect(useToolcraftMediaPresentationUrls).toEqual(expect.any(Function)); expect(ToolcraftReact.isToolcraftLayerVisibleInTree).toBe( isToolcraftLayerVisibleInTree, ); expect(isToolcraftLayerVisibleInTree).toEqual(expect.any(Function)); expect(ToolcraftReact.useToolcraftProductSceneFrame).toBe( useToolcraftProductSceneFrame, ); expect(useToolcraftProductSceneFrame).toEqual(expect.any(Function)); expect(ToolcraftReact).not.toHaveProperty("ToolcraftStoreContext"); expect(ToolcraftReact).not.toHaveProperty("useToolcraftStore"); }); it("keeps app assembly props narrow", () => { expectTypeOf().toEqualTypeOf< "after" | "before" | "group" | "label" >(); expectTypeOf().toEqualTypeOf< "dispatch" | "state" >(); expectTypeOf>().toEqualTypeOf< React.Dispatch >(); expectTypeOf().toEqualTypeOf< | "canvasContent" | "controlRenderers" | "exportRenderer" | "infiniteCanvasContent" | "modelPresentation" | "onPanelAction" | "renderDefaultCanvasMedia" | "rendererPipelineRegistration" | "sceneBoundsProvider" | "schema" | "svgExportRenderer" >(); expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf< | "canvasContent" | "className" | "controlRenderers" | "exportRenderer" | "infiniteCanvasContent" | "modelPresentation" | "onPanelAction" | "renderDefaultCanvasMedia" | "rendererPipelineRegistration" | "sceneBoundsProvider" | "schema" | "style" | "svgExportRenderer" >(); expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf< | "children" | "initialState" | "modelPresentation" | "rendererPipelineRegistration" | "schema" >(); expectTypeOf>().toEqualTypeOf< ToolcraftRendererPipelineClient | null >(); expectTypeOf().toEqualTypeOf< | "action" | "dispatch" | "reportFeedback" | "resolveMediaResource" | "rendererPipeline" | "reportProgress" | "state" >(); if (false) { const client = useToolcraftPipeline(); if (client) { // @ts-expect-error Product-facing pipeline clients cannot close shared runtime ownership. void client.dispose(); } } }); it("keeps runtime panel props focused on host integration", () => { type RuntimePanelKeys = | "className" | "framed" | "onPanelStateChange" | "panelPlacement" | "panelState"; type ControlsPanelKeys = | RuntimePanelKeys | "controlRenderers" | "onPanelAction" | "sceneExport"; expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf(); expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf(); expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf(); expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf(); }); it("keeps shell and panel host props separate", () => { expectTypeOf< keyof React.ComponentProps >().toEqualTypeOf< "children" | "infiniteCanvasContent" | "renderDefaultMedia" >(); expectTypeOf>().toHaveProperty( "panelType", ); expectTypeOf>().toHaveProperty( "placement", ); expectTypeOf>().toHaveProperty( "children", ); }); it("rejects hidden runtime wiring props at compile time", () => { expect().toBeTruthy(); expect().toBeTruthy(); expect().toBeTruthy(); const appWithInitialStateProps: React.ComponentProps< typeof ToolcraftApp > = { // @ts-expect-error Runtime state seeding belongs to ToolcraftRoot, not the app shell. initialState: {}, schema: runtimePublicApiSchema, }; const appWithChildrenProps: React.ComponentProps = { // @ts-expect-error ToolcraftApp is assembled from schema and does not accept children. children: "Child", schema: runtimePublicApiSchema, }; const appWithToolbarThemeToggleProps: React.ComponentProps< typeof ToolcraftApp > = { schema: runtimePublicApiSchema, // @ts-expect-error Toolbar theme visibility belongs to schema.toolbar.theme. toolbarThemeToggle: false, }; const appWithCanvasSlotProps: React.ComponentProps = { canvasContent:
, onPanelAction: ({ action }) => { void action.value; }, renderDefaultCanvasMedia: false, schema: runtimePublicApiSchema, }; const compositionWithClassName: ToolcraftAppComposition = { schema: runtimePublicApiSchema, // @ts-expect-error Host layout classes belong to the protected route. className: "product-shell", }; const compositionWithStyle: ToolcraftAppComposition = { schema: runtimePublicApiSchema, // @ts-expect-error Host layout styles belong to the protected route. style: { minHeight: 0 }, }; const canvasWithSlotProps: React.ComponentProps = { children:
, infiniteCanvasContent:
, renderDefaultMedia: false, }; // @ts-expect-error Canvas upload belongs to schema.canvas.upload. const canvasWithUpload = ; // @ts-expect-error CanvasShell is a runtime-owned shell, not a styled wrapper. const styledCanvas = ; // @ts-expect-error Toolbar history visibility belongs to schema.toolbar.history. const toolbarWithHistory = ; // @ts-expect-error Toolbar theme visibility belongs to schema.toolbar.theme. const toolbarWithThemeToggle = ; expect(appWithInitialStateProps).toBeTruthy(); expect(appWithChildrenProps).toBeTruthy(); expect(appWithToolbarThemeToggleProps).toBeTruthy(); expect(appWithCanvasSlotProps).toBeTruthy(); expect(compositionWithClassName).toBeTruthy(); expect(compositionWithStyle).toBeTruthy(); expect(canvasWithSlotProps).toBeTruthy(); expect(canvasWithUpload).toBeTruthy(); expect(styledCanvas).toBeTruthy(); expect(toolbarWithHistory).toBeTruthy(); expect(toolbarWithThemeToggle).toBeTruthy(); }); });