Files
webeditor3d/tests/domain/duplicate-selection.command.test.ts
Victor Giers abad876514 auto-git:
[change] tests/domain/duplicate-selection.command.test.ts
2026-04-22 18:25:00 +02:00

339 lines
9.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { createModelInstance } from "../../src/assets/model-instances";
import { createProjectAssetStorageKey, type ModelAssetRecord } from "../../src/assets/project-assets";
import { createEditorStore } from "../../src/app/editor-store";
import { createDuplicateSelectionCommand } from "../../src/commands/duplicate-selection-command";
import { createBoxBrush } from "../../src/document/brushes";
import { createEmptySceneDocument } from "../../src/document/scene-document";
import {
createNpcEntity,
createTriggerVolumeEntity
} from "../../src/entities/entity-instances";
const modelAsset = {
id: "asset-model-duplicate",
kind: "model",
sourceName: "duplicate-fixture.gltf",
mimeType: "model/gltf+json",
storageKey: createProjectAssetStorageKey("asset-model-duplicate"),
byteLength: 64,
metadata: {
kind: "model",
format: "gltf",
sceneName: "Duplicate Fixture",
nodeCount: 1,
meshCount: 1,
materialNames: [],
textureNames: [],
animationNames: [],
boundingBox: {
min: {
x: 0,
y: 0,
z: 0
},
max: {
x: 1,
y: 1,
z: 1
},
size: {
x: 1,
y: 1,
z: 1
}
},
warnings: []
}
} satisfies ModelAssetRecord;
describe("duplicate selection command", () => {
it("duplicates one selected whitebox solid with a fresh id and supports undo/redo", () => {
const sourceBrush = createBoxBrush({
id: "brush-source",
center: {
x: 2,
y: 1,
z: -3
}
});
const store = createEditorStore({
initialDocument: {
...createEmptySceneDocument({ name: "Duplicate Brush" }),
brushes: {
[sourceBrush.id]: sourceBrush
}
}
});
store.setSelection({
kind: "brushes",
ids: [sourceBrush.id]
});
store.executeCommand(createDuplicateSelectionCommand());
const selection = store.getState().selection;
expect(selection.kind).toBe("brushes");
if (selection.kind !== "brushes") {
throw new Error("Expected duplicated brush selection.");
}
expect(selection.ids).toHaveLength(1);
expect(selection.ids[0]).not.toBe(sourceBrush.id);
const duplicatedBrush = store.getState().document.brushes[selection.ids[0]];
expect(duplicatedBrush).toBeDefined();
expect(duplicatedBrush.center).toEqual(sourceBrush.center);
expect(duplicatedBrush.faces).toEqual(sourceBrush.faces);
expect(store.undo()).toBe(true);
expect(Object.keys(store.getState().document.brushes)).toEqual([sourceBrush.id]);
expect(store.redo()).toBe(true);
expect(store.getState().selection).toEqual({
kind: "brushes",
ids: [duplicatedBrush.id]
});
expect(store.getState().document.brushes[duplicatedBrush.id]).toEqual(duplicatedBrush);
});
it("duplicates one selected model instance without duplicating its asset", () => {
const sourceModelInstance = createModelInstance({
id: "model-instance-source",
assetId: modelAsset.id,
position: {
x: -4,
y: 2,
z: 5
},
rotationDegrees: {
x: 0,
y: 25,
z: 0
},
scale: {
x: 1.5,
y: 1.5,
z: 1.5
}
});
const store = createEditorStore({
initialDocument: {
...createEmptySceneDocument({ name: "Duplicate Model Instance" }),
assets: {
[modelAsset.id]: modelAsset
},
modelInstances: {
[sourceModelInstance.id]: sourceModelInstance
}
}
});
store.setSelection({
kind: "modelInstances",
ids: [sourceModelInstance.id]
});
store.executeCommand(createDuplicateSelectionCommand());
const selection = store.getState().selection;
expect(selection.kind).toBe("modelInstances");
if (selection.kind !== "modelInstances") {
throw new Error("Expected duplicated model instance selection.");
}
const duplicatedModelInstanceId = selection.ids[0];
expect(duplicatedModelInstanceId).not.toBe(sourceModelInstance.id);
const duplicatedModelInstance = store.getState().document.modelInstances[duplicatedModelInstanceId];
expect(duplicatedModelInstance.assetId).toBe(sourceModelInstance.assetId);
expect(duplicatedModelInstance.position).toEqual(sourceModelInstance.position);
expect(store.getState().document.assets[modelAsset.id]).toEqual(modelAsset);
expect(
Object.keys(store.getState().document.assets)
).toContain(modelAsset.id);
expect(store.undo()).toBe(true);
expect(store.getState().document.modelInstances[sourceModelInstance.id]).toEqual(sourceModelInstance);
expect(store.getState().document.modelInstances[duplicatedModelInstanceId]).toBeUndefined();
expect(store.redo()).toBe(true);
expect(store.getState().selection).toEqual({
kind: "modelInstances",
ids: [duplicatedModelInstanceId]
});
});
it("duplicates one selected entity with a fresh id and selects the duplicate", () => {
const sourceEntity = createTriggerVolumeEntity({
id: "entity-source-trigger",
position: {
x: 8,
y: 1,
z: -2
},
size: {
x: 4,
y: 3,
z: 2
},
triggerOnEnter: false,
triggerOnExit: true
});
const store = createEditorStore({
initialDocument: {
...createEmptySceneDocument({ name: "Duplicate Entity" }),
entities: {
[sourceEntity.id]: sourceEntity
}
}
});
store.setSelection({
kind: "entities",
ids: [sourceEntity.id]
});
store.executeCommand(createDuplicateSelectionCommand());
const selection = store.getState().selection;
expect(selection.kind).toBe("entities");
if (selection.kind !== "entities") {
throw new Error("Expected duplicated entity selection.");
}
expect(selection.ids).toHaveLength(1);
expect(selection.ids[0]).not.toBe(sourceEntity.id);
const duplicatedEntity = store.getState().document.entities[selection.ids[0]];
expect(duplicatedEntity).toBeDefined();
expect(duplicatedEntity.kind).toBe(sourceEntity.kind);
if (!("position" in duplicatedEntity)) {
throw new Error("Expected duplicated entity to expose a position.");
}
expect(duplicatedEntity.position).toEqual(sourceEntity.position);
expect(store.undo()).toBe(true);
expect(store.getState().document.entities[sourceEntity.id]).toEqual(sourceEntity);
expect(store.redo()).toBe(true);
expect(store.getState().selection).toEqual({
kind: "entities",
ids: [duplicatedEntity.id]
});
});
it("duplicates NPCs with a fresh stable actor id", () => {
const sourceNpc = createNpcEntity({
id: "entity-npc-source",
actorId: "actor-town-merchant",
position: {
x: 3,
y: 0,
z: -6
},
yawDegrees: 45
});
const store = createEditorStore({
initialDocument: {
...createEmptySceneDocument({ name: "Duplicate NPC" }),
entities: {
[sourceNpc.id]: sourceNpc
}
}
});
store.setSelection({
kind: "entities",
ids: [sourceNpc.id]
});
store.executeCommand(createDuplicateSelectionCommand());
const selection = store.getState().selection;
expect(selection.kind).toBe("entities");
if (selection.kind !== "entities") {
throw new Error("Expected duplicated NPC selection.");
}
const duplicatedNpc = store.getState().document.entities[selection.ids[0]];
expect(duplicatedNpc).toMatchObject({
kind: "npc",
position: sourceNpc.position,
yawDegrees: sourceNpc.yawDegrees
});
expect(duplicatedNpc.id).not.toBe(sourceNpc.id);
if (duplicatedNpc.kind !== "npc") {
throw new Error("Expected duplicated entity to stay an NPC.");
}
expect(duplicatedNpc.actorId).not.toBe(sourceNpc.actorId);
});
it("duplicates multiple selected whitebox solids in one operation", () => {
const sourceBrushA = createBoxBrush({
id: "brush-source-a",
center: {
x: 0,
y: 1,
z: 0
}
});
const sourceBrushB = createBoxBrush({
id: "brush-source-b",
center: {
x: 6,
y: 2,
z: -4
}
});
const store = createEditorStore({
initialDocument: {
...createEmptySceneDocument({ name: "Duplicate Multi" }),
brushes: {
[sourceBrushA.id]: sourceBrushA,
[sourceBrushB.id]: sourceBrushB
}
}
});
store.setSelection({
kind: "brushes",
ids: [sourceBrushA.id, sourceBrushB.id]
});
store.executeCommand(createDuplicateSelectionCommand());
const selection = store.getState().selection;
expect(selection.kind).toBe("brushes");
if (selection.kind !== "brushes") {
throw new Error("Expected duplicated multi-brush selection.");
}
expect(selection.ids).toHaveLength(2);
expect(new Set(selection.ids).size).toBe(2);
expect(selection.ids).not.toEqual([sourceBrushA.id, sourceBrushB.id]);
expect(store.getState().activeSelectionId).toBe(selection.ids[1]);
const duplicatedBrushA = store.getState().document.brushes[selection.ids[0]];
const duplicatedBrushB = store.getState().document.brushes[selection.ids[1]];
expect(duplicatedBrushA.center).toEqual(sourceBrushA.center);
expect(duplicatedBrushB.center).toEqual(sourceBrushB.center);
expect(store.undo()).toBe(true);
expect(Object.keys(store.getState().document.brushes).sort()).toEqual([sourceBrushA.id, sourceBrushB.id]);
expect(store.redo()).toBe(true);
expect(store.getState().selection).toEqual({
kind: "brushes",
ids: [duplicatedBrushA.id, duplicatedBrushB.id]
});
expect(store.getState().activeSelectionId).toBe(duplicatedBrushB.id);
});
});