import { cleanup, render } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { renderFileDropControl } from "./controls-panel-media-renderer"; import { defineToolcraft } from "../../../schema/define-toolcraft"; import type { ToolcraftSourceAssetCoordinator } from "../../../source-assets/source-asset-coordinator"; import type { ToolcraftModelAsset } from "../../../state/types"; import { ToolcraftRoot } from "../../app-shell/toolcraft-root"; import { ToolcraftSourceAssetCoordinatorContext } from "../../app-shell/toolcraft-source-asset-context"; const legacyFileDrop = vi.hoisted(() => vi.fn((_props: unknown, _legacyContext?: unknown) => null), ); vi.mock("@repo/ui", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, FileDrop: legacyFileDrop, }; }); afterEach(() => { cleanup(); legacyFileDrop.mockClear(); }); describe("controls-panel media renderer", () => { it("does not invoke the legacy uploader for model fileDrops", () => { const dispatchCommand = vi.fn(); const idleOperation = { phase: "idle" as const, target: "source.model", }; const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: () => idleOperation, supportsKind: () => false, hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch: vi.fn(), repairModel: vi.fn(), reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, } satisfies ToolcraftSourceAssetCoordinator; const renderedControl = renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control: { applicability: { mode: "always" as const }, assetKind: "model", target: "source.model", type: "fileDrop", }, dispatchCommand, id: "source-model", mediaAssets: [], name: "Model", sourceAssetCoordinator, }); const { container } = render(<>{renderedControl}); expect(container.firstChild).toBeNull(); expect(legacyFileDrop).not.toHaveBeenCalled(); expect(dispatchCommand).not.toHaveBeenCalled(); }); it.each(["file", "model"] as const)( "does not leak selected image actions into %s controls", (assetKind) => { const control = { applicability: { mode: "always" as const }, ...(assetKind === "model" ? { assetKind } : { assetKind }), target: `source.${assetKind}`, type: "fileDrop" as const, }; const operation = { phase: "idle" as const, target: control.target }; const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: vi.fn(() => operation), hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch: vi.fn(), repairModel: vi.fn(), reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, supportsKind: () => true, } satisfies ToolcraftSourceAssetCoordinator; const schema = defineToolcraft({ base: { identity: { id: "runtime-test", title: "Toolcraft fixture", }, canvas: { enabled: true }, panels: {}, }, modules: [], }); render( {renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control, dispatchCommand: vi.fn(), id: `source-${assetKind}`, layerSelectionEnabled: true, mediaAssets: [], name: assetKind, sourceAssetCoordinator, })} , ); const presentation = legacyFileDrop.mock.calls[0]?.[0] as { presentation: { secondaryActions: readonly { value: string; }[]; }; }; expect(presentation.presentation.secondaryActions).toEqual([]); }, ); it("creates the real file presentation when the coordinator supports the kind", () => { const control = { applicability: { mode: "always" as const }, assetKind: "file" as const, target: "source.files", type: "fileDrop" as const, }; const operation = { phase: "idle" as const, target: control.target }; const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: vi.fn(() => operation), supportsKind: (kind) => kind === "file", hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch: vi.fn(), repairModel: vi.fn(), reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, } satisfies ToolcraftSourceAssetCoordinator; render( <> {renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control, dispatchCommand: vi.fn(), id: "source-files", mediaAssets: [], name: "Source files", sourceAssetCoordinator, })} , ); expect(legacyFileDrop).toHaveBeenCalledWith( expect.objectContaining({ presentation: { accept: "", allowsFileBatch: false, allowsFolderSelection: false, emptyDescription: "or drag it onto the canvas", emptyTitle: "Click to upload a file", items: [], presenter: "file-list", secondaryActions: [], }, }), undefined, ); expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual( expect.objectContaining({ collectionActions: undefined, onFolderSelect: undefined, }), ); }); it("maps the collection-actions variant to compact FileDrop cardinality controls", () => { const control = { applicability: { mode: "always" as const }, assetKind: "file" as const, itemLabel: "bump map", multiple: true, target: "surface.bumpMaps", type: "fileDrop" as const, variant: "collection-actions", } as const; const operation = { phase: "idle" as const, target: control.target }; const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: vi.fn(() => operation), supportsKind: (kind) => kind === "file", hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch: vi.fn(), repairModel: vi.fn(), reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, } satisfies ToolcraftSourceAssetCoordinator; render( <> {renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control, dispatchCommand: vi.fn(), id: "surface-bump-maps", mediaAssets: [ { assetKind: "file", fileName: "grain.png", id: "bump-grain", layerId: "layer-bump-grain", lifecycle: "ready", mimeType: "image/png", position: { x: 0, y: 0 }, resourceRef: "media:file:grain", sourceTarget: control.target, }, ], name: "Bump maps", sourceAssetCoordinator, })} , ); expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual( expect.objectContaining({ collectionActions: { addLabel: "Add bump map", canAdd: true, itemLabel: "bump map", name: "Bump maps", removeLabel: "Remove bump map", }, }), ); }); it("creates the real image presentation for an available image handler", () => { const control = { applicability: { mode: "always" as const }, assetKind: "image" as const, multiple: true, target: "source.images", type: "fileDrop" as const, }; const operation = { phase: "idle" as const, target: control.target }; const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: vi.fn(() => operation), hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch: vi.fn(), repairModel: vi.fn(), reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, supportsKind: (kind) => kind === "image", } satisfies ToolcraftSourceAssetCoordinator; render( <> {renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control, dispatchCommand: vi.fn(), id: "source-images", mediaAssets: [ { assetKind: "image", fileName: "source.png", id: "image-1", layerId: "layer-image-1", lifecycle: "ready", mimeType: "image/png", position: { x: 0, y: 0 }, resourceRef: "media:image:source", size: { height: 96, unit: "px", width: 96 }, sourceSize: { height: 96, unit: "px", width: 96 }, sourceTarget: control.target, }, ], name: "Images", sourceAssetCoordinator, })} , ); expect(legacyFileDrop.mock.calls[0]?.[0]).toEqual( expect.objectContaining({ presentation: expect.objectContaining({ emptyTitle: "Click to upload an image", items: [ expect.objectContaining({ fileName: "source.png", id: "image-1" }), ], presenter: "single-image", }), }), ); }); it("routes model file and folder picks through one coordinator import path", () => { const control = { applicability: { mode: "always" as const }, assetKind: "model" as const, target: "source.model", type: "fileDrop" as const, }; const operation = { phase: "idle" as const, target: control.target }; const importBatch = vi.fn(); const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: vi.fn(() => operation), supportsKind: (kind) => kind === "model", hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch, repairModel: vi.fn(), reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, } satisfies ToolcraftSourceAssetCoordinator; render( <> {renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control, dispatchCommand: vi.fn(), id: "source-model", mediaAssets: [], name: "Model", sourceAssetCoordinator, })} , ); const fileDropProps = legacyFileDrop.mock.calls[0]?.[0] as unknown as { onFilesSelect: (files: File[]) => void; onFolderSelect: (files: File[]) => void; }; const files = [new File([new Uint8Array([1])], "scene.gltf")]; expect(fileDropProps.onFolderSelect).toBe(fileDropProps.onFilesSelect); fileDropProps.onFolderSelect(files); expect(importBatch).toHaveBeenCalledWith( { files, origin: "panel", target: "source.model" }, control, ); }); it("routes the built-in model repair action to the source coordinator", () => { const control = { applicability: { mode: "always" as const }, assetKind: "model" as const, target: "source.model", type: "fileDrop" as const, }; const operation = { phase: "idle" as const, target: control.target }; const modelAsset: ToolcraftModelAsset = { activeDocumentRef: "toolcraft:model-document:active", analysis: { boundaryEdges: 0, diagnostics: [], disconnectedComponents: 1, nonManifoldEdges: 0, outcome: "repairable", repairPlanRef: "toolcraft:model-repair-plan:sha256:plan:sha256:envelope", triangles: 1, vertices: 3, }, assetKind: "model", fileName: "scene.glb", id: "model-1", layerId: "layer-model-1", lifecycle: "repairable", mimeType: "model/gltf-binary", originalAnalysis: { boundaryEdges: 0, diagnostics: [], disconnectedComponents: 1, nonManifoldEdges: 0, outcome: "repairable", repairPlanRef: "toolcraft:model-repair-plan:sha256:plan:sha256:envelope", triangles: 1, vertices: 3, }, originalDocumentRef: "toolcraft:model-document:original", position: { x: 0, y: 0 }, size: { height: 512, unit: "px", width: 512 }, sourceBundleDigest: "sha256:source", sourceBundleRef: "toolcraft:model-source-bundle:source", sourceTarget: control.target, topologyProfile: "realtime-mesh", }; const repairModel = vi.fn(async () => ({ assetIds: ["model-1"], kind: "committed" as const, })); const sourceAssetCoordinator = { cancelTarget: vi.fn(), clearPresentationFeedback: vi.fn(), dispose: vi.fn(async () => undefined), getOperation: vi.fn(() => operation), supportsKind: (kind) => kind === "model", hydrateBinaryMedia: vi.fn(async () => undefined), hydrateModels: vi.fn(async () => undefined), importBatch: vi.fn(), repairModel, reportPresentationFeedback: vi.fn(), subscribe: () => () => undefined, } satisfies ToolcraftSourceAssetCoordinator; render( <> {renderFileDropControl({ canvasSize: { height: 720, unit: "px", width: 1280 }, control, dispatchCommand: vi.fn(), id: "source-model", mediaAssets: [modelAsset], name: "Model", sourceAssetCoordinator, })} , ); const fileDropCalls = legacyFileDrop.mock.calls as unknown as Array< [ { onAction?: (value: string) => void; }, ] >; const onAction = fileDropCalls[0]?.[0].onAction; expect(onAction).toBeTypeOf("function"); onAction?.("model.fix"); expect(repairModel).toHaveBeenCalledWith("model-1"); }); });