Add E2E test for transform foundation

This commit is contained in:
2026-04-03 02:19:41 +02:00
parent e801c93a90
commit 14b1de3a91

View File

@@ -0,0 +1,228 @@
import { expect, test } from "@playwright/test";
import { createModelInstance } from "../../src/assets/model-instances";
import { createProjectAssetStorageKey, type ModelAssetRecord } from "../../src/assets/project-assets";
import { createBoxBrush } from "../../src/document/brushes";
import { createEmptySceneDocument } from "../../src/document/scene-document";
import { createPlayerStartEntity } from "../../src/entities/entity-instances";
import {
clickViewport,
getEditorStoreSnapshot,
getViewportCanvas,
replaceSceneDocument
} from "./viewport-test-helpers";
const modelAsset = {
id: "asset-model-transform-e2e",
kind: "model",
sourceName: "transform-fixture.glb",
mimeType: "model/gltf-binary",
storageKey: createProjectAssetStorageKey("asset-model-transform-e2e"),
byteLength: 72,
metadata: {
kind: "model",
format: "glb",
sceneName: "Transform Fixture",
nodeCount: 1,
meshCount: 1,
materialNames: [],
textureNames: [],
animationNames: [],
boundingBox: {
min: {
x: -0.5,
y: 0,
z: -0.5
},
max: {
x: 0.5,
y: 1,
z: 0.5
},
size: {
x: 1,
y: 1,
z: 1
}
},
warnings: []
}
} satisfies ModelAssetRecord;
async function seedTransformScene(page: Parameters<typeof test>[0]["page"]) {
const brush = createBoxBrush({
id: "brush-transform-main",
name: "Brush Transform Fixture",
center: {
x: 0,
y: 1,
z: 0
}
});
const playerStart = createPlayerStartEntity({
id: "entity-player-start-transform",
name: "Player Start Fixture",
position: {
x: 4,
y: 0,
z: -4
},
yawDegrees: 0
});
const modelInstance = createModelInstance({
id: "model-instance-transform-main",
assetId: modelAsset.id,
name: "Model Transform Fixture",
position: {
x: -4,
y: 0,
z: 4
}
});
await replaceSceneDocument(page, {
...createEmptySceneDocument({ name: "Transform Foundation Fixture" }),
brushes: {
[brush.id]: brush
},
assets: {
[modelAsset.id]: modelAsset
},
entities: {
[playerStart.id]: playerStart
},
modelInstances: {
[modelInstance.id]: modelInstance
}
});
return {
brush,
playerStart,
modelInstance
};
}
async function getViewportCenter(page: Parameters<typeof test>[0]["page"], panelId: "topLeft" | "topRight" | "bottomLeft" | "bottomRight") {
const viewportCanvas = getViewportCanvas(page, panelId);
const bounds = await viewportCanvas.boundingBox();
if (bounds === null) {
throw new Error(`Viewport ${panelId} is not visible.`);
}
return {
x: bounds.x + bounds.width * 0.5,
y: bounds.y + bounds.height * 0.5
};
}
async function commitKeyboardMove(
page: Parameters<typeof test>[0]["page"],
panelId: "topLeft" | "topRight" | "bottomLeft" | "bottomRight",
delta: { x: number; y: number },
axisConstraint?: "x" | "y" | "z"
) {
const center = await getViewportCenter(page, panelId);
await page.mouse.move(center.x, center.y);
await page.keyboard.press("g");
if (axisConstraint !== undefined) {
await page.keyboard.press(axisConstraint);
}
await page.mouse.move(center.x + delta.x, center.y + delta.y);
await page.mouse.click(center.x + delta.x, center.y + delta.y);
}
test("viewport picking defaults to whole-brush selection and axis-constrained brush move commits through the shared transform session", async ({ page }) => {
await page.goto("/");
await page.evaluate((storageKey) => {
window.localStorage.removeItem(storageKey);
}, "webeditor3d.scene-document-draft");
await page.reload();
const fixtures = await seedTransformScene(page);
await page.getByTestId("viewport-layout-quad").click();
await clickViewport(page, "topRight");
const selectedSnapshot = await getEditorStoreSnapshot(page);
expect(selectedSnapshot.selection).toEqual({
kind: "brushes",
ids: [fixtures.brush.id]
});
await commitKeyboardMove(page, "topRight", { x: 160, y: 0 }, "x");
const movedSnapshot = await getEditorStoreSnapshot(page);
expect(movedSnapshot.viewportTransientState.transformSession).toEqual({
kind: "none"
});
expect(movedSnapshot.document.brushes[fixtures.brush.id].center.y).toBe(fixtures.brush.center.y);
expect(movedSnapshot.document.brushes[fixtures.brush.id].center.z).toBe(fixtures.brush.center.z);
expect(movedSnapshot.document.brushes[fixtures.brush.id].center.x).not.toBe(fixtures.brush.center.x);
});
test("keyboard move commits an entity translation through the shared transform controller", async ({ page }) => {
await page.goto("/");
await page.evaluate((storageKey) => {
window.localStorage.removeItem(storageKey);
}, "webeditor3d.scene-document-draft");
await page.reload();
const fixtures = await seedTransformScene(page);
await page.getByRole("button", { name: fixtures.playerStart.name as string }).click();
await commitKeyboardMove(page, "topLeft", { x: 100, y: -60 });
const snapshot = await getEditorStoreSnapshot(page);
expect(snapshot.viewportTransientState.transformSession).toEqual({
kind: "none"
});
expect(snapshot.document.entities[fixtures.playerStart.id].position).not.toEqual(fixtures.playerStart.position);
});
test("escape cancels an active entity transform session without committing preview changes", async ({ page }) => {
await page.goto("/");
await page.evaluate((storageKey) => {
window.localStorage.removeItem(storageKey);
}, "webeditor3d.scene-document-draft");
await page.reload();
const fixtures = await seedTransformScene(page);
await page.getByRole("button", { name: fixtures.playerStart.name as string }).click();
const center = await getViewportCenter(page, "topLeft");
await page.mouse.move(center.x, center.y);
await page.keyboard.press("g");
await page.mouse.move(center.x + 140, center.y + 40);
await page.keyboard.press("Escape");
const snapshot = await getEditorStoreSnapshot(page);
expect(snapshot.viewportTransientState.transformSession).toEqual({
kind: "none"
});
expect(snapshot.document.entities[fixtures.playerStart.id].position).toEqual(fixtures.playerStart.position);
});
test("keyboard move commits a model instance translation through the shared transform controller", async ({ page }) => {
await page.goto("/");
await page.evaluate((storageKey) => {
window.localStorage.removeItem(storageKey);
}, "webeditor3d.scene-document-draft");
await page.reload();
const fixtures = await seedTransformScene(page);
await page.getByRole("button", { name: fixtures.modelInstance.name as string }).click();
await commitKeyboardMove(page, "topLeft", { x: -120, y: 80 });
const snapshot = await getEditorStoreSnapshot(page);
expect(snapshot.viewportTransientState.transformSession).toEqual({
kind: "none"
});
expect(snapshot.document.modelInstances[fixtures.modelInstance.id].position).not.toEqual(fixtures.modelInstance.position);
});