Add tests and update navigation controller deactivation logic
This commit is contained in:
@@ -3,6 +3,7 @@ import { Vector3 } from "three";
|
||||
import type { Vec3 } from "../core/vector";
|
||||
|
||||
import type {
|
||||
NavigationControllerDeactivateOptions,
|
||||
NavigationController,
|
||||
RuntimeControllerContext
|
||||
} from "./navigation-controller";
|
||||
@@ -83,7 +84,11 @@ export class OrbitVisitorNavigationController implements NavigationController {
|
||||
this.updateCameraTransform();
|
||||
}
|
||||
|
||||
deactivate(ctx: RuntimeControllerContext): void {
|
||||
deactivate(
|
||||
ctx: RuntimeControllerContext,
|
||||
_options: NavigationControllerDeactivateOptions = {}
|
||||
): void {
|
||||
void _options;
|
||||
ctx.domElement.removeEventListener("pointerdown", this.handlePointerDown);
|
||||
ctx.domElement.removeEventListener("wheel", this.handleWheel);
|
||||
ctx.domElement.removeEventListener("contextmenu", this.handleContextMenu);
|
||||
|
||||
@@ -342,11 +342,17 @@ export class RuntimeHost {
|
||||
|
||||
loadScene(runtimeScene: RuntimeSceneDefinition) {
|
||||
const requestId = ++this.collisionWorldRequestId;
|
||||
const preservePointerLockDuringLoad =
|
||||
this.activeController === this.firstPersonController &&
|
||||
this.desiredNavigationMode === "firstPerson" &&
|
||||
document.pointerLockElement === this.domElement;
|
||||
|
||||
this.sceneReady = false;
|
||||
this.runtimeScene = runtimeScene;
|
||||
this.currentWorld = runtimeScene.world;
|
||||
this.activeController?.deactivate(this.controllerContext);
|
||||
this.activeController?.deactivate(this.controllerContext, {
|
||||
releasePointerLock: !preservePointerLockDuringLoad
|
||||
});
|
||||
this.activeController = null;
|
||||
this.firstPersonController.resetSceneState();
|
||||
this.orbitVisitorController.resetSceneState();
|
||||
|
||||
84
tests/unit/first-person-navigation-controller.test.ts
Normal file
84
tests/unit/first-person-navigation-controller.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { PerspectiveCamera } from "three";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createEmptySceneDocument } from "../../src/document/scene-document";
|
||||
import { createPlayerStartEntity } from "../../src/entities/entity-instances";
|
||||
import { buildRuntimeSceneFromDocument } from "../../src/runtime-three/runtime-scene-build";
|
||||
import { FirstPersonNavigationController } from "../../src/runtime-three/first-person-navigation-controller";
|
||||
|
||||
function createRuntimeControllerContext() {
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||
{
|
||||
...createEmptySceneDocument({ name: "Pointer Lock Scene" }),
|
||||
entities: {
|
||||
"entity-player-start-main": createPlayerStartEntity({
|
||||
id: "entity-player-start-main"
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
navigationMode: "firstPerson"
|
||||
}
|
||||
);
|
||||
const domElement = document.createElement("canvas");
|
||||
|
||||
return {
|
||||
domElement,
|
||||
context: {
|
||||
camera: new PerspectiveCamera(70, 1, 0.05, 1000),
|
||||
domElement,
|
||||
getRuntimeScene: () => runtimeScene,
|
||||
resolveFirstPersonMotion: () => null,
|
||||
resolvePlayerVolumeState: () => ({
|
||||
inWater: false,
|
||||
inFog: false
|
||||
}),
|
||||
setRuntimeMessage: vi.fn(),
|
||||
setFirstPersonTelemetry: vi.fn()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe("FirstPersonNavigationController", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("can deactivate during a scene transition without releasing pointer lock", () => {
|
||||
const { context, domElement } = createRuntimeControllerContext();
|
||||
const controller = new FirstPersonNavigationController();
|
||||
const exitPointerLockSpy = vi
|
||||
.spyOn(document, "exitPointerLock")
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
Object.defineProperty(document, "pointerLockElement", {
|
||||
configurable: true,
|
||||
get: () => domElement
|
||||
});
|
||||
|
||||
controller.activate(context);
|
||||
controller.deactivate(context, {
|
||||
releasePointerLock: false
|
||||
});
|
||||
|
||||
expect(exitPointerLockSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("still releases pointer lock for a normal deactivation", () => {
|
||||
const { context, domElement } = createRuntimeControllerContext();
|
||||
const controller = new FirstPersonNavigationController();
|
||||
const exitPointerLockSpy = vi
|
||||
.spyOn(document, "exitPointerLock")
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
Object.defineProperty(document, "pointerLockElement", {
|
||||
configurable: true,
|
||||
get: () => domElement
|
||||
});
|
||||
|
||||
controller.activate(context);
|
||||
controller.deactivate(context);
|
||||
|
||||
expect(exitPointerLockSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user