import { waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createEmptySceneDocument } from "../../src/document/scene-document"; import { RapierCollisionWorld } from "../../src/runtime-three/rapier-collision-world"; import { RuntimeHost, type RuntimeSceneLoadState } from "../../src/runtime-three/runtime-host"; import { buildRuntimeSceneFromDocument } from "../../src/runtime-three/runtime-scene-build"; function createDeferred() { let resolve: ((value: T) => void) | null = null; let reject: ((error: unknown) => void) | null = null; const promise = new Promise((innerResolve, innerReject) => { resolve = innerResolve; reject = innerReject; }); return { promise, resolve(value: T) { resolve?.(value); }, reject(error: unknown) { reject?.(error); } }; } describe("RuntimeHost", () => { afterEach(() => { vi.restoreAllMocks(); }); it("delays controller activation until collision setup reports the scene as ready", async () => { const runtimeScene = buildRuntimeSceneFromDocument( createEmptySceneDocument() ); vi.spyOn(console, "warn").mockImplementation(() => undefined); const collisionWorld = { dispose: vi.fn(), resolveThirdPersonCameraCollision: vi.fn( (_pivot, desiredCameraPosition) => desiredCameraPosition ) } as unknown as RapierCollisionWorld; const deferredCollisionWorld = createDeferred(); vi.spyOn(RapierCollisionWorld, "create").mockReturnValue( deferredCollisionWorld.promise ); const runtimeMessages: Array = []; const sceneLoadStates: RuntimeSceneLoadState[] = []; const host = new RuntimeHost({ enableRendering: false }); host.setRuntimeMessageHandler((message) => { runtimeMessages.push(message); }); host.setSceneLoadStateHandler((state) => { sceneLoadStates.push(state); }); host.loadScene(runtimeScene); host.setNavigationMode("thirdPerson"); expect(sceneLoadStates).toEqual([ { status: "loading", message: null } ]); expect(runtimeMessages).toEqual([null]); deferredCollisionWorld.resolve(collisionWorld); await waitFor(() => { expect(sceneLoadStates).toContainEqual({ status: "ready", message: null }); expect(runtimeMessages).toContain( "Third Person active. Drag to orbit the camera, use the right stick for gamepad camera look, move with your authored bindings, and scroll to zoom." ); }); host.dispose(); expect(collisionWorld.dispose).toHaveBeenCalledTimes(1); }); });