Add tests for playAnimation interaction link validation and runtime dispatch
This commit is contained in:
@@ -3,8 +3,14 @@ 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 { createModelInstance } from "../../src/assets/model-instances";
|
||||
import { createProjectAssetStorageKey, type ModelAssetRecord } from "../../src/assets/project-assets";
|
||||
import { createInteractableEntity, createPlayerStartEntity, createTeleportTargetEntity, createTriggerVolumeEntity } from "../../src/entities/entity-instances";
|
||||
import { createTeleportPlayerInteractionLink, createToggleVisibilityInteractionLink } from "../../src/interactions/interaction-links";
|
||||
import {
|
||||
createPlayAnimationInteractionLink,
|
||||
createTeleportPlayerInteractionLink,
|
||||
createToggleVisibilityInteractionLink
|
||||
} from "../../src/interactions/interaction-links";
|
||||
|
||||
describe("interaction link validation", () => {
|
||||
it("accepts valid Trigger Volume and Interactable links", () => {
|
||||
@@ -125,4 +131,67 @@ describe("interaction link validation", () => {
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("detects playAnimation links that reference a missing clip on the target model asset", () => {
|
||||
const modelAsset = {
|
||||
id: "asset-model-animated",
|
||||
kind: "model",
|
||||
sourceName: "animated.glb",
|
||||
mimeType: "model/gltf-binary",
|
||||
storageKey: createProjectAssetStorageKey("asset-model-animated"),
|
||||
byteLength: 1024,
|
||||
metadata: {
|
||||
kind: "model" as const,
|
||||
format: "glb" as const,
|
||||
sceneName: null,
|
||||
nodeCount: 1,
|
||||
meshCount: 1,
|
||||
materialNames: [],
|
||||
textureNames: [],
|
||||
animationNames: ["Idle", "Run"],
|
||||
boundingBox: null,
|
||||
warnings: []
|
||||
}
|
||||
} satisfies ModelAssetRecord;
|
||||
const modelInstance = createModelInstance({
|
||||
id: "model-instance-animated",
|
||||
assetId: modelAsset.id
|
||||
});
|
||||
const triggerVolume = createTriggerVolumeEntity({
|
||||
id: "entity-trigger-main"
|
||||
});
|
||||
|
||||
const document = {
|
||||
...createEmptySceneDocument(),
|
||||
assets: {
|
||||
[modelAsset.id]: modelAsset
|
||||
},
|
||||
modelInstances: {
|
||||
[modelInstance.id]: modelInstance
|
||||
},
|
||||
entities: {
|
||||
[triggerVolume.id]: triggerVolume
|
||||
},
|
||||
interactionLinks: {
|
||||
"link-play-missing-clip": createPlayAnimationInteractionLink({
|
||||
id: "link-play-missing-clip",
|
||||
sourceEntityId: triggerVolume.id,
|
||||
trigger: "enter",
|
||||
targetModelInstanceId: modelInstance.id,
|
||||
clipName: "Walk"
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const validation = validateSceneDocument(document);
|
||||
|
||||
expect(validation.errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: "missing-play-animation-clip",
|
||||
path: "interactionLinks.link-play-missing-clip.action.clipName"
|
||||
})
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { createTeleportPlayerInteractionLink, createToggleVisibilityInteractionLink } from "../../src/interactions/interaction-links";
|
||||
import {
|
||||
createPlayAnimationInteractionLink,
|
||||
createTeleportPlayerInteractionLink,
|
||||
createToggleVisibilityInteractionLink,
|
||||
createStopAnimationInteractionLink
|
||||
} from "../../src/interactions/interaction-links";
|
||||
import { RuntimeInteractionSystem } from "../../src/runtime-three/runtime-interaction-system";
|
||||
import type { RuntimeSceneDefinition } from "../../src/runtime-three/runtime-scene-build";
|
||||
|
||||
@@ -28,6 +33,10 @@ function createRuntimeSceneFixture(): RuntimeSceneDefinition {
|
||||
brushes: [],
|
||||
colliders: [],
|
||||
sceneBounds: null,
|
||||
localLights: {
|
||||
pointLights: [],
|
||||
spotLights: []
|
||||
},
|
||||
modelInstances: [],
|
||||
entities: {
|
||||
playerStarts: [],
|
||||
@@ -155,6 +164,49 @@ describe("RuntimeInteractionSystem", () => {
|
||||
expect(dispatches).toEqual(["link-teleport:entity-teleport-main:8"]);
|
||||
});
|
||||
|
||||
it("dispatches animation actions with the authored target model instance and clip", () => {
|
||||
const runtimeScene = createRuntimeSceneFixture();
|
||||
runtimeScene.interactionLinks = [
|
||||
createPlayAnimationInteractionLink({
|
||||
id: "link-play-animation",
|
||||
sourceEntityId: "entity-interactable-console",
|
||||
trigger: "click",
|
||||
targetModelInstanceId: "model-instance-animated",
|
||||
clipName: "Walk",
|
||||
loop: false
|
||||
}),
|
||||
createStopAnimationInteractionLink({
|
||||
id: "link-stop-animation",
|
||||
sourceEntityId: "entity-interactable-console",
|
||||
trigger: "click",
|
||||
targetModelInstanceId: "model-instance-animated"
|
||||
})
|
||||
];
|
||||
|
||||
const interactionSystem = new RuntimeInteractionSystem();
|
||||
const dispatches: string[] = [];
|
||||
|
||||
interactionSystem.dispatchClickInteraction("entity-interactable-console", runtimeScene, {
|
||||
teleportPlayer: () => {
|
||||
throw new Error("Teleport should not dispatch in this fixture.");
|
||||
},
|
||||
toggleBrushVisibility: () => {
|
||||
throw new Error("Visibility should not dispatch in this fixture.");
|
||||
},
|
||||
playAnimation: (instanceId, clipName, loop, link) => {
|
||||
dispatches.push(`${link.id}:${instanceId}:${clipName}:${loop === false ? "once" : "loop"}`);
|
||||
},
|
||||
stopAnimation: (instanceId, link) => {
|
||||
dispatches.push(`${link.id}:${instanceId}`);
|
||||
}
|
||||
});
|
||||
|
||||
expect(dispatches).toEqual([
|
||||
"link-play-animation:model-instance-animated:Walk:once",
|
||||
"link-stop-animation:model-instance-animated"
|
||||
]);
|
||||
});
|
||||
|
||||
it("dispatches visibility actions only when exiting an occupied Trigger Volume", () => {
|
||||
const runtimeScene = createRuntimeSceneFixture();
|
||||
runtimeScene.interactionLinks = [
|
||||
|
||||
@@ -180,7 +180,8 @@ describe("scene document JSON", () => {
|
||||
};
|
||||
document.world.background = {
|
||||
mode: "image",
|
||||
assetId: imageAsset.id
|
||||
assetId: imageAsset.id,
|
||||
environmentIntensity: 0.75
|
||||
};
|
||||
|
||||
expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(document);
|
||||
@@ -630,7 +631,8 @@ describe("scene document JSON", () => {
|
||||
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
|
||||
expect(migratedDocument.world.background).toEqual({
|
||||
mode: "image",
|
||||
assetId: imageAsset.id
|
||||
assetId: imageAsset.id,
|
||||
environmentIntensity: 0.5
|
||||
});
|
||||
expect(migratedDocument.entities[pointLight.id]).toEqual(pointLight);
|
||||
expect(migratedDocument.entities[spotLight.id]).toEqual(spotLight);
|
||||
@@ -792,7 +794,7 @@ describe("scene document JSON", () => {
|
||||
boundingBox: null,
|
||||
warnings: []
|
||||
}
|
||||
};
|
||||
} satisfies ModelAssetRecord;
|
||||
|
||||
const migratedDocument = migrateSceneDocument({
|
||||
version: 11,
|
||||
|
||||
Reference in New Issue
Block a user