auto-git:
[add] playwright.config.js [add] src/app/App.js [add] src/app/editor-store.js [add] src/app/use-editor-store.js [add] src/assets/audio-assets.js [add] src/assets/gltf-model-import.js [add] src/assets/image-assets.js [add] src/assets/model-instance-labels.js [add] src/assets/model-instance-rendering.js [add] src/assets/model-instances.js [add] src/assets/project-asset-storage.js [add] src/assets/project-assets.js [add] src/commands/brush-command-helpers.js [add] src/commands/command-history.js [add] src/commands/command.js [add] src/commands/commit-transform-session-command.js [add] src/commands/create-box-brush-command.js [add] src/commands/delete-box-brush-command.js [add] src/commands/delete-entity-command.js [add] src/commands/delete-interaction-link-command.js [add] src/commands/delete-model-instance-command.js [add] src/commands/import-audio-asset-command.js [add] src/commands/import-background-image-asset-command.js [add] src/commands/import-model-asset-command.js [add] src/commands/move-box-brush-command.js [add] src/commands/resize-box-brush-command.js [add] src/commands/rotate-box-brush-command.js [add] src/commands/set-box-brush-face-material-command.js [add] src/commands/set-box-brush-face-uv-state-command.js [add] src/commands/set-box-brush-name-command.js [add] src/commands/set-box-brush-transform-command.js [add] src/commands/set-entity-name-command.js [add] src/commands/set-model-instance-name-command.js [add] src/commands/set-player-start-command.js [add] src/commands/set-scene-name-command.js [add] src/commands/set-world-settings-command.js [add] src/commands/upsert-entity-command.js [add] src/commands/upsert-interaction-link-command.js [add] src/commands/upsert-model-instance-command.js [add] src/core/ids.js [add] src/core/selection.js [add] src/core/tool-mode.js [add] src/core/transform-session.js [add] src/core/vector.js [add] src/core/whitebox-selection-feedback.js [add] src/core/whitebox-selection-mode.js [add] src/document/brushes.js [add] src/document/migrate-scene-document.js [add] src/document/scene-document-validation.js [add] src/document/scene-document.js [add] src/document/world-settings.js [add] src/entities/entity-instances.js [add] src/entities/entity-labels.js [add] src/geometry/box-brush-components.js [add] src/geometry/box-brush-mesh.js [add] src/geometry/box-brush.js [add] src/geometry/box-face-uvs.js [add] src/geometry/grid-snapping.js [add] src/geometry/model-instance-collider-debug-mesh.js [add] src/geometry/model-instance-collider-generation.js [add] src/interactions/interaction-links.js [add] src/main.js [add] src/materials/starter-material-library.js [add] src/materials/starter-material-textures.js [add] src/rendering/advanced-rendering.js [add] src/runner-web/RunnerCanvas.js [add] src/runtime-three/first-person-navigation-controller.js [add] src/runtime-three/navigation-controller.js [add] src/runtime-three/orbit-visitor-navigation-controller.js [add] src/runtime-three/player-collision.js [add] src/runtime-three/rapier-collision-world.js [add] src/runtime-three/runtime-audio-system.js [add] src/runtime-three/runtime-host.js [add] src/runtime-three/runtime-interaction-system.js [add] src/runtime-three/runtime-scene-build.js [add] src/runtime-three/runtime-scene-validation.js [add] src/serialization/local-draft-storage.js [add] src/serialization/scene-document-json.js [add] src/shared-ui/HierarchicalMenu.js [add] src/shared-ui/Panel.js [add] src/shared-ui/world-background-style.js [add] src/viewport-three/ViewportCanvas.js [add] src/viewport-three/ViewportPanel.js [add] src/viewport-three/viewport-entity-markers.js [add] src/viewport-three/viewport-focus.js [add] src/viewport-three/viewport-host.js [add] src/viewport-three/viewport-layout.js [add] src/viewport-three/viewport-transient-state.js [add] src/viewport-three/viewport-view-modes.js [add] tests/domain/box-brush-face-editing.command.test.js [add] tests/domain/build-runtime-scene.test.js [add] tests/domain/create-box-brush.command.test.js [add] tests/domain/create-empty-scene-document.test.js [add] tests/domain/editor-store.test.js [add] tests/domain/entity.command.test.js [add] tests/domain/interaction-links.validation.test.js [add] tests/domain/model-import.test.js [add] tests/domain/model-instance.command.test.js [add] tests/domain/player-start.command.test.js [add] tests/domain/rapier-collision-world.test.js [add] tests/domain/runtime-audio-system.test.js [add] tests/domain/runtime-interaction-system.test.js [add] tests/domain/runtime-scene-validation.test.js [add] tests/domain/scene-document-validation.test.js [add] tests/domain/transform-session.command.test.js [add] tests/domain/world-settings.command.test.js [add] tests/domain/world-settings.test.js [add] tests/e2e/app-smoke.e2e.js [add] tests/e2e/box-brush-authoring.e2e.js [add] tests/e2e/entities-foundation.e2e.js [add] tests/e2e/face-material-authoring.e2e.js [add] tests/e2e/first-room-workflow.e2e.js [add] tests/e2e/import-draco-model-asset.e2e.js [add] tests/e2e/import-external-model-asset.e2e.js [add] tests/e2e/import-model-asset.e2e.js [add] tests/e2e/local-lights-and-background.e2e.js [add] tests/e2e/orthographic-views.e2e.js [add] tests/e2e/runner-v1.e2e.js [add] tests/e2e/runtime-click-interaction.e2e.js [add] tests/e2e/runtime-trigger-teleport.e2e.js [add] tests/e2e/viewport-quad-layout.e2e.js [add] tests/e2e/viewport-test-helpers.js [add] tests/e2e/whitebox-component-selection.e2e.js [add] tests/e2e/world-environment.e2e.js [add] tests/geometry/box-brush-geometry.test.js [add] tests/geometry/box-face-uvs.test.js [add] tests/geometry/model-instance-collider-generation.test.js [add] tests/helpers/model-collider-fixtures.js [add] tests/serialization/local-draft-storage.test.js [add] tests/serialization/project-asset-storage.test.js [add] tests/serialization/scene-document-json.test.js [add] tests/setup/vitest.setup.js [add] tests/unit/audio-assets.test.js [add] tests/unit/entity-instances.test.js [add] tests/unit/package-scripts.test.js [add] tests/unit/transform-foundation.integration.test.js [add] tests/unit/viewport-canvas.test.js [add] tests/unit/viewport-entity-markers.test.js [add] tests/unit/viewport-focus.test.js [add] tests/unit/viewport-layout.test.js [add] tests/unit/viewport-view-modes.test.js [add] vite.config.js [add] vitest.config.js
This commit is contained in:
24
tests/e2e/app-smoke.e2e.js
Normal file
24
tests/e2e/app-smoke.e2e.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
test("app boots and shows the viewport shell", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await expect(page.getByTestId("toolbar-scene-name")).toHaveValue("Untitled Scene");
|
||||
await expect(page.getByTestId("viewport-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-panel-topLeft")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-layout-single")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-layout-quad")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "World" })).toBeVisible();
|
||||
await expect(page.getByTestId("world-background-mode-value")).toBeVisible();
|
||||
await expect(page.getByTestId("enter-run-mode")).toBeVisible();
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
103
tests/e2e/box-brush-authoring.e2e.js
Normal file
103
tests/e2e/box-brush-authoring.e2e.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { beginBoxCreation, clickViewport, getEditorStoreSnapshot } from "./viewport-test-helpers";
|
||||
test("user can create a whitebox box with float transforms and keep it through reload and run mode", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await beginBoxCreation(page);
|
||||
const creationSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(creationSnapshot).toMatchObject({
|
||||
toolMode: "create",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "create",
|
||||
sourcePanelId: "topLeft",
|
||||
target: {
|
||||
kind: "box-brush"
|
||||
},
|
||||
center: null
|
||||
}
|
||||
}
|
||||
});
|
||||
await page.keyboard.press("Escape");
|
||||
const cancelledSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(cancelledSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page);
|
||||
await page.getByTestId("whitebox-snap-toggle").click();
|
||||
const committedSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(committedSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(page.getByRole("button", { name: /Whitebox Box 1/ })).toBeVisible();
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 1)")).toBeVisible();
|
||||
await expect(page.getByTestId("apply-brush-position")).toHaveCount(0);
|
||||
await expect(page.getByTestId("apply-brush-size")).toHaveCount(0);
|
||||
await page.getByTestId("brush-center-x").fill("1.25");
|
||||
await page.getByTestId("brush-center-x").press("Tab");
|
||||
await page.getByTestId("brush-center-y").fill("2.125");
|
||||
await page.getByTestId("brush-center-y").press("Tab");
|
||||
await page.getByTestId("brush-rotation-y").fill("37.5");
|
||||
await page.getByTestId("brush-rotation-y").press("Tab");
|
||||
await page.getByTestId("brush-size-z").fill("4.5");
|
||||
await page.getByTestId("brush-size-z").press("Tab");
|
||||
await page.getByTestId("selected-brush-name").fill("Entry Room");
|
||||
await page.getByTestId("selected-brush-name").press("Tab");
|
||||
await expect(page.getByTestId("selected-brush-name")).toHaveValue("Entry Room");
|
||||
await page.getByRole("button", { name: "Save Draft" }).click();
|
||||
await page.reload();
|
||||
await expect(page.getByRole("button", { name: /^Entry Room$/ })).toBeVisible();
|
||||
await page.getByRole("button", { name: /^Entry Room$/ }).click();
|
||||
await expect(page.getByTestId("brush-center-x")).toHaveValue("1.25");
|
||||
await expect(page.getByTestId("brush-center-y")).toHaveValue("2.125");
|
||||
await expect(page.getByTestId("brush-rotation-y")).toHaveValue("37.5");
|
||||
await expect(page.getByTestId("brush-size-z")).toHaveValue("4.5");
|
||||
await expect(page.getByTestId("viewport-overlay-topLeft")).toHaveCount(0);
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
test("switching selection while a transform input is active does not overwrite the newly selected brush", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page);
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page);
|
||||
const outlinerButtons = page.getByTestId("outliner-brush-list").getByRole("button");
|
||||
await outlinerButtons.nth(0).click();
|
||||
await page.getByTestId("brush-size-z").fill("4");
|
||||
await outlinerButtons.nth(1).click();
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 2)")).toBeVisible();
|
||||
await expect(page.getByTestId("brush-size-z")).toHaveValue("2");
|
||||
await outlinerButtons.nth(0).click();
|
||||
await expect(page.getByTestId("brush-size-z")).toHaveValue("4");
|
||||
});
|
||||
155
tests/e2e/entities-foundation.e2e.js
Normal file
155
tests/e2e/entities-foundation.e2e.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, getEditorStoreSnapshot, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
test("user can place and select typed entities from the entity foundation workflow", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-sound-emitter").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "soundEmitter", audioAssetId: null }, { x: 4, y: 1, z: -6 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
const soundEmitterSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(soundEmitterSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
const selectedSoundEmitterId = soundEmitterSnapshot.selection.kind === "entities" ? soundEmitterSnapshot.selection.ids?.[0] ?? null : null;
|
||||
expect(selectedSoundEmitterId).not.toBeNull();
|
||||
const selectedSoundEmitter = soundEmitterSnapshot.document.entities[selectedSoundEmitterId];
|
||||
if (selectedSoundEmitter === undefined) {
|
||||
throw new Error("Placed sound emitter is missing from the document snapshot.");
|
||||
}
|
||||
expect(selectedSoundEmitter.position).toMatchObject({
|
||||
x: 4,
|
||||
y: 1,
|
||||
z: -6
|
||||
});
|
||||
await expect(page.getByTestId("sound-emitter-ref-distance")).toHaveValue("6");
|
||||
await expect(page.getByTestId("sound-emitter-max-distance")).toHaveValue("24");
|
||||
await page.getByTestId("sound-emitter-ref-distance").fill("9");
|
||||
await page.getByTestId("sound-emitter-ref-distance").press("Tab");
|
||||
await page.getByTestId("sound-emitter-autoplay").click();
|
||||
await page.getByTestId("sound-emitter-loop").click();
|
||||
await expect(page.getByTestId("sound-emitter-autoplay")).toBeChecked();
|
||||
await expect(page.getByTestId("sound-emitter-loop")).toBeChecked();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-interactable").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "interactable", audioAssetId: null }, { x: -8, y: 1, z: 12 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
const interactableSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(interactableSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(page.getByTestId("interactable-prompt")).toHaveValue("Use");
|
||||
await page
|
||||
.locator('[data-testid^="outliner-entity-"]')
|
||||
.filter({ hasText: "Sound Emitter" })
|
||||
.first()
|
||||
.click();
|
||||
await expect(page.getByTestId("sound-emitter-ref-distance")).toHaveValue("9");
|
||||
await expect(page.getByTestId("sound-emitter-autoplay")).toBeChecked();
|
||||
await expect(page.getByTestId("sound-emitter-loop")).toBeChecked();
|
||||
await expect(page.getByTestId("interactable-prompt")).toHaveCount(0);
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
test("shift+a opens the add menu at the cursor", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.mouse.move(420, 260);
|
||||
await page.keyboard.press("Shift+A");
|
||||
await expect(page.getByRole("menu", { name: "Add" })).toBeVisible();
|
||||
await page.getByTestId("add-menu-lights").click();
|
||||
await page.getByTestId("add-menu-point-light").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "pointLight", audioAssetId: null }, { x: 12, y: 3, z: -4 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByTestId("point-light-intensity")).toHaveValue("1.25");
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
test("escape cancels a typed entity creation preview", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-player-start").click();
|
||||
const creationSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(creationSnapshot).toMatchObject({
|
||||
toolMode: "create",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "create",
|
||||
sourcePanelId: "topLeft",
|
||||
target: {
|
||||
kind: "entity",
|
||||
entityKind: "playerStart",
|
||||
audioAssetId: null
|
||||
},
|
||||
center: null
|
||||
}
|
||||
}
|
||||
});
|
||||
await page.keyboard.press("Escape");
|
||||
const cancelledSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(cancelledSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
31
tests/e2e/face-material-authoring.e2e.js
Normal file
31
tests/e2e/face-material-authoring.e2e.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { beginBoxCreation, clickViewport } from "./viewport-test-helpers";
|
||||
test("user can assign a face material through the UI and keep it through a draft reload", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page);
|
||||
await page.getByTestId("face-button-posZ").click();
|
||||
await page.getByTestId("material-button-starter-amber-grid").click();
|
||||
await expect(page.getByTestId("selected-face-material-name")).toContainText("Amber Grid");
|
||||
await page.getByRole("button", { name: "Save Draft" }).click();
|
||||
await page.reload();
|
||||
await page.getByRole("button", { name: /Whitebox Box 1/ }).click();
|
||||
await page.getByTestId("face-button-posZ").click();
|
||||
await expect(page.getByTestId("selected-face-material-name")).toContainText("Amber Grid");
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
54
tests/e2e/first-room-workflow.e2e.js
Normal file
54
tests/e2e/first-room-workflow.e2e.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { beginBoxCreation, clickViewport, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
test("first-room workflow covers create, texture, save/load, and run", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page);
|
||||
await page.getByTestId("face-button-posZ").click();
|
||||
await page.getByTestId("material-button-starter-amber-grid").click();
|
||||
await page.getByRole("button", { name: "First Person" }).click();
|
||||
await expect(page.getByTestId("status-message")).toContainText("Author a Player Start before running");
|
||||
await expect(page.getByTestId("status-run-preflight")).toContainText("Blocked");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-player-start").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "playerStart", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("player-start-position-x").fill("2");
|
||||
await page.getByTestId("player-start-position-x").press("Tab");
|
||||
await page.getByTestId("player-start-position-z").fill("-2");
|
||||
await page.getByTestId("player-start-position-z").press("Tab");
|
||||
await page.getByTestId("player-start-yaw").fill("90");
|
||||
await page.getByTestId("player-start-yaw").press("Tab");
|
||||
await expect(page.getByTestId("status-run-preflight")).toContainText("Ready for First Person");
|
||||
await page.getByRole("button", { name: "Save Draft" }).click();
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page);
|
||||
await expect(page.getByRole("button", { name: /Whitebox Box 2/ })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Load Draft" }).click();
|
||||
await expect(page.getByRole("button", { name: /Whitebox Box 2/ })).toHaveCount(0);
|
||||
await page.getByRole("button", { name: /Whitebox Box 1/ }).click();
|
||||
await page.getByTestId("face-button-posZ").click();
|
||||
await expect(page.getByTestId("selected-face-material-name")).toContainText("Amber Grid");
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-spawn-state")).toContainText("Player Start");
|
||||
await expect(page.getByTestId("runner-player-position")).toContainText("2.00,");
|
||||
await expect(page.getByTestId("runner-player-position")).toContainText(", -2.00");
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
59
tests/e2e/import-draco-model-asset.e2e.js
Normal file
59
tests/e2e/import-draco-model-asset.e2e.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, getEditorStoreSnapshot, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
const fixturePath = path.resolve(process.cwd(), "fixtures/assets/tiny-triangle-draco.glb");
|
||||
test("imports a draco-compressed glb asset, places an instance, and survives reload", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-import").click();
|
||||
await page.getByTestId("import-menu-model").click();
|
||||
await page.locator('input[type="file"][accept*="gltf"]').setInputFiles(fixturePath);
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(1);
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-models").click();
|
||||
const addMenu = page.getByRole("menu", { name: "Add" });
|
||||
await expect(addMenu.getByRole("menuitem", { name: "tiny-triangle-draco.glb" })).toBeVisible();
|
||||
await addMenu.getByRole("menuitem", { name: "tiny-triangle-draco.glb" }).click();
|
||||
const importedSnapshot = await getEditorStoreSnapshot(page);
|
||||
const importedModelAsset = Object.values(importedSnapshot.document.assets).find((asset) => asset.kind === "model" && asset.sourceName === "tiny-triangle-draco.glb");
|
||||
if (importedModelAsset === undefined) {
|
||||
throw new Error("Imported model asset was not found in the document snapshot.");
|
||||
}
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "model-instance", assetId: importedModelAsset.id }, { x: 84, y: 0, z: -88 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(2);
|
||||
const snapshot = await getEditorStoreSnapshot(page);
|
||||
const selectedModelInstanceId = snapshot.selection.kind === "modelInstances" ? snapshot.selection.ids?.[0] ?? null : null;
|
||||
expect(selectedModelInstanceId).not.toBeNull();
|
||||
const selectedModelInstance = snapshot.document.modelInstances[selectedModelInstanceId];
|
||||
if (selectedModelInstance === undefined) {
|
||||
throw new Error("Placed model instance is missing from the document snapshot.");
|
||||
}
|
||||
expect(selectedModelInstance.position).toMatchObject({
|
||||
x: 84,
|
||||
z: -88
|
||||
});
|
||||
await page.getByRole("button", { name: "Save Draft" }).dispatchEvent("click");
|
||||
await expect(page.getByTestId("status-message")).toContainText("Local draft saved.");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-models").click();
|
||||
await expect(page.getByRole("menu", { name: "Add" }).getByRole("menuitem", { name: "tiny-triangle-draco.glb" })).toBeVisible();
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(2);
|
||||
await expect(page.getByTestId("asset-status-message")).toHaveCount(0);
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
53
tests/e2e/import-external-model-asset.e2e.js
Normal file
53
tests/e2e/import-external-model-asset.e2e.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, getEditorStoreSnapshot, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
const gltfFixturePath = path.resolve(process.cwd(), "fixtures/assets/external-triangle/scene.gltf");
|
||||
const binFixturePath = path.resolve(process.cwd(), "fixtures/assets/external-triangle/triangle.bin");
|
||||
test("imports a gltf asset with external resources and places an instance", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-import").click();
|
||||
await page.getByTestId("import-menu-model").click();
|
||||
await page.locator('input[type="file"][accept*="gltf"]').setInputFiles([gltfFixturePath, binFixturePath]);
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(1);
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-models").click();
|
||||
const addMenu = page.getByRole("menu", { name: "Add" });
|
||||
await expect(addMenu.getByRole("menuitem", { name: "scene.gltf" })).toBeVisible();
|
||||
await addMenu.getByRole("menuitem", { name: "scene.gltf" }).hover();
|
||||
await expect(page.getByTestId("status-asset-hover")).toContainText("Storage key:");
|
||||
await addMenu.getByRole("menuitem", { name: "scene.gltf" }).click();
|
||||
const importedSnapshot = await getEditorStoreSnapshot(page);
|
||||
const importedModelAsset = Object.values(importedSnapshot.document.assets).find((asset) => asset.kind === "model" && asset.sourceName === "scene.gltf");
|
||||
if (importedModelAsset === undefined) {
|
||||
throw new Error("Imported model asset was not found in the document snapshot.");
|
||||
}
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "model-instance", assetId: importedModelAsset.id }, { x: 88, y: 0, z: -84 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(2);
|
||||
const snapshot = await getEditorStoreSnapshot(page);
|
||||
const selectedModelInstanceId = snapshot.selection.kind === "modelInstances" ? snapshot.selection.ids?.[0] ?? null : null;
|
||||
expect(selectedModelInstanceId).not.toBeNull();
|
||||
const selectedModelInstance = snapshot.document.modelInstances[selectedModelInstanceId];
|
||||
if (selectedModelInstance === undefined) {
|
||||
throw new Error("Placed model instance is missing from the document snapshot.");
|
||||
}
|
||||
expect(selectedModelInstance.position).toMatchObject({
|
||||
x: 88,
|
||||
z: -84
|
||||
});
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
101
tests/e2e/import-model-asset.e2e.js
Normal file
101
tests/e2e/import-model-asset.e2e.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, getEditorStoreSnapshot, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
const fixturePath = path.resolve(process.cwd(), "fixtures/assets/tiny-triangle.gltf");
|
||||
test("imports a model asset, places an instance, and survives reload", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-import").click();
|
||||
await expect(page.getByTestId("import-menu-model")).toBeVisible();
|
||||
await expect(page.getByTestId("import-menu-environment")).toBeVisible();
|
||||
await expect(page.getByTestId("import-menu-audio")).toBeVisible();
|
||||
await page.getByTestId("import-menu-model").click();
|
||||
await page.locator('input[type="file"][accept*="gltf"]').setInputFiles(fixturePath);
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(1);
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-models").click();
|
||||
const addMenu = page.getByRole("menu", { name: "Add" });
|
||||
await expect(addMenu.getByRole("menuitem", { name: "tiny-triangle.gltf" })).toBeVisible();
|
||||
await addMenu.getByRole("menuitem", { name: "tiny-triangle.gltf" }).hover();
|
||||
await expect(page.getByTestId("status-asset-hover")).toContainText("Storage key:");
|
||||
await addMenu.getByRole("menuitem", { name: "tiny-triangle.gltf" }).click();
|
||||
const importedSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(importedSnapshot).toMatchObject({
|
||||
toolMode: "create",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "create",
|
||||
sourcePanelId: "topLeft",
|
||||
target: {
|
||||
kind: "model-instance",
|
||||
assetId: expect.any(String)
|
||||
},
|
||||
center: null
|
||||
}
|
||||
}
|
||||
});
|
||||
const importedModelAsset = Object.values(importedSnapshot.document.assets).find((asset) => asset.kind === "model" && asset.sourceName === "tiny-triangle.gltf");
|
||||
if (importedModelAsset === undefined) {
|
||||
throw new Error("Imported model asset was not found in the document snapshot.");
|
||||
}
|
||||
await page.keyboard.press("Escape");
|
||||
const cancelledSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(cancelledSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-models").click();
|
||||
await page.getByRole("menu", { name: "Add" }).getByRole("menuitem", { name: "tiny-triangle.gltf" }).click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "model-instance", assetId: importedModelAsset.id }, { x: 92, y: 0, z: -76 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
const committedSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(committedSnapshot).toMatchObject({
|
||||
toolMode: "select",
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "none"
|
||||
}
|
||||
}
|
||||
});
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(2);
|
||||
const snapshot = committedSnapshot;
|
||||
const selectedModelInstanceId = snapshot.selection.kind === "modelInstances" ? snapshot.selection.ids?.[0] ?? null : null;
|
||||
expect(selectedModelInstanceId).not.toBeNull();
|
||||
const selectedModelInstance = snapshot.document.modelInstances[selectedModelInstanceId];
|
||||
if (selectedModelInstance === undefined) {
|
||||
throw new Error("Placed model instance is missing from the document snapshot.");
|
||||
}
|
||||
expect(selectedModelInstance.position).toMatchObject({
|
||||
x: 92,
|
||||
z: -76
|
||||
});
|
||||
await page.getByRole("button", { name: "Save Draft" }).dispatchEvent("click");
|
||||
await expect(page.getByTestId("status-message")).toContainText("Local draft saved.");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-models").click();
|
||||
await expect(page.getByRole("menu", { name: "Add" }).getByRole("menuitem", { name: "tiny-triangle.gltf" })).toBeVisible();
|
||||
await expect(page.getByTestId("outliner-model-instance-list").getByRole("button")).toHaveCount(2);
|
||||
await expect(page.getByTestId("asset-status-message")).toHaveCount(0);
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
71
tests/e2e/local-lights-and-background.e2e.js
Normal file
71
tests/e2e/local-lights-and-background.e2e.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
const panoramaFixturePath = path.resolve(process.cwd(), "fixtures/assets/skybox-panorama.svg");
|
||||
test("local lights and background images persist through editor and runner flows", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-import").click();
|
||||
await page.getByTestId("import-menu-environment").click();
|
||||
await page.locator('input[type="file"][accept*="image"]').setInputFiles(panoramaFixturePath);
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-environments").click();
|
||||
const addMenu = page.getByRole("menu", { name: "Add" });
|
||||
await expect(addMenu.getByRole("menuitem", { name: "skybox-panorama.svg" })).toBeVisible();
|
||||
await addMenu.getByRole("menuitem", { name: "skybox-panorama.svg" }).click();
|
||||
await expect(page.getByTestId("world-background-mode-value")).toContainText("Image");
|
||||
await expect(page.getByTestId("world-background-asset-value")).toContainText("skybox-panorama.svg");
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-image", /url/);
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-lights").click();
|
||||
await page.getByTestId("add-menu-point-light").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "pointLight", audioAssetId: null }, { x: 12, y: 3, z: -4 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByTestId("point-light-distance")).toHaveValue("8");
|
||||
await page.getByTestId("point-light-distance").fill("12");
|
||||
await page.getByTestId("point-light-distance").press("Tab");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-lights").click();
|
||||
await page.getByTestId("add-menu-spot-light").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "spotLight", audioAssetId: null }, { x: -10, y: 4, z: 6 });
|
||||
await expect(page.getByTestId("viewport-snap-preview-topLeft")).toBeVisible();
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByTestId("spot-light-angle")).toHaveValue("35");
|
||||
await page.getByTestId("spot-light-angle").fill("48");
|
||||
await page.getByTestId("spot-light-angle").press("Tab");
|
||||
await page.getByTestId("spot-light-direction-y").fill("-0.9");
|
||||
await page.getByTestId("spot-light-direction-y").press("Tab");
|
||||
await expect(page.locator('[data-testid^="outliner-entity-"]')).toHaveCount(2);
|
||||
await page.getByRole("button", { name: "Save Draft" }).click();
|
||||
await expect(page.getByTestId("status-message")).toContainText("Local draft saved.");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-assets").click();
|
||||
await page.getByTestId("add-menu-assets-environments").click();
|
||||
const reloadedAddMenu = page.getByRole("menu", { name: "Add" });
|
||||
await expect(reloadedAddMenu.getByRole("menuitem", { name: "skybox-panorama.svg" })).toBeVisible();
|
||||
await reloadedAddMenu.getByRole("menuitem", { name: "skybox-panorama.svg" }).click();
|
||||
await expect(page.locator('[data-testid^="outliner-entity-"]')).toHaveCount(2);
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-image", /url/);
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-shell")).toHaveCSS("background-image", /url/);
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
47
tests/e2e/orthographic-views.e2e.js
Normal file
47
tests/e2e/orthographic-views.e2e.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { beginBoxCreation, clickViewport, getViewportOverlay, getViewportPanel } from "./viewport-test-helpers";
|
||||
test("orthographic panel controls keep brush authoring and selection behavior intact", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByRole("button", { name: /Whitebox Box 1/ })).toBeVisible();
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 1)")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-active-panel")).toHaveCount(0);
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-view-perspective")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(getViewportOverlay(page, "topLeft")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-selection-mode-topLeft")).toHaveText("Object");
|
||||
await page.getByTestId("viewport-panel-topLeft-view-top").dispatchEvent("click");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-view-top")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-selection-mode-topLeft")).toHaveText("Object");
|
||||
await page.getByTestId("viewport-panel-topLeft-view-front").dispatchEvent("click");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-view-front")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-selection-mode-topLeft")).toHaveText("Object");
|
||||
await page.getByTestId("viewport-panel-topLeft-view-side").dispatchEvent("click");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-view-side")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-selection-mode-topLeft")).toHaveText("Object");
|
||||
await page.getByTestId("viewport-panel-topLeft-display-authoring").dispatchEvent("click");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-display-authoring")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-color", "rgb(0, 0, 0)");
|
||||
await expect(getViewportPanel(page, "topLeft")).toHaveAttribute("data-active", "true");
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 1)")).toBeVisible();
|
||||
await page.getByTestId("viewport-panel-topLeft-display-wireframe").dispatchEvent("click");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-display-wireframe")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-color", "rgb(0, 0, 0)");
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 1)")).toBeVisible();
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
40
tests/e2e/runner-v1.e2e.js
Normal file
40
tests/e2e/runner-v1.e2e.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
test("user can place PlayerStart, enter run mode, and spawn from it", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-player-start").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "playerStart", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("player-start-position-x").fill("4");
|
||||
await page.getByTestId("player-start-position-x").press("Tab");
|
||||
await page.getByTestId("player-start-position-z").fill("-2");
|
||||
await page.getByTestId("player-start-position-z").press("Tab");
|
||||
await page.getByTestId("player-start-yaw").fill("90");
|
||||
await page.getByTestId("player-start-yaw").press("Tab");
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-spawn-state")).toContainText("Player Start");
|
||||
await expect(page.getByTestId("runner-player-position")).toContainText("4.00, 0.00, -2.00");
|
||||
await page.getByTestId("runner-mode-orbit-visitor").click();
|
||||
await expect(page.getByTestId("runner-mode-orbit-visitor")).toHaveClass(/toolbar__button--active/);
|
||||
await page.getByTestId("exit-run-mode").click();
|
||||
await expect(page.getByTestId("viewport-shell")).toBeVisible();
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
59
tests/e2e/runtime-click-interaction.e2e.js
Normal file
59
tests/e2e/runtime-click-interaction.e2e.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
test("Interactable click prompt can teleport the player in run mode", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-player-start").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "playerStart", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-interactable").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "interactable", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("interactable-position-y").fill("1");
|
||||
await page.getByTestId("interactable-position-y").press("Tab");
|
||||
await page.getByTestId("interactable-position-z").fill("1");
|
||||
await page.getByTestId("interactable-position-z").press("Tab");
|
||||
await page.getByTestId("interactable-radius").fill("4");
|
||||
await page.getByTestId("interactable-radius").press("Tab");
|
||||
await page.getByTestId("interactable-prompt").fill("Use Console");
|
||||
await page.getByTestId("interactable-prompt").press("Tab");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-teleport-target").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "teleportTarget", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("teleportTarget-position-x").fill("6");
|
||||
await page.getByTestId("teleportTarget-position-x").press("Tab");
|
||||
await page
|
||||
.locator('[data-testid^="outliner-entity-"]')
|
||||
.filter({ hasText: "Interactable" })
|
||||
.first()
|
||||
.click();
|
||||
await page.getByTestId("add-interactable-teleport-link").click();
|
||||
await page.getByRole("button", { name: "First Person" }).first().click();
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-interaction-prompt")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-interaction-prompt-text")).toContainText("Use Console");
|
||||
await page.locator('[data-testid="runner-shell"] canvas').click();
|
||||
await expect(page.getByTestId("runner-player-position")).toContainText("6.00,");
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
48
tests/e2e/runtime-trigger-teleport.e2e.js
Normal file
48
tests/e2e/runtime-trigger-teleport.e2e.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { clickViewport, setViewportCreationPreview } from "./viewport-test-helpers";
|
||||
test("Trigger Volume enter can teleport the player to a Teleport Target", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-player-start").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "playerStart", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-trigger-volume").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "triggerVolume", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-entities").click();
|
||||
await page.getByTestId("add-menu-teleport-target").click();
|
||||
await setViewportCreationPreview(page, "topLeft", { kind: "entity", entityKind: "teleportTarget", audioAssetId: null }, { x: 0, y: 0, z: 0 });
|
||||
await clickViewport(page, "topLeft");
|
||||
await page.getByTestId("teleportTarget-position-x").fill("6");
|
||||
await page.getByTestId("teleportTarget-position-x").press("Tab");
|
||||
await page
|
||||
.locator('[data-testid^="outliner-entity-"]')
|
||||
.filter({ hasText: "Trigger Volume" })
|
||||
.first()
|
||||
.click();
|
||||
await page.getByTestId("add-trigger-teleport-link").click();
|
||||
await page.getByRole("button", { name: "First Person" }).first().click();
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-player-position")).toContainText("6.00,");
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
134
tests/e2e/viewport-quad-layout.e2e.js
Normal file
134
tests/e2e/viewport-quad-layout.e2e.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { beginBoxCreation, clickViewport, getEditorStoreSnapshot, getViewportPanel, setSharedBoxCreationPreview } from "./viewport-test-helpers";
|
||||
test("quad viewport layout shows four linked panels with shared selection and active panel state", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await beginBoxCreation(page);
|
||||
await clickViewport(page, "topLeft");
|
||||
await expect(page.getByRole("button", { name: /Whitebox Box 1/ })).toBeVisible();
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 1)")).toBeVisible();
|
||||
await page.getByTestId("viewport-layout-quad").click();
|
||||
await expect(page.getByTestId("viewport-panel-topLeft")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-panel-topRight")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-panel-bottomLeft")).toBeVisible();
|
||||
await expect(page.getByTestId("viewport-panel-bottomRight")).toBeVisible();
|
||||
const initialLayoutSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(initialLayoutSnapshot.viewportQuadSplit).toEqual({
|
||||
x: 0.5,
|
||||
y: 0.5
|
||||
});
|
||||
const dragSplitter = async (testId, deltaX, deltaY) => {
|
||||
const splitter = page.getByTestId(testId);
|
||||
const box = await splitter.boundingBox();
|
||||
if (box === null) {
|
||||
throw new Error(`Missing splitter handle: ${testId}`);
|
||||
}
|
||||
await page.mouse.move(box.x + box.width * 0.5, box.y + box.height * 0.5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(box.x + box.width * 0.5 + deltaX, box.y + box.height * 0.5 + deltaY);
|
||||
await page.mouse.up();
|
||||
};
|
||||
await dragSplitter("viewport-quad-splitter-center", 80, 48);
|
||||
const afterCenterResizeSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(afterCenterResizeSnapshot.viewportQuadSplit.x).toBeGreaterThan(0.5);
|
||||
expect(afterCenterResizeSnapshot.viewportQuadSplit.y).toBeGreaterThan(0.5);
|
||||
await dragSplitter("viewport-quad-splitter-vertical", -60, 0);
|
||||
const afterVerticalResizeSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(afterVerticalResizeSnapshot.viewportQuadSplit.x).toBeLessThan(afterCenterResizeSnapshot.viewportQuadSplit.x);
|
||||
expect(Math.abs(afterVerticalResizeSnapshot.viewportQuadSplit.y - afterCenterResizeSnapshot.viewportQuadSplit.y)).toBeLessThan(0.02);
|
||||
await dragSplitter("viewport-quad-splitter-horizontal", 0, -40);
|
||||
const afterHorizontalResizeSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(Math.abs(afterHorizontalResizeSnapshot.viewportQuadSplit.x - afterVerticalResizeSnapshot.viewportQuadSplit.x)).toBeLessThan(0.02);
|
||||
expect(afterHorizontalResizeSnapshot.viewportQuadSplit.y).toBeLessThan(afterVerticalResizeSnapshot.viewportQuadSplit.y);
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-view-perspective")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-display-normal")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-panel-topRight-view-top")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-panel-topRight-display-authoring")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-canvas-topRight")).toHaveCSS("background-color", "rgb(0, 0, 0)");
|
||||
await expect(page.getByTestId("viewport-panel-bottomLeft-view-front")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-panel-bottomLeft-display-authoring")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-panel-bottomRight-view-side")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-panel-bottomRight-display-authoring")).toHaveAttribute("aria-pressed", "true");
|
||||
for (const panelId of ["topLeft", "topRight", "bottomLeft", "bottomRight"]) {
|
||||
await expect(getViewportPanel(page, panelId).locator(".viewport-panel__subtitle")).toHaveCount(0);
|
||||
await expect(getViewportPanel(page, panelId).locator(".viewport-canvas__overlay-text")).toHaveCount(0);
|
||||
}
|
||||
await setSharedBoxCreationPreview(page, "topLeft", { x: 4, y: 0, z: 8 });
|
||||
const initialSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(initialSnapshot).toMatchObject({
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "create",
|
||||
sourcePanelId: "topLeft",
|
||||
target: {
|
||||
kind: "box-brush"
|
||||
},
|
||||
center: {
|
||||
x: 4,
|
||||
y: 0,
|
||||
z: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await getViewportPanel(page, "topRight").click({ position: { x: 16, y: 16 }, force: true });
|
||||
await page.getByTestId("viewport-panel-topRight-view-side").dispatchEvent("click");
|
||||
await expect(getViewportPanel(page, "topRight")).toHaveAttribute("data-active", "true");
|
||||
await expect(page.getByTestId("viewport-panel-topRight-view-side")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByRole("button", { name: /Whitebox Box 1/ })).toBeVisible();
|
||||
const transferredSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(transferredSnapshot).toMatchObject({
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "create",
|
||||
sourcePanelId: "topLeft",
|
||||
target: {
|
||||
kind: "box-brush"
|
||||
},
|
||||
center: {
|
||||
x: 4,
|
||||
y: 0,
|
||||
z: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
await getViewportPanel(page, "topLeft").click({ position: { x: 16, y: 16 }, force: true });
|
||||
await page.getByTestId("viewport-panel-topLeft-display-authoring").dispatchEvent("click");
|
||||
await expect(getViewportPanel(page, "topLeft")).toHaveAttribute("data-active", "true");
|
||||
await expect(page.getByTestId("viewport-panel-topLeft-display-authoring")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-color", "rgb(0, 0, 0)");
|
||||
await expect(page.getByText("1 solid selected (Whitebox Box 1)")).toBeVisible();
|
||||
const finalSnapshot = await getEditorStoreSnapshot(page);
|
||||
expect(finalSnapshot).toMatchObject({
|
||||
viewportTransientState: {
|
||||
toolPreview: {
|
||||
kind: "create",
|
||||
sourcePanelId: "topLeft",
|
||||
target: {
|
||||
kind: "box-brush"
|
||||
},
|
||||
center: {
|
||||
x: 4,
|
||||
y: 0,
|
||||
z: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
105
tests/e2e/viewport-test-helpers.js
Normal file
105
tests/e2e/viewport-test-helpers.js
Normal file
@@ -0,0 +1,105 @@
|
||||
export const DEFAULT_VIEWPORT_PANEL_ID = "topLeft";
|
||||
export function getViewportPanel(page, panelId = DEFAULT_VIEWPORT_PANEL_ID) {
|
||||
return page.getByTestId(`viewport-panel-${panelId}`);
|
||||
}
|
||||
export function getViewportCanvas(page, panelId = DEFAULT_VIEWPORT_PANEL_ID) {
|
||||
return getViewportPanel(page, panelId).locator("canvas");
|
||||
}
|
||||
export function getViewportOverlay(page, panelId = DEFAULT_VIEWPORT_PANEL_ID) {
|
||||
return page.getByTestId(`viewport-overlay-${panelId}`);
|
||||
}
|
||||
export async function getEditorStoreSnapshot(page) {
|
||||
return page.evaluate(() => {
|
||||
const store = window.__webeditor3dEditorStore;
|
||||
if (store === undefined) {
|
||||
throw new Error("Editor store debug hook is unavailable.");
|
||||
}
|
||||
return store.getState();
|
||||
});
|
||||
}
|
||||
export async function getViewportToolPreview(page) {
|
||||
const snapshot = await getEditorStoreSnapshot(page);
|
||||
return snapshot.viewportTransientState.toolPreview;
|
||||
}
|
||||
export async function replaceSceneDocument(page, document) {
|
||||
await page.evaluate((nextDocument) => {
|
||||
const store = window.__webeditor3dEditorStore;
|
||||
if (store === undefined) {
|
||||
throw new Error("Editor store debug hook is unavailable.");
|
||||
}
|
||||
store.replaceDocument(nextDocument);
|
||||
}, document);
|
||||
}
|
||||
export async function setViewportCreationPreview(page, panelId, target, center) {
|
||||
await page.evaluate(({ sourcePanelId, nextTarget, nextCenter }) => {
|
||||
const store = window.__webeditor3dEditorStore;
|
||||
if (store === undefined) {
|
||||
throw new Error("Editor store debug hook is unavailable.");
|
||||
}
|
||||
store.setViewportToolPreview({
|
||||
kind: "create",
|
||||
sourcePanelId,
|
||||
target: nextTarget,
|
||||
center: nextCenter
|
||||
});
|
||||
}, {
|
||||
sourcePanelId: panelId,
|
||||
nextTarget: target,
|
||||
nextCenter: center
|
||||
});
|
||||
}
|
||||
export async function clearViewportCreationPreview(page) {
|
||||
await page.evaluate(() => {
|
||||
const store = window.__webeditor3dEditorStore;
|
||||
if (store === undefined) {
|
||||
throw new Error("Editor store debug hook is unavailable.");
|
||||
}
|
||||
store.setViewportToolPreview({
|
||||
kind: "none"
|
||||
});
|
||||
});
|
||||
}
|
||||
export async function beginBoxCreation(page) {
|
||||
await page.getByTestId("outliner-add-button").click();
|
||||
await page.getByTestId("add-menu-box").click();
|
||||
}
|
||||
export async function clickViewport(page, panelId = DEFAULT_VIEWPORT_PANEL_ID) {
|
||||
const viewportPanel = getViewportPanel(page, panelId);
|
||||
await viewportPanel.click({ position: { x: 16, y: 16 }, force: true });
|
||||
const fallbackButton = viewportPanel.getByTestId(`viewport-fallback-create-${panelId}`);
|
||||
if ((await fallbackButton.count()) > 0) {
|
||||
await fallbackButton.click();
|
||||
return;
|
||||
}
|
||||
const viewportCanvas = getViewportCanvas(page, panelId);
|
||||
if ((await viewportCanvas.count()) > 0) {
|
||||
await viewportCanvas.click();
|
||||
return;
|
||||
}
|
||||
}
|
||||
export async function clickViewportAtRatio(page, panelId, xRatio, yRatio) {
|
||||
const viewportCanvas = getViewportCanvas(page, panelId);
|
||||
if ((await viewportCanvas.count()) > 0) {
|
||||
const canvasBox = await viewportCanvas.boundingBox();
|
||||
if (canvasBox !== null) {
|
||||
await viewportCanvas.click({
|
||||
position: {
|
||||
x: canvasBox.width * xRatio,
|
||||
y: canvasBox.height * yRatio
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const viewportPanel = getViewportPanel(page, panelId);
|
||||
const box = await viewportPanel.boundingBox();
|
||||
if (box === null) {
|
||||
throw new Error(`Missing viewport panel for ${panelId}.`);
|
||||
}
|
||||
await page.mouse.click(box.x + box.width * xRatio, box.y + box.height * yRatio);
|
||||
}
|
||||
export async function setSharedBoxCreationPreview(page, panelId, center) {
|
||||
return setViewportCreationPreview(page, panelId, {
|
||||
kind: "box-brush"
|
||||
}, center);
|
||||
}
|
||||
121
tests/e2e/whitebox-component-selection.e2e.js
Normal file
121
tests/e2e/whitebox-component-selection.e2e.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createBoxBrush } from "../../src/document/brushes";
|
||||
import { createEmptySceneDocument } from "../../src/document/scene-document";
|
||||
import { clickViewportAtRatio, getEditorStoreSnapshot, replaceSceneDocument } from "./viewport-test-helpers";
|
||||
test("whitebox component selection modes keep object picking intentional across perspective and orthographic panes", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
test.skip((await page.getByText("Viewport Unavailable").count()) > 0, "WebGL is unavailable in this Playwright environment.");
|
||||
const brush = createBoxBrush({
|
||||
id: "brush-selection-modes-main",
|
||||
name: "Selection Fixture",
|
||||
center: {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
},
|
||||
size: {
|
||||
x: 2,
|
||||
y: 2,
|
||||
z: 2
|
||||
}
|
||||
});
|
||||
await replaceSceneDocument(page, {
|
||||
...createEmptySceneDocument({ name: "Selection Modes Scene" }),
|
||||
brushes: {
|
||||
[brush.id]: brush
|
||||
}
|
||||
});
|
||||
await page.evaluate(({ target }) => {
|
||||
const store = window.__webeditor3dEditorStore;
|
||||
if (store === undefined) {
|
||||
throw new Error("Editor store debug hook is unavailable.");
|
||||
}
|
||||
const topLeftCameraState = store.getState().viewportPanels.topLeft.cameraState;
|
||||
store.setViewportPanelCameraState("topLeft", {
|
||||
...topLeftCameraState,
|
||||
target,
|
||||
perspectiveOrbit: {
|
||||
radius: 4.5,
|
||||
theta: 0.72,
|
||||
phi: 1.08
|
||||
}
|
||||
});
|
||||
}, { target: brush.center });
|
||||
await expect(page.getByTestId("viewport-selection-mode-topLeft")).toHaveText("Object");
|
||||
await clickViewportAtRatio(page, "topLeft", 0.5, 0.52);
|
||||
let snapshot = await getEditorStoreSnapshot(page);
|
||||
expect(snapshot.whiteboxSelectionMode).toBe("object");
|
||||
expect(snapshot.selection).toMatchObject({
|
||||
kind: "brushes",
|
||||
ids: [brush.id]
|
||||
});
|
||||
await page.getByTestId("viewport-layout-quad").click();
|
||||
await page.evaluate(({ target }) => {
|
||||
const store = window.__webeditor3dEditorStore;
|
||||
if (store === undefined) {
|
||||
throw new Error("Editor store debug hook is unavailable.");
|
||||
}
|
||||
const topRightCameraState = store.getState().viewportPanels.topRight.cameraState;
|
||||
store.setViewportPanelViewMode("topRight", "top");
|
||||
store.setViewportPanelCameraState("topRight", {
|
||||
...topRightCameraState,
|
||||
target,
|
||||
orthographicZoom: 8
|
||||
});
|
||||
}, { target: brush.center });
|
||||
await page.getByTestId("whitebox-selection-mode-face").click();
|
||||
await expect(page.getByTestId("viewport-selection-mode-topRight")).toHaveText("Face");
|
||||
await clickViewportAtRatio(page, "topRight", 0.5, 0.5);
|
||||
snapshot = await getEditorStoreSnapshot(page);
|
||||
expect(snapshot.whiteboxSelectionMode).toBe("face");
|
||||
expect(snapshot.selection).toMatchObject({
|
||||
kind: "brushFace",
|
||||
brushId: brush.id,
|
||||
faceId: "posY"
|
||||
});
|
||||
await expect(page.getByTestId("viewport-panel-active-badge-topRight")).toBeVisible();
|
||||
await page.getByTestId("whitebox-selection-mode-edge").click();
|
||||
await expect(page.getByTestId("viewport-selection-mode-topRight")).toHaveText("Edge");
|
||||
await clickViewportAtRatio(page, "topRight", 0.5, 0.12);
|
||||
snapshot = await getEditorStoreSnapshot(page);
|
||||
expect(snapshot.whiteboxSelectionMode).toBe("edge");
|
||||
expect(snapshot.selection).toMatchObject({
|
||||
kind: "brushEdge",
|
||||
brushId: brush.id,
|
||||
edgeId: "edgeX_posY_negZ"
|
||||
});
|
||||
await page.getByTestId("whitebox-selection-mode-vertex").click();
|
||||
await expect(page.getByTestId("viewport-selection-mode-topRight")).toHaveText("Vertex");
|
||||
await clickViewportAtRatio(page, "topRight", 0.88, 0.12);
|
||||
snapshot = await getEditorStoreSnapshot(page);
|
||||
expect(snapshot.whiteboxSelectionMode).toBe("vertex");
|
||||
expect(snapshot.selection).toMatchObject({
|
||||
kind: "brushVertex",
|
||||
brushId: brush.id,
|
||||
vertexId: "posX_posY_negZ"
|
||||
});
|
||||
await page.getByTestId("whitebox-selection-mode-object").click();
|
||||
await clickViewportAtRatio(page, "topRight", 0.5, 0.5);
|
||||
snapshot = await getEditorStoreSnapshot(page);
|
||||
expect(snapshot.whiteboxSelectionMode).toBe("object");
|
||||
expect(snapshot.selection).toMatchObject({
|
||||
kind: "brushes",
|
||||
ids: [brush.id]
|
||||
});
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
52
tests/e2e/world-environment.e2e.js
Normal file
52
tests/e2e/world-environment.e2e.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
async function setColorInput(locator, value) {
|
||||
await locator.evaluate((element, nextValue) => {
|
||||
const input = element;
|
||||
input.value = nextValue;
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}, value);
|
||||
}
|
||||
test("world environment settings persist and carry into the runner", async ({ page }) => {
|
||||
const pageErrors = [];
|
||||
const consoleErrors = [];
|
||||
page.on("pageerror", (error) => {
|
||||
pageErrors.push(error.message);
|
||||
});
|
||||
page.on("console", (message) => {
|
||||
if (message.type() === "error") {
|
||||
consoleErrors.push(message.text());
|
||||
}
|
||||
});
|
||||
await page.goto("/");
|
||||
await page.evaluate((storageKey) => {
|
||||
window.localStorage.removeItem(storageKey);
|
||||
}, "webeditor3d.scene-document-draft");
|
||||
await page.reload();
|
||||
await page.getByTestId("world-background-mode-gradient").click();
|
||||
await setColorInput(page.getByTestId("world-background-top-color"), "#6a87ab");
|
||||
await setColorInput(page.getByTestId("world-background-bottom-color"), "#151b23");
|
||||
await setColorInput(page.getByTestId("world-ambient-color"), "#d4e2ff");
|
||||
await page.getByTestId("world-ambient-intensity").fill("0.45");
|
||||
await page.getByTestId("world-ambient-intensity").press("Tab");
|
||||
await page.getByTestId("world-sun-intensity").fill("2.25");
|
||||
await page.getByTestId("world-sun-intensity").press("Tab");
|
||||
await page.getByTestId("world-sun-direction-x").fill("-1");
|
||||
await page.getByTestId("world-sun-direction-x").press("Tab");
|
||||
await expect(page.getByTestId("world-background-mode-value")).toContainText("Vertical Gradient");
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-image", /linear-gradient/);
|
||||
await page.getByRole("button", { name: "Save Draft" }).click();
|
||||
await page.getByTestId("world-background-mode-solid").click();
|
||||
await setColorInput(page.getByTestId("world-background-solid-color"), "#223344");
|
||||
await page.getByTestId("world-ambient-intensity").fill("0.9");
|
||||
await page.getByTestId("world-ambient-intensity").press("Tab");
|
||||
await page.getByRole("button", { name: "Load Draft" }).click();
|
||||
await expect(page.getByTestId("world-background-mode-value")).toContainText("Vertical Gradient");
|
||||
await expect(page.getByTestId("world-ambient-intensity")).toHaveValue("0.45");
|
||||
await expect(page.getByTestId("viewport-canvas-topLeft")).toHaveCSS("background-image", /linear-gradient/);
|
||||
await page.getByTestId("enter-run-mode").click();
|
||||
await expect(page.getByTestId("runner-shell")).toBeVisible();
|
||||
await expect(page.getByTestId("runner-shell")).toHaveCSS("background-image", /linear-gradient/);
|
||||
expect(pageErrors).toEqual([]);
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user