229 lines
6.9 KiB
TypeScript
229 lines
6.9 KiB
TypeScript
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);
|
|
});
|