Files
shacraft-core/client/tests/pointer-lock.test.js
Emil c7e86663d8
MVP checks / mvp (push) Waiting to run
Expand voxel gameplay, lighting, full-height streaming and world imports
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
2026-09-17 02:10:53 +03:00

170 lines
5.5 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { PointerLockController } from "../pointer-lock.js";
function deferred() {
let resolve, reject;
const promise = new Promise((yes, no) => {
resolve = yes;
reject = no;
});
return { promise, resolve, reject };
}
function fixture(t, request) {
const document = new EventTarget(),
calls = [],
changes = [],
errors = [];
document.pointerLockElement = null;
document.exitPointerLock = () => {
calls.push("exit");
document.pointerLockElement = null;
document.dispatchEvent(new Event("pointerlockchange"));
};
const canvas = {
ownerDocument: document,
tabIndex: -1,
focus(options) {
calls.push("focus");
assert.equal(options.preventScroll, true);
},
requestPointerLock() {
calls.push("request");
return request?.();
},
};
const controller = new PointerLockController(canvas, {
onChange: (locked) => changes.push(locked),
onError: (error) => errors.push(error),
});
const lock = () => {
document.pointerLockElement = canvas;
document.dispatchEvent(new Event("pointerlockchange"));
};
t.after(() => controller.dispose());
return { controller, canvas, document, calls, changes, errors, lock };
}
test("focus precedes the synchronous user-gesture request and duplicate pending calls are ignored", (t) => {
const pending = deferred(),
{ controller, canvas, calls } = fixture(t, () => pending.promise);
assert.equal(controller.request(), true);
assert.deepEqual(calls, ["focus", "request"]);
assert.equal(canvas.tabIndex, 0);
assert.equal(controller.status, "requesting");
assert.equal(controller.request(), false);
assert.deepEqual(calls, ["focus", "request"]);
});
test("a rejected request remains retryable and subsequent success clears the error", async (t) => {
const first = deferred(),
second = deferred();
let count = 0;
const { controller, errors, changes, lock } = fixture(t, () =>
++count === 1 ? first.promise : second.promise,
);
controller.request();
const error = new Error("Document not focused");
first.reject(error);
await Promise.resolve();
assert.equal(controller.status, "error");
assert.equal(controller.lastError, error);
assert.deepEqual(errors, [error]);
assert.equal(controller.request(), true);
lock();
second.resolve();
await Promise.resolve();
assert.equal(controller.status, "locked");
assert.equal(controller.locked, true);
assert.equal(controller.lastError, null);
assert.deepEqual(changes, [true]);
});
test("legacy success, ordinary unlock, and recapture follow actual document state", (t) => {
const { controller, document, changes, lock, calls } = fixture(t);
controller.request();
lock();
assert.equal(controller.request(), false);
document.exitPointerLock();
assert.equal(controller.status, "idle");
assert.equal(controller.locked, false);
assert.equal(controller.request(), true);
lock();
assert.deepEqual(changes, [true, false, true]);
assert.equal(calls.filter((call) => call === "request").length, 2);
});
test("missing APIs and legacy error events report failure without permanently disabling retries", (t) => {
const { controller, canvas, document, errors, lock } = fixture(t);
delete canvas.requestPointerLock;
assert.equal(controller.request(), false);
assert.equal(controller.status, "unsupported");
assert.equal(errors[0].name, "NotSupportedError");
canvas.requestPointerLock = () => undefined;
assert.equal(controller.request(), true);
document.dispatchEvent(new Event("pointerlockerror"));
assert.equal(controller.status, "error");
assert.equal(errors.length, 2);
assert.equal(controller.request(), true);
lock();
assert.equal(controller.status, "locked");
});
test("a stale promise rejection cannot overwrite a later successful capture", async (t) => {
const first = deferred();
const { controller, document, errors, lock } = fixture(
t,
() => first.promise,
);
controller.request();
document.dispatchEvent(new Event("pointerlockerror"));
controller.request();
lock();
first.reject(new Error("Late failure from an old request"));
await Promise.resolve();
assert.equal(controller.status, "locked");
assert.equal(controller.lastError, null);
assert.equal(errors.length, 1);
});
test("release cancels pending capture and immediately exits a late acquisition", async (t) => {
const pending = deferred(),
{ controller, changes, errors, lock, calls } = fixture(
t,
() => pending.promise,
);
controller.request();
controller.release();
assert.equal(controller.status, "idle");
lock();
pending.resolve();
await Promise.resolve();
assert.equal(controller.locked, false);
assert.equal(controller.status, "idle");
assert.deepEqual(changes, []);
assert.deepEqual(errors, []);
assert.equal(calls.filter((call) => call === "exit").length, 2);
});
test("synchronous failures are retryable and disposal removes event handling", (t) => {
const { controller, canvas, document, errors, changes, lock } = fixture(
t,
() => {
throw new Error("Synchronous rejection");
},
);
assert.equal(controller.request(), false);
assert.equal(controller.status, "error");
assert.equal(errors.length, 1);
canvas.requestPointerLock = () => undefined;
controller.request();
lock();
controller.dispose();
assert.deepEqual(changes, [true, false]);
assert.equal(controller.request(), false);
lock();
document.dispatchEvent(new Event("pointerlockerror"));
assert.deepEqual(changes, [true, false]);
assert.equal(errors.length, 1);
});