Files
webeditor3d/tests/unit/app-scene-transition.integration.test.tsx

285 lines
8.3 KiB
TypeScript

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,
createNpcEntity
} from "../../src/entities/entity-instances";
import {
createActorControlTargetRef,
createSetActorPresenceControlEffect
} from "../../src/controls/control-surface";
import { createProjectScheduleRoutine } from "../../src/scheduler/project-scheduler";
import { createProjectSequence } from "../../src/sequencer/project-sequences";
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>;
updateSelection: ReturnType<typeof vi.fn>;
updateSimulation: 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>;
setTransformPreviewChangeHandler: 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();
updateSelection = vi.fn();
updateSimulation = 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();
setTransformPreviewChangeHandler = 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;
onSceneTransitionActivated(request: {
sourceEntityId: string | null;
targetSceneId: string;
targetEntryEntityId: string;
}): void;
}) => {
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-transition"
onClick={() => {
props.onSceneTransitionActivated({
sourceEntityId: "entity-interactable-front-door",
targetSceneId: "scene-house",
targetEntryEntityId: "entity-scene-entry-house-front"
});
}}
>
Trigger Scene Transition
</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 ana = createNpcEntity({
id: "entity-npc-ana-nanto",
actorId: "Ana Nanto"
});
const anaTarget = createActorControlTargetRef(ana.actorId);
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 projectDocument = createEmptyProjectDocument({
sceneId: outdoorScene.id,
sceneName: outdoorScene.name
});
projectDocument.activeSceneId = outdoorScene.id;
projectDocument.scenes = {
[outdoorScene.id]: {
...outdoorScene,
entities: {
[outdoorPlayerStart.id]: outdoorPlayerStart,
[ana.id]: ana
}
},
[houseScene.id]: {
...houseScene,
entities: {
[housePlayerStart.id]: housePlayerStart,
[houseEntry.id]: houseEntry
}
}
};
projectDocument.sequences.sequences["sequence-ana-presence"] =
createProjectSequence({
id: "sequence-ana-presence",
title: "Ana Presence",
effects: [
{
stepClass: "held",
type: "controlEffect",
effect: createSetActorPresenceControlEffect({
target: anaTarget,
active: true
})
}
]
});
projectDocument.scheduler.routines["routine-ana-presence"] =
createProjectScheduleRoutine({
id: "routine-ana-presence",
title: "Ana Presence",
target: anaTarget,
sequenceId: "sequence-ana-presence",
effects: []
});
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 transition requests 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-transition"));
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");
});
});