import { describe, expect, it } from "vitest"; import { canvasEditingModule } from "./built-ins/canvas-editing/canvas-editing-module"; import type { ToolcraftProductModuleContribution } from "./contract/contribution"; import { assertToolcraftProductModuleDefinition, type ToolcraftProductModuleDefinition, type ToolcraftProductModuleRecord, } from "./contract/module-definition"; import { resolveToolcraftModuleDependencies } from "./testing/module-resolution-test-support"; import { resolveToolcraftModulePortRequirements } from "./resolution/resolve-product-modules"; import { resolveToolcraftProductModules } from "./testing/module-resolution-test-support"; import { createTestToolcraftProductModuleDefinition, unsafeCastTestOnlyToolcraftProductModuleRecord, } from "./testing/create-test-module-definition"; type WithExtensionMetadata = T & { extensionMetadata: { readonly source: string }; }; type ContributionById< Id extends ToolcraftProductModuleContribution["id"], > = Extract; const wrongProjectBKind = { behavior: "editing", id: "canvas-editing.behavior", kind: "media-policy", moduleId: "canvas-editing", operations: ["drag", "handles", "resize", "selection"], } as const; // @ts-expect-error Canvas editing behavior ids cannot identify media policies. const wrongProjectBKindContribution: ToolcraftProductModuleContribution = wrongProjectBKind; void wrongProjectBKindContribution; const wrongProjectBOwner = { behavior: "spatial-view", id: "spatial-view.behavior", kind: "canvas-behavior", moduleId: "canvas-editing", operations: ["gizmo", "orbit", "paused-live-sync", "pose"], } as const; // @ts-expect-error Spatial-view behavior belongs only to the spatial-view module. const wrongProjectBOwnerContribution: ToolcraftProductModuleContribution = wrongProjectBOwner; void wrongProjectBOwnerContribution; const layersSurfaceAuthoredAsTimeline = { id: "layers.surface", kind: "panel-surface", moduleId: "layers", surface: "timeline", configuration: { enabled: true }, } as const; // @ts-expect-error The layers surface cannot be authored as a timeline surface. const wrongLayersSurfaceContribution: ToolcraftProductModuleContribution = layersSurfaceAuthoredAsTimeline; void wrongLayersSurfaceContribution; const layersPersistenceWithMediaSlice = { id: "layers.persistence", kind: "persistence-requirement", moduleId: "layers", slice: "media", } as const; // @ts-expect-error Layers persistence owns only the layers slice. const wrongLayersPersistenceContribution: ToolcraftProductModuleContribution = layersPersistenceWithMediaSlice; void wrongLayersPersistenceContribution; const mediaPersistenceWithLayersSlice = { id: "media-source.persistence", kind: "persistence-requirement", moduleId: "media-source", slice: "layers", } as const; // @ts-expect-error Media-source persistence owns only the media slice. const wrongMediaPersistenceContribution: ToolcraftProductModuleContribution = mediaPersistenceWithLayersSlice; void wrongMediaPersistenceContribution; const editingWithUnsupportedOperation = { behavior: "editing", id: "canvas-editing.behavior", kind: "canvas-behavior", moduleId: "canvas-editing", operations: ["drag", "handles", "rotate", "selection"], } as const; // @ts-expect-error Canvas editing operations are a closed exact tuple. const unsupportedOperationContribution: ToolcraftProductModuleContribution = editingWithUnsupportedOperation; void unsupportedOperationContribution; const mediaPolicyWithUnsupportedSourceKind = { id: "media-source.policy", kind: "media-policy", moduleId: "media-source", policy: "source-workflow", sourceKinds: ["file", "image", "video"], } as const; // @ts-expect-error Media source kinds are the exact file, image, model tuple. const unsupportedSourceKindContribution: ToolcraftProductModuleContribution = mediaPolicyWithUnsupportedSourceKind; void unsupportedSourceKindContribution; const ordinaryProjectBModuleRecord = { contractVersion: 2, contributions: [], defaultProviders: [], id: "model-3d", portRequirements: [], provides: ["model.3d"], requires: [], } as const satisfies ToolcraftProductModuleRecord; function assertResolversRequireOpaqueDefinitions() { // @ts-expect-error Dependency resolution rejects ordinary raw records. resolveToolcraftModuleDependencies([ordinaryProjectBModuleRecord]); // @ts-expect-error Product resolution rejects ordinary raw records. resolveToolcraftProductModules([ordinaryProjectBModuleRecord]); // @ts-expect-error Internal port resolution requires opaque definitions. resolveToolcraftModulePortRequirements([ordinaryProjectBModuleRecord]); } void assertResolversRequireOpaqueDefinitions; // @ts-expect-error Ordinary records do not carry the private definition brand. const ordinaryProjectBDefinition: ToolcraftProductModuleDefinition = ordinaryProjectBModuleRecord; void ordinaryProjectBDefinition; describe("Project B Toolcraft product module definitions", () => { it("rejects spread clones instead of trusting copied private symbols", () => { const source = canvasEditingModule(); const spreadClone = unsafeCastTestOnlyToolcraftProductModuleRecord({ ...source, contributions: [], }); expect(Object.getOwnPropertySymbols(spreadClone)).toEqual( Object.getOwnPropertySymbols(source), ); expect(() => assertToolcraftProductModuleDefinition(spreadClone), ).toThrowError( 'Toolcraft product module "canvas-editing" is not an opaque definition created by a Toolcraft module factory; pass the factory result without copying or mutation.', ); expect(() => assertToolcraftProductModuleDefinition(source), ).not.toThrow(); }); it("rejects malformed id-correlated fields before projection", () => { const mismatchedBehavior = { behavior: "spatial-view", id: "canvas-editing.behavior", kind: "canvas-behavior", moduleId: "canvas-editing", operations: ["gizmo", "orbit", "paused-live-sync", "pose"], } as const; expect(() => createTestToolcraftProductModuleDefinition({ // @ts-expect-error Runtime validation rejects malformed id-correlated behavior fields. contributions: [mismatchedBehavior], defaultProviders: [], id: "canvas-editing", portRequirements: [], provides: ["canvas.editing"], requires: [], }), ).toThrowError( 'Invalid Toolcraft module contribution "canvas-editing.behavior": payload does not match its closed contract.', ); }); it("normalizes every Project B contribution into an exact immutable definition", () => { const editingOperations: ["drag", "handles", "resize", "selection"] = [ "drag", "handles", "resize", "selection", ]; const spatialOperations: [ "gizmo", "orbit", "paused-live-sync", "pose", ] = ["gizmo", "orbit", "paused-live-sync", "pose"]; const sourceKinds: ["file", "image", "model"] = [ "file", "image", "model", ]; const canvasBehavior: WithExtensionMetadata< ContributionById<"canvas-editing.behavior"> > = { behavior: "editing", extensionMetadata: { source: "test" }, id: "canvas-editing.behavior", kind: "canvas-behavior", moduleId: "canvas-editing", operations: editingOperations, }; const layersPersistence: WithExtensionMetadata< ContributionById<"layers.persistence"> > = { extensionMetadata: { source: "test" }, id: "layers.persistence", kind: "persistence-requirement", moduleId: "layers", slice: "layers", }; const layersSurface: WithExtensionMetadata< ContributionById<"layers.surface"> > = { extensionMetadata: { source: "test" }, id: "layers.surface", kind: "panel-surface", configuration: true, moduleId: "layers", surface: "layers", }; const mediaPersistence: WithExtensionMetadata< ContributionById<"media-source.persistence"> > = { extensionMetadata: { source: "test" }, id: "media-source.persistence", kind: "persistence-requirement", moduleId: "media-source", slice: "media", }; const mediaPolicy: WithExtensionMetadata< ContributionById<"media-source.policy"> > = { extensionMetadata: { source: "test" }, id: "media-source.policy", kind: "media-policy", moduleId: "media-source", policy: "source-workflow", sourceKinds, }; const spatialBehavior: WithExtensionMetadata< ContributionById<"spatial-view.behavior"> > = { behavior: "spatial-view", extensionMetadata: { source: "test" }, id: "spatial-view.behavior", kind: "canvas-behavior", moduleId: "spatial-view", operations: spatialOperations, }; const definitions = [ createTestToolcraftProductModuleDefinition({ contributions: [canvasBehavior], defaultProviders: [], id: "canvas-editing", portRequirements: [ { applicability: "always", id: "scene.canvasContent" }, ], provides: ["canvas.editing"], requires: [], }), createTestToolcraftProductModuleDefinition({ contributions: [layersPersistence, layersSurface], defaultProviders: [], id: "layers", portRequirements: [], provides: ["layers.management"], requires: [], }), createTestToolcraftProductModuleDefinition({ contributions: [mediaPersistence, mediaPolicy], defaultProviders: [ { capabilityId: "media.source", providerId: "media.source-default", }, ], id: "media-source", portRequirements: [], provides: ["media.source"], requires: [], }), createTestToolcraftProductModuleDefinition({ contributions: [], defaultProviders: [], id: "model-3d", portRequirements: [ { applicability: "product-scene", id: "modelPresentation" }, ], provides: ["model.3d"], requires: ["media.source"], }), createTestToolcraftProductModuleDefinition({ contributions: [spatialBehavior], defaultProviders: [], id: "spatial-view", portRequirements: [ { applicability: "spatial-product-scene", id: "scene.canvasContent", }, ], provides: ["spatial.view"], requires: [], }), ]; for (const definition of definitions) { expect(definition.contractVersion).toBe(2); expect(Object.isFrozen(definition)).toBe(true); expect(Object.isFrozen(definition.contributions)).toBe(true); expect(Object.isFrozen(definition.defaultProviders)).toBe(true); expect(Object.isFrozen(definition.portRequirements)).toBe(true); expect(Object.isFrozen(definition.provides)).toBe(true); expect(Object.isFrozen(definition.requires)).toBe(true); expect(definition.contributions.every(Object.isFrozen)).toBe(true); expect(definition.defaultProviders.every(Object.isFrozen)).toBe(true); expect(definition.portRequirements.every(Object.isFrozen)).toBe(true); } const normalizedContributions = definitions.flatMap( ({ contributions }) => contributions, ); expect(normalizedContributions).toEqual([ { behavior: "editing", id: "canvas-editing.behavior", kind: "canvas-behavior", moduleId: "canvas-editing", operations: ["drag", "handles", "resize", "selection"], }, { id: "layers.persistence", kind: "persistence-requirement", moduleId: "layers", slice: "layers", }, { id: "layers.surface", kind: "panel-surface", configuration: true, moduleId: "layers", surface: "layers", }, { id: "media-source.persistence", kind: "persistence-requirement", moduleId: "media-source", slice: "media", }, { id: "media-source.policy", kind: "media-policy", moduleId: "media-source", policy: "source-workflow", sourceKinds: ["file", "image", "model"], }, { behavior: "spatial-view", id: "spatial-view.behavior", kind: "canvas-behavior", moduleId: "spatial-view", operations: ["gizmo", "orbit", "paused-live-sync", "pose"], }, ]); for (const contribution of normalizedContributions) { expect(Object.hasOwn(contribution, "extensionMetadata")).toBe(false); if (contribution.kind === "canvas-behavior") { expect(Object.isFrozen(contribution.operations)).toBe(true); } if (contribution.kind === "media-policy") { expect(Object.isFrozen(contribution.sourceKinds)).toBe(true); } } const normalizedEditing = normalizedContributions[0]; const normalizedMediaPolicy = normalizedContributions[4]; const normalizedSpatial = normalizedContributions[5]; if ( normalizedEditing?.kind !== "canvas-behavior" || normalizedMediaPolicy?.kind !== "media-policy" || normalizedSpatial?.kind !== "canvas-behavior" ) { throw new Error("Expected normalized Project B tuple contributions."); } expect(normalizedEditing.operations).not.toBe(editingOperations); expect(normalizedMediaPolicy.sourceKinds).not.toBe(sourceKinds); expect(normalizedSpatial.operations).not.toBe(spatialOperations); editingOperations.reverse(); sourceKinds.reverse(); spatialOperations.reverse(); expect(normalizedEditing.operations).toEqual([ "drag", "handles", "resize", "selection", ]); expect(normalizedMediaPolicy.sourceKinds).toEqual([ "file", "image", "model", ]); expect(normalizedSpatial.operations).toEqual([ "gizmo", "orbit", "paused-live-sync", "pose", ]); }); });