Add integration test for app scene transition
This commit is contained in:
271
tests/unit/app-scene-transition.integration.test.tsx
Normal file
271
tests/unit/app-scene-transition.integration.test.tsx
Normal file
@@ -0,0 +1,271 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
createEmptyProjectDocument,
|
||||
createEmptyProjectScene
|
||||
} from "../../src/document/scene-document";
|
||||
import {
|
||||
createPlayerStartEntity,
|
||||
createSceneEntryEntity,
|
||||
createSceneExitEntity
|
||||
} from "../../src/entities/entity-instances";
|
||||
|
||||
const { MockViewportHost, viewportHostInstances } = vi.hoisted(() => {
|
||||
const viewportHostInstances: Array<{
|
||||
setTransformCommitHandler: ReturnType<typeof vi.fn>;
|
||||
setRenderEnabled: ReturnType<typeof vi.fn>;
|
||||
mount: ReturnType<typeof vi.fn>;
|
||||
dispose: ReturnType<typeof vi.fn>;
|
||||
updateWorld: ReturnType<typeof vi.fn>;
|
||||
updateAssets: ReturnType<typeof vi.fn>;
|
||||
updateDocument: ReturnType<typeof vi.fn>;
|
||||
setPanelId: ReturnType<typeof vi.fn>;
|
||||
setViewMode: ReturnType<typeof vi.fn>;
|
||||
setDisplayMode: ReturnType<typeof vi.fn>;
|
||||
setGridVisible: ReturnType<typeof vi.fn>;
|
||||
setCameraState: ReturnType<typeof vi.fn>;
|
||||
setBrushSelectionChangeHandler: ReturnType<typeof vi.fn>;
|
||||
setCameraStateChangeHandler: ReturnType<typeof vi.fn>;
|
||||
setCreationPreviewChangeHandler: ReturnType<typeof vi.fn>;
|
||||
setCreationCommitHandler: ReturnType<typeof vi.fn>;
|
||||
setTransformSessionChangeHandler: ReturnType<typeof vi.fn>;
|
||||
setTransformCancelHandler: ReturnType<typeof vi.fn>;
|
||||
setWhiteboxHoverLabelChangeHandler: ReturnType<typeof vi.fn>;
|
||||
setWhiteboxSelectionMode: ReturnType<typeof vi.fn>;
|
||||
setWhiteboxSnapSettings: ReturnType<typeof vi.fn>;
|
||||
setToolMode: ReturnType<typeof vi.fn>;
|
||||
setCreationPreview: ReturnType<typeof vi.fn>;
|
||||
setTransformSession: ReturnType<typeof vi.fn>;
|
||||
focusSelection: ReturnType<typeof vi.fn>;
|
||||
}> = [];
|
||||
|
||||
class MockViewportHost {
|
||||
setPanelId = vi.fn();
|
||||
setRenderEnabled = vi.fn();
|
||||
mount = vi.fn();
|
||||
dispose = vi.fn();
|
||||
updateWorld = vi.fn();
|
||||
updateAssets = vi.fn();
|
||||
updateDocument = vi.fn();
|
||||
setViewMode = vi.fn();
|
||||
setDisplayMode = vi.fn();
|
||||
setGridVisible = vi.fn();
|
||||
setCameraState = vi.fn();
|
||||
setBrushSelectionChangeHandler = vi.fn();
|
||||
setCameraStateChangeHandler = vi.fn();
|
||||
setCreationPreviewChangeHandler = vi.fn();
|
||||
setCreationCommitHandler = vi.fn();
|
||||
setTransformSessionChangeHandler = vi.fn();
|
||||
setTransformCommitHandler = vi.fn();
|
||||
setTransformCancelHandler = vi.fn();
|
||||
setWhiteboxHoverLabelChangeHandler = vi.fn();
|
||||
setWhiteboxSelectionMode = vi.fn();
|
||||
setWhiteboxSnapSettings = vi.fn();
|
||||
setToolMode = vi.fn();
|
||||
setCreationPreview = vi.fn();
|
||||
setTransformSession = vi.fn();
|
||||
focusSelection = vi.fn();
|
||||
|
||||
constructor() {
|
||||
viewportHostInstances.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
MockViewportHost,
|
||||
viewportHostInstances
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../src/viewport-three/viewport-host", () => ({
|
||||
ViewportHost: MockViewportHost
|
||||
}));
|
||||
|
||||
vi.mock("../../src/assets/project-asset-storage", () => ({
|
||||
getBrowserProjectAssetStorageAccess: vi.fn(async () => ({
|
||||
storage: null,
|
||||
diagnostic: null
|
||||
}))
|
||||
}));
|
||||
|
||||
vi.mock("../../src/runner-web/RunnerCanvas", () => ({
|
||||
RunnerCanvas: (props: {
|
||||
sceneName: string;
|
||||
runtimeScene: {
|
||||
entities: {
|
||||
sceneExits: Array<{
|
||||
entityId: string;
|
||||
targetSceneId: string;
|
||||
targetEntryEntityId: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
onSceneExitActivated(request: {
|
||||
sourceExitEntityId: string;
|
||||
targetSceneId: string;
|
||||
targetEntryEntityId: string;
|
||||
}): void;
|
||||
}) => {
|
||||
const firstSceneExit = props.runtimeScene.entities.sceneExits[0] ?? null;
|
||||
|
||||
return (
|
||||
<div data-testid="mock-runner-canvas">
|
||||
<div data-testid="mock-runner-scene-name">{props.sceneName}</div>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="mock-trigger-scene-exit"
|
||||
disabled={firstSceneExit === null}
|
||||
onClick={() => {
|
||||
if (firstSceneExit === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
props.onSceneExitActivated({
|
||||
sourceExitEntityId: firstSceneExit.entityId,
|
||||
targetSceneId: firstSceneExit.targetSceneId,
|
||||
targetEntryEntityId: firstSceneExit.targetEntryEntityId
|
||||
});
|
||||
}}
|
||||
>
|
||||
Trigger Scene Exit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
import { App } from "../../src/app/App";
|
||||
import { createEditorStore } from "../../src/app/editor-store";
|
||||
|
||||
function createSceneTransitionProject() {
|
||||
const outdoorScene = createEmptyProjectScene({
|
||||
id: "scene-outside",
|
||||
name: "Outside"
|
||||
});
|
||||
const houseScene = createEmptyProjectScene({
|
||||
id: "scene-house",
|
||||
name: "House"
|
||||
});
|
||||
const outdoorPlayerStart = createPlayerStartEntity({
|
||||
id: "entity-player-start-outside",
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
yawDegrees: 90
|
||||
});
|
||||
const housePlayerStart = createPlayerStartEntity({
|
||||
id: "entity-player-start-house",
|
||||
position: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
yawDegrees: 180
|
||||
});
|
||||
const houseEntry = createSceneEntryEntity({
|
||||
id: "entity-scene-entry-house-front",
|
||||
position: {
|
||||
x: 3,
|
||||
y: 0,
|
||||
z: -1
|
||||
},
|
||||
yawDegrees: 270
|
||||
});
|
||||
const outsideExit = createSceneExitEntity({
|
||||
id: "entity-scene-exit-front-door",
|
||||
position: {
|
||||
x: 2,
|
||||
y: 1,
|
||||
z: 0
|
||||
},
|
||||
prompt: "Enter House",
|
||||
targetSceneId: houseScene.id,
|
||||
targetEntryEntityId: houseEntry.id
|
||||
});
|
||||
const projectDocument = createEmptyProjectDocument({
|
||||
sceneId: outdoorScene.id,
|
||||
sceneName: outdoorScene.name
|
||||
});
|
||||
|
||||
projectDocument.activeSceneId = outdoorScene.id;
|
||||
projectDocument.scenes = {
|
||||
[outdoorScene.id]: {
|
||||
...outdoorScene,
|
||||
entities: {
|
||||
[outdoorPlayerStart.id]: outdoorPlayerStart,
|
||||
[outsideExit.id]: outsideExit
|
||||
}
|
||||
},
|
||||
[houseScene.id]: {
|
||||
...houseScene,
|
||||
entities: {
|
||||
[housePlayerStart.id]: housePlayerStart,
|
||||
[houseEntry.id]: houseEntry
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return projectDocument;
|
||||
}
|
||||
|
||||
describe("App scene transition flow", () => {
|
||||
beforeEach(() => {
|
||||
viewportHostInstances.length = 0;
|
||||
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockImplementation(
|
||||
() => ({}) as never
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("switches runtime scenes through Scene Exits without changing the editor active scene", async () => {
|
||||
const store = createEditorStore({
|
||||
initialProjectDocument: createSceneTransitionProject()
|
||||
});
|
||||
|
||||
render(<App store={store} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(viewportHostInstances.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Run Scene" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("mock-runner-scene-name")).toHaveTextContent(
|
||||
"Outside"
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("runner-scene-name")).toHaveTextContent(
|
||||
"Outside"
|
||||
);
|
||||
expect(screen.getByTestId("runner-transition-count")).toHaveTextContent(
|
||||
"0"
|
||||
);
|
||||
expect(store.getState().activeSceneId).toBe("scene-outside");
|
||||
|
||||
fireEvent.click(screen.getByTestId("mock-trigger-scene-exit"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("mock-runner-scene-name")).toHaveTextContent(
|
||||
"House"
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("runner-scene-name")).toHaveTextContent("House");
|
||||
expect(screen.getByTestId("runner-transition-count")).toHaveTextContent(
|
||||
"1"
|
||||
);
|
||||
expect(screen.getByTestId("runner-spawn-state")).toHaveTextContent(
|
||||
"Scene Entry"
|
||||
);
|
||||
expect(screen.getByText("Last transition: Outside to House")).toBeVisible();
|
||||
expect(store.getState().activeSceneId).toBe("scene-outside");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user