import { describe, expect, it } from "vitest"; import type { ToolcraftControlSchema } from "../../schema/types"; import type { ToolcraftModelAsset } from "../../state/types"; import { createToolcraftSourceAssetPresentation } from "./source-asset-presentation"; const MODEL_CONTROL: ToolcraftControlSchema = { applicability: { mode: "always" as const }, assetKind: "model", target: "source.model", type: "fileDrop", }; function createModelAsset( lifecycle: ToolcraftModelAsset["lifecycle"], ): ToolcraftModelAsset { const repairPlanRef = lifecycle === "repairable" ? "toolcraft:model-repair-plan:sha256:plan:sha256:envelope" : undefined; return { activeDocumentRef: "toolcraft:model-document:active", analysis: { boundaryEdges: 0, diagnostics: [], disconnectedComponents: 1, nonManifoldEdges: 0, outcome: lifecycle === "repairable" ? "repairable" : "clean", ...(repairPlanRef ? { repairPlanRef } : {}), triangles: 1, vertices: 3, }, assetKind: "model", fileName: "scene.glb", id: "model-1", layerId: "layer-model-1", lifecycle, mimeType: "model/gltf-binary", originalAnalysis: { boundaryEdges: 0, diagnostics: [], disconnectedComponents: 1, nonManifoldEdges: 0, outcome: lifecycle === "repairable" ? "repairable" : "clean", ...(repairPlanRef ? { repairPlanRef } : {}), 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: MODEL_CONTROL.target, topologyProfile: "realtime-mesh", }; } describe("source asset presentation", () => { it("uses an image grid for multiple images", () => { const control: ToolcraftControlSchema = { applicability: { mode: "always" as const }, assetKind: "image", multiple: true, target: "source.images", type: "fileDrop", }; const images = ["one.png", "two.png"].map((fileName, index) => ({ assetKind: "image" as const, fileName, id: `image-${index + 1}`, layerId: `layer-${index + 1}`, lifecycle: "ready" as const, mimeType: "image/png", position: { x: 0, y: 0 }, resourceRef: `media:image:${index + 1}`, size: { height: 96, unit: "px" as const, width: 96 }, sourceSize: { height: 96, unit: "px" as const, width: 96 }, sourceTarget: control.target, })); const presentation = createToolcraftSourceAssetPresentation( "image", control, images, { phase: "idle", target: control.target }, ); expect(presentation.presenter).toBe("image-grid"); expect(presentation.items.map(({ fileName }) => fileName)).toEqual([ "one.png", "two.png", ]); expect(presentation.secondaryActions.map(({ value }) => value)).toEqual([ "rotate-right", "flip-horizontal", "flip-vertical", ]); }); it("offers one half-width-compatible Fix model action only while repairable", () => { const presentation = createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [createModelAsset("repairable")], { phase: "idle", target: MODEL_CONTROL.target }, ); expect(presentation.secondaryActions).toEqual([ expect.objectContaining({ label: "Fix model", state: undefined, value: "model.fix", }), ]); expect(presentation.allowsFileBatch).toBe(true); expect(presentation.allowsFolderSelection).toBe(true); expect( createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [createModelAsset("fixed")], { phase: "idle", target: MODEL_CONTROL.target, }, ).secondaryActions, ).toEqual([]); }); it("keeps folder selection disabled for ordinary file controls", () => { const presentation = createToolcraftSourceAssetPresentation( "file", { applicability: { mode: "always" as const }, assetKind: "file", target: "source.files", type: "fileDrop", }, [], { phase: "idle", target: "source.files" }, ); expect(presentation.allowsFolderSelection).toBe(false); }); it("uses the standard loading button state while repair runs", () => { const presentation = createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [createModelAsset("repairable")], { phase: "repairing", progress: 0.4, target: MODEL_CONTROL.target }, ); expect(presentation.secondaryActions[0]).toMatchObject({ state: { disabled: true, loading: true, loadingAriaLabel: "Fixing model", }, }); }); it("presents persisted restoring and unavailable states through the standard uploader", () => { const restoring = createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [createModelAsset("restoring")], { phase: "idle", target: MODEL_CONTROL.target }, ); const unavailableAsset = { ...createModelAsset("unavailable"), lastRepairError: { category: "resource-unavailable" as const, code: "model-resource-unavailable", message: "The saved model resources are unavailable.", }, }; const unavailable = createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [unavailableAsset], { phase: "idle", target: MODEL_CONTROL.target }, ); expect(restoring.status).toEqual({ label: "Restoring model", phase: "analyzing", }); expect(unavailable.feedback).toEqual(unavailableAsset.lastRepairError); }); it("shows presentation feedback without treating it as topology repair", () => { const feedback = { category: "resource-unavailable" as const, code: "model-presentation-unavailable", message: "The model presentation is unavailable.", }; const presentation = createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [createModelAsset("clean")], { phase: "idle", presentationFeedback: feedback, target: MODEL_CONTROL.target, }, ); expect(presentation.feedback).toEqual(feedback); expect(presentation.secondaryActions).toEqual([]); }); it("keeps a committed model warning visible without offering topology repair", () => { const asset = createModelAsset("clean"); const warningAsset: ToolcraftModelAsset = { ...asset, analysis: { ...asset.analysis, diagnostics: [ { affectedCount: 1, code: "missing-appearance-resource", explanation: "The referenced texture is absent; other material channels remain available.", severity: "warning", }, ], }, }; const presentation = createToolcraftSourceAssetPresentation( "model", MODEL_CONTROL, [warningAsset], { phase: "idle", target: MODEL_CONTROL.target }, ); expect(presentation.feedback).toEqual({ category: "format", code: "missing-appearance-resource", message: "The referenced texture is absent; other material channels remain available.", }); expect(presentation.secondaryActions).toEqual([]); }); });