import type * as React from "react"; import { describe, expect, expectTypeOf, it } from "vitest"; import * as ToolcraftRuntime from "../../index"; import type { ToolcraftHistoryPatch, ToolcraftModelPerformanceDimension, } from "../../index"; import type { ToolcraftRendererPipelineClient } from "../../rendering"; import type { ToolcraftCommand } from "../../state/types"; import * as ToolcraftReact from "../index"; import { composeToolcraftApp, isToolcraftLayerVisibleInTree, renderToolcraftModelsToCanvas, type ToolcraftAppPorts, useToolcraftMediaPresentationUrls, useToolcraftProductSceneFrame, useToolcraftViewportInteractionActive, } 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 { ToolcraftSourceAssetProvider } from "./toolcraft-source-asset-context"; 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("publishes the typed app composition constructor", () => { const ports: ToolcraftAppPorts = {}; const composition = composeToolcraftApp(runtimePublicApiSchema, ports); expect(composition.schema).toBe(runtimePublicApiSchema); expect(ToolcraftReact.composeToolcraftApp).toBe(composeToolcraftApp); }); it("publishes every module factory without resolver, contribution, or catalog internals", () => { const publicFactories = [ "canvasEditingModule", "imageExportModule", "layersModule", "mediaSourceModule", "model3dModule", "spatialViewModule", "svgExportModule", "timelineModule", "videoExportModule", ] as const; for (const factory of publicFactories) { expect(ToolcraftRuntime[factory]).toEqual(expect.any(Function)); } for (const privateName of [ "resolveToolcraftModuleContributions", "resolveToolcraftProductModules", "TOOLCRAFT_BUILT_IN_MODULE_CATALOG", "TOOLCRAFT_DEFAULT_PROVIDER_CATALOG", ]) { expect(ToolcraftRuntime).not.toHaveProperty(privateName); } if (false) { // @ts-expect-error The old complete schema is not a public input. const legacySchema: ToolcraftRuntime.ToolcraftAppSchema = {}; // @ts-expect-error The materialized intermediate schema stays private. const materializedSchema: ToolcraftRuntime.ToolcraftMaterializedAppSchema = {}; const dimension = null as unknown as ToolcraftModelPerformanceDimension; // @ts-expect-error The performance maximumValue alias was removed. void dimension.maximumValue; const sourceProviderProps = null as unknown as React.ComponentProps< typeof ToolcraftSourceAssetProvider >; // @ts-expect-error Source asset disposal errors use the canonical onError owner. void sourceProviderProps.onDisposeError; void legacySchema; void materializedSchema; } }); 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.renderToolcraftModelsToCanvas).toBe( renderToolcraftModelsToCanvas, ); expect(ToolcraftReact).not.toHaveProperty( "renderToolcraftModelsInWorldContext", ); expect(ToolcraftReact.useToolcraftProductSceneFrame).toBe( useToolcraftProductSceneFrame, ); expect(useToolcraftProductSceneFrame).toEqual(expect.any(Function)); expect(ToolcraftReact.useToolcraftViewportInteractionActive).toBe( useToolcraftViewportInteractionActive, ); expect(useToolcraftViewportInteractionActive).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< ReturnType >().toEqualTypeOf(); 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 = { // @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(); }); });