From 304d3ccec1a16a556f269e34f61797a982a87fe2 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Sat, 2 May 2026 03:48:47 +0200 Subject: [PATCH] auto-git: [add] tests/domain/foliage.test.ts --- tests/domain/foliage.test.ts | 169 +++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 tests/domain/foliage.test.ts diff --git a/tests/domain/foliage.test.ts b/tests/domain/foliage.test.ts new file mode 100644 index 00000000..01826459 --- /dev/null +++ b/tests/domain/foliage.test.ts @@ -0,0 +1,169 @@ +import { describe, expect, it } from "vitest"; + +import { + createProjectAssetStorageKey, + type ModelAssetRecord +} from "../../src/assets/project-assets"; +import { + createEmptySceneDocument, + createSceneDocumentFromProject, + createEmptyProjectDocument +} from "../../src/document/scene-document"; +import { validateSceneDocument } from "../../src/document/scene-document-validation"; +import { BUNDLED_FOLIAGE_PROTOTYPES } from "../../src/foliage/bundled-foliage-manifest"; +import { + FOLIAGE_PROTOTYPE_LOD_LEVELS, + createFoliageLayer, + createFoliagePrototype, + type FoliagePrototypeLod +} from "../../src/foliage/foliage"; + +function createModelAsset(id: string): ModelAssetRecord { + return { + id, + kind: "model", + sourceName: `${id}.glb`, + mimeType: "model/gltf-binary", + storageKey: createProjectAssetStorageKey(id), + byteLength: 1024, + metadata: { + kind: "model", + format: "glb", + sceneName: null, + nodeCount: 1, + meshCount: 1, + materialNames: [], + textureNames: [], + animationNames: [], + boundingBox: null, + warnings: [] + } + }; +} + +function createProjectAssetLods(modelAssetId: string): FoliagePrototypeLod[] { + return FOLIAGE_PROTOTYPE_LOD_LEVELS.map((level) => ({ + level, + source: "projectAsset", + modelAssetId, + maxDistance: 20 + level * 20, + castShadow: level < 2 + })); +} + +describe("foliage document foundations", () => { + it("accepts scene foliage layers that reference bundled prototype ids", () => { + const bundledPrototype = BUNDLED_FOLIAGE_PROTOTYPES[0]; + const document = createEmptySceneDocument(); + const layer = createFoliageLayer({ + id: "foliage-layer-meadow", + name: "Meadow", + prototypeIds: [bundledPrototype.id] + }); + + document.foliageLayers[layer.id] = layer; + + expect(validateSceneDocument(document).errors).toEqual([]); + }); + + it("accepts custom project-asset sourced foliage prototypes", () => { + const modelAsset = createModelAsset("asset-custom-foliage"); + const document = createEmptySceneDocument(); + const prototype = createFoliagePrototype({ + id: "foliage-custom-clover", + label: "Custom Clover", + category: "grass", + lods: createProjectAssetLods(modelAsset.id) + }); + const layer = createFoliageLayer({ + id: "foliage-layer-custom", + name: "Custom Foliage", + prototypeIds: [prototype.id] + }); + + document.assets[modelAsset.id] = modelAsset; + document.foliagePrototypes[prototype.id] = prototype; + document.foliageLayers[layer.id] = layer; + + expect(validateSceneDocument(document).errors).toEqual([]); + }); + + it("validates layer ranges and prototype references", () => { + const document = createEmptySceneDocument(); + const layer = createFoliageLayer({ + id: "foliage-layer-invalid", + name: "Invalid", + prototypeIds: ["missing-foliage-prototype"] + }); + + document.foliageLayers[layer.id] = { + ...layer, + minScale: 2, + maxScale: 1, + maxSlopeDegrees: 120, + noiseStrength: 1.5 + }; + + expect(validateSceneDocument(document).errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ code: "invalid-foliage-layer-scale-range" }), + expect.objectContaining({ code: "invalid-foliage-layer-max-slope" }), + expect.objectContaining({ + code: "invalid-foliage-layer-noise-strength" + }), + expect.objectContaining({ code: "missing-foliage-layer-prototype" }) + ]) + ); + }); + + it("validates custom project-asset LOD references", () => { + const document = createEmptySceneDocument(); + const prototype = createFoliagePrototype({ + id: "foliage-missing-model", + label: "Missing Model", + category: "other", + lods: createProjectAssetLods("asset-missing") + }); + + document.foliagePrototypes[prototype.id] = prototype; + + expect(validateSceneDocument(document).errors).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + code: "missing-foliage-prototype-model-asset" + }) + ]) + ); + }); + + it("keeps project custom prototypes global and layers scene-local", () => { + const modelAsset = createModelAsset("asset-project-foliage"); + const project = createEmptyProjectDocument({ + assets: { + [modelAsset.id]: modelAsset + } + }); + const prototype = createFoliagePrototype({ + id: "foliage-project-reed", + label: "Project Reed", + category: "grass", + lods: createProjectAssetLods(modelAsset.id) + }); + const layer = createFoliageLayer({ + id: "foliage-layer-project-reed", + name: "Project Reed Layer", + prototypeIds: [prototype.id] + }); + + project.foliagePrototypes[prototype.id] = prototype; + project.scenes[project.activeSceneId]!.foliageLayers[layer.id] = layer; + + const sceneDocument = createSceneDocumentFromProject(project); + + expect(sceneDocument.foliagePrototypes).toEqual(project.foliagePrototypes); + expect(sceneDocument.foliageLayers).toEqual( + project.scenes[project.activeSceneId]!.foliageLayers + ); + expect(validateSceneDocument(sceneDocument).errors).toEqual([]); + }); +});