import { describe, expect, it } from "vitest"; import { createBoxBrush } from "../../src/document/brushes"; import { createEmptySceneDocument } from "../../src/document/scene-document"; import { validateSceneDocument } from "../../src/document/scene-document-validation"; import { createInteractableEntity, createPlayerStartEntity, createSoundEmitterEntity, createTeleportTargetEntity, createTriggerVolumeEntity } from "../../src/entities/entity-instances"; describe("validateSceneDocument", () => { it("accepts a valid first-room document", () => { const brush = createBoxBrush({ id: "brush-room-shell" }); const playerStart = createPlayerStartEntity({ id: "entity-player-start-main" }); const document = { ...createEmptySceneDocument({ name: "First Room" }), brushes: { [brush.id]: brush }, entities: { [playerStart.id]: playerStart } }; const validation = validateSceneDocument(document); expect(validation.errors).toEqual([]); expect(validation.warnings).toEqual([]); }); it("detects duplicate authored ids across collections", () => { const brush = createBoxBrush({ id: "shared-room-id" }); const playerStart = createPlayerStartEntity({ id: "shared-room-id" }); const document = { ...createEmptySceneDocument(), brushes: { [brush.id]: brush }, entities: { "entity-player-start-main": playerStart } }; const validation = validateSceneDocument(document); expect(validation.errors).toEqual( expect.arrayContaining([ expect.objectContaining({ code: "entity-id-mismatch" }), expect.objectContaining({ code: "duplicate-authored-id" }) ]) ); }); it("detects invalid box sizes and missing material references", () => { const brush = createBoxBrush({ id: "brush-invalid" }); brush.size.x = 0; brush.faces.posZ.materialId = "material-that-does-not-exist"; const validation = validateSceneDocument({ ...createEmptySceneDocument(), brushes: { [brush.id]: brush } }); expect(validation.errors).toEqual( expect.arrayContaining([ expect.objectContaining({ code: "invalid-box-size", path: "brushes.brush-invalid.size" }), expect.objectContaining({ code: "missing-material-ref", path: "brushes.brush-invalid.faces.posZ.materialId" }) ]) ); }); it("detects invalid Player Start values", () => { const validation = validateSceneDocument({ ...createEmptySceneDocument(), entities: { "entity-player-start-main": { id: "entity-player-start-main", kind: "playerStart", position: { x: 0, y: Number.NaN, z: 0 }, yawDegrees: Number.NaN } } }); expect(validation.errors).toEqual( expect.arrayContaining([ expect.objectContaining({ code: "invalid-player-start-position" }), expect.objectContaining({ code: "invalid-player-start-yaw" }) ]) ); }); it("detects invalid typed entity values across the entity registry", () => { const soundEmitter = createSoundEmitterEntity({ id: "entity-sound-main" }); const triggerVolume = createTriggerVolumeEntity({ id: "entity-trigger-main" }); const teleportTarget = createTeleportTargetEntity({ id: "entity-teleport-main" }); const interactable = createInteractableEntity({ id: "entity-interactable-main" }); const validation = validateSceneDocument({ ...createEmptySceneDocument(), entities: { [soundEmitter.id]: { ...soundEmitter, radius: Number.NaN }, [triggerVolume.id]: { ...triggerVolume, size: { x: 0, y: 2, z: 2 } }, [teleportTarget.id]: { ...teleportTarget, yawDegrees: Number.POSITIVE_INFINITY }, [interactable.id]: { ...interactable, prompt: " ", enabled: "yes" as unknown as boolean } } }); expect(validation.errors).toEqual( expect.arrayContaining([ expect.objectContaining({ code: "invalid-sound-emitter-radius" }), expect.objectContaining({ code: "invalid-trigger-volume-size" }), expect.objectContaining({ code: "invalid-teleport-target-yaw" }), expect.objectContaining({ code: "invalid-interactable-prompt" }), expect.objectContaining({ code: "invalid-interactable-enabled" }) ]) ); }); it("detects invalid world lighting and background settings", () => { const document = createEmptySceneDocument(); document.world.background = { mode: "verticalGradient", topColorHex: "sky-blue", bottomColorHex: "#18212b" }; document.world.ambientLight.intensity = -0.25; document.world.sunLight.direction = { x: 0, y: 0, z: 0 }; const validation = validateSceneDocument(document); expect(validation.errors).toEqual( expect.arrayContaining([ expect.objectContaining({ code: "invalid-world-background-top-color", path: "world.background.topColorHex" }), expect.objectContaining({ code: "invalid-world-ambient-intensity", path: "world.ambientLight.intensity" }), expect.objectContaining({ code: "invalid-world-sun-direction", path: "world.sunLight.direction" }) ]) ); }); });