import { describe, expect, it } from "vitest"; import type { GLTF } from "@gltf-transform/core"; import * as modelImportBarrel from "../index"; import * as formatsBarrel from "./index"; import { createToolcraftModelFormatAdapterRegistry } from "./model-format-adapter"; import { contextFor, createGlbTransfer, createGltfTransfer, createHierarchyFixture, createNormalizedQuantizedFixture, createPrimitiveFixture, } from "../test-fixtures/model-fixtures"; import { TOOLCRAFT_GLTF_ADAPTER_VERSION, TOOLCRAFT_GLTF_GEOMETRY_DECODER_VERSION, TOOLCRAFT_GLTF_MODEL_FORMAT_ADAPTERS, toolcraftGlbModelFormatAdapter, toolcraftGltfModelFormatAdapter, } from "./gltf-model-format-adapter"; describe("glTF model format adapters", () => { it("keeps concrete adapters out of main barrels while exporting lightweight versions", () => { for (const barrel of [formatsBarrel, modelImportBarrel]) { expect("toolcraftGlbModelFormatAdapter" in barrel).toBe(false); expect("toolcraftGltfModelFormatAdapter" in barrel).toBe(false); expect("TOOLCRAFT_GLTF_MODEL_FORMAT_ADAPTERS" in barrel).toBe(false); expect(barrel.TOOLCRAFT_GLTF_ADAPTER_VERSION).toBe( TOOLCRAFT_GLTF_ADAPTER_VERSION, ); expect(barrel.TOOLCRAFT_GLTF_GEOMETRY_DECODER_VERSION).toBe( TOOLCRAFT_GLTF_GEOMETRY_DECODER_VERSION, ); } }); it("registers one immutable adapter per format with matching provenance", async () => { expect(TOOLCRAFT_GLTF_MODEL_FORMAT_ADAPTERS).toEqual([ toolcraftGlbModelFormatAdapter, toolcraftGltfModelFormatAdapter, ]); expect(toolcraftGlbModelFormatAdapter).toMatchObject({ format: "glb", rootExtensions: [".glb"], workerCapable: true, }); expect(toolcraftGltfModelFormatAdapter).toMatchObject({ format: "gltf", rootExtensions: [".gltf"], workerCapable: true, }); expect(Object.isFrozen(TOOLCRAFT_GLTF_MODEL_FORMAT_ADAPTERS)).toBe(true); expect(Object.isFrozen(toolcraftGlbModelFormatAdapter)).toBe(true); expect(Object.isFrozen(toolcraftGltfModelFormatAdapter)).toBe(true); expect( createToolcraftModelFormatAdapterRegistry( TOOLCRAFT_GLTF_MODEL_FORMAT_ADAPTERS, ).adapters.map(({ format }) => format), ).toEqual(["glb", "gltf"]); const fixture = createPrimitiveFixture(); const [glb, gltf] = await Promise.all([ toolcraftGlbModelFormatAdapter.decode( contextFor(createGlbTransfer(fixture)), ), toolcraftGltfModelFormatAdapter.decode( contextFor(createGltfTransfer(fixture)), ), ]); expect(glb.document.provenance.sourceFormat).toBe("glb"); expect(gltf.document.provenance.sourceFormat).toBe("gltf"); }); it("decodes GLB and glTF plus an exact external bin without baking transforms", async () => { const fixture = createHierarchyFixture(); const glb = await toolcraftGlbModelFormatAdapter.decode( contextFor(createGlbTransfer(fixture)), ); const gltf = await toolcraftGltfModelFormatAdapter.decode( contextFor( createGltfTransfer( { ...fixture, json: { ...fixture.json, buffers: [{ ...fixture.json.buffers![0], uri: "buffers/mesh.bin" }] } }, "models/scene.gltf", "models/buffers/mesh.bin", ), ), ); for (const result of [glb, gltf]) { expect(result.document.rootNodeIds).toEqual(["node:0", "node:2"]); expect(result.document.nodes).toEqual([ { children: ["node:1"], id: "node:0", localMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 0, 0, 1], name: "Parent", primitiveIds: [], }, { children: [], id: "node:1", localMatrix: [2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 5, 6, 7, 1], name: "Child", primitiveIds: ["primitive:0", "primitive:1"], }, { children: [], id: "node:2", localMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -50, 0, 0, 1], name: "Instance", primitiveIds: ["primitive:0", "primitive:1"], }, ]); expect(result.document.primitives).toHaveLength(2); expect(result.document.primitives[0]!.positions).toEqual( new Float32Array([0, 0, 0, 2, 0, 0, 0, 3, 0]), ); expect(result.document.bounds).toEqual({ max: [2, 3, 1], min: [-4, -2, 0], }); } }); it("maps raw buffer URIs to canonical bundle paths without guessing", async () => { const fixture = createPrimitiveFixture(); fixture.json.buffers![0]!.uri = "buffers/mesh.bin"; const valid = createGltfTransfer( fixture, "models/scene.gltf", "models/buffers/mesh.bin", ); await expect( toolcraftGltfModelFormatAdapter.decode(contextFor(valid)), ).resolves.toBeDefined(); const mismatched = createGltfTransfer( fixture, "models/scene.gltf", "models/mesh.bin", ); await expect( toolcraftGltfModelFormatAdapter.decode(contextFor(mismatched)), ).rejects.toMatchObject({ category: "bundle", code: "missing-buffer-file", }); }); it("creates sequential indices for non-indexed input and records provenance", async () => { const result = await toolcraftGlbModelFormatAdapter.decode( contextFor(createGlbTransfer(createPrimitiveFixture({ indices: null }))), ); expect(result.document.primitives[0]!.indices).toEqual( new Uint32Array([0, 1, 2]), ); expect(result.document.provenance.operations).toEqual([ "sequential-indexing", ]); }); it("decodes normalized integer POSITION and NORMAL accessors through accessor semantics", async () => { const result = await toolcraftGlbModelFormatAdapter.decode( contextFor(createGlbTransfer(createNormalizedQuantizedFixture())), ); expect(result.document.primitives[0]!.positions).toEqual( new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]), ); expect(result.document.primitives[0]!.normals).toEqual( new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1]), ); }); it.each([ [4, [0, 1, 2, 0, 2, 3], [0, 1, 2, 0, 2, 3], []], [5, [0, 1, 2, 3], [0, 1, 2, 2, 1, 3], ["deterministic-triangulation"]], [6, [0, 1, 2, 3], [0, 1, 2, 0, 2, 3], ["deterministic-triangulation"]], ] as const)( "canonicalizes primitive mode %s deterministically", async (mode, indices, expected, operations) => { const fixture = createPrimitiveFixture({ indices, mode: mode as GLTF.MeshPrimitiveMode, positions: [0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0], }); const result = await toolcraftGlbModelFormatAdapter.decode( contextFor(createGlbTransfer(fixture)), ); expect(result.document.primitives[0]!.indices).toEqual( new Uint32Array(expected), ); expect(result.document.provenance.operations).toEqual(operations); }, ); it.each([0, 1, 2, 3] as const)( "rejects unsupported points/lines primitive mode %s", async (mode) => { await expect( toolcraftGlbModelFormatAdapter.decode( contextFor( createGlbTransfer( createPrimitiveFixture({ mode: mode as GLTF.MeshPrimitiveMode }), ), ), ), ).rejects.toMatchObject({ category: "format", code: "unsupported-primitive-mode", }); }, ); it.each([ ["triangle cardinality", createPrimitiveFixture({ indices: [0, 1, 2, 0] }), "malformed-triangle-primitive"], ["short strip", createPrimitiveFixture({ indices: [0, 1], mode: 5 }), "malformed-triangle-primitive"], ["short fan", createPrimitiveFixture({ indices: [0, 1], mode: 6 }), "malformed-triangle-primitive"], ["corrupt index", createPrimitiveFixture({ indices: [0, 1, 9] }), "index-out-of-range"], ] as const)("rejects malformed %s input", async (_name, fixture, code) => { await expect( toolcraftGlbModelFormatAdapter.decode( contextFor(createGlbTransfer(fixture)), ), ).rejects.toMatchObject({ category: "geometry", code }); }); });