mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
The harness conversion replaced pauses that stood in for real conditions with camera readiness: the cables and infrastructure idle checks started measuring while the overlay was still fading in after moveEnd, directions sampled before its maneuver dots had ground cells, three scripts clicked before the loading cover was gone, the credits check hit-tested during UI transitions, and the oblique traffic flight lost its height cap. Each now waits for its condition; idle checks observe without requesting frames and record the reasons of the frames they count; readiness waits for the startup cover and no longer requests frames while polling.
310 lines
9.8 KiB
JavaScript
310 lines
9.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createFakeEngine } from '../../src/globe/testing/fakeEngine.js';
|
|
import { createSettledState } from '../../src/app/qaProbe.js';
|
|
import {
|
|
waitForSettled,
|
|
whenSettled,
|
|
whenFramesDrawn,
|
|
whenTilesLoaded,
|
|
whenUiSettled,
|
|
pollPage,
|
|
until,
|
|
} from './ready.mjs';
|
|
|
|
function pageEngine(t) {
|
|
const engine = createFakeEngine();
|
|
const previous = globalThis.window;
|
|
const launches = { qaDrawn: () => ({ paused: false, replay: null }) };
|
|
const app = {
|
|
engine,
|
|
launches,
|
|
dataManager: { layers: new Map([['rocket-launches', { module: launches }]]) },
|
|
styleManager: {
|
|
initialRestorePromise: Promise.resolve(),
|
|
getCockpitState: () => ({ active: false }),
|
|
},
|
|
};
|
|
globalThis.window = { __godsEyeView: app };
|
|
t.after(() => {
|
|
if (previous === undefined) delete globalThis.window;
|
|
else globalThis.window = previous;
|
|
engine.destroy();
|
|
});
|
|
return app;
|
|
}
|
|
|
|
test('cockpit, follow and replay readiness allow held inputs and moving poses after drawn frames', async (t) => {
|
|
for (const owner of ['cockpit', 'follow', 'replay']) {
|
|
await t.test(owner, async (t) => {
|
|
const { engine, styleManager, launches } = pageEngine(t);
|
|
engine.input.suspendCameraInputs();
|
|
let drawn = 0;
|
|
if (owner === 'cockpit')
|
|
styleManager.getCockpitState = () => ({ active: true });
|
|
else if (owner === 'replay')
|
|
launches.qaDrawn = () => ({ paused: false, replay: { active: true } });
|
|
else
|
|
engine.camera.follow({
|
|
position: () => ({ x: 6378137, y: drawn * 10, z: 0 }),
|
|
viewFrom: { x: 0, y: -100, z: 100 },
|
|
});
|
|
t.mock.method(engine.render, 'request', () =>
|
|
setImmediate(() => {
|
|
engine.fake.setPose({ heading: ++drawn / 100 });
|
|
engine.fake.frame();
|
|
}),
|
|
);
|
|
const status = await waitForSettled(createSettledState, {
|
|
frames: 3,
|
|
timeoutMs: 1000,
|
|
});
|
|
assert.equal(status.settled, true);
|
|
assert.equal(status.framesDrawn, 3);
|
|
assert.equal(status.observed.inputsEnabled, false);
|
|
assert.equal(status.observed.cockpitActive, owner === 'cockpit');
|
|
assert.equal(status.observed.following, owner === 'follow');
|
|
assert.equal(status.observed.replaying, owner === 'replay');
|
|
assert.ok(status.observed.lastPoseDelta.orientation > 0);
|
|
assert.equal(engine.frames.postRender.numberOfListeners, 0);
|
|
});
|
|
}
|
|
});
|
|
|
|
test('camera owners do not excuse restoration, flights, missing frames or destruction', async (t) => {
|
|
for (const unmet of [
|
|
'initialViewRestored',
|
|
'noFlight',
|
|
'frameDrawn',
|
|
'engineStarted',
|
|
]) {
|
|
await t.test(unmet, async (t) => {
|
|
const { engine, styleManager } = pageEngine(t);
|
|
styleManager.getCockpitState = () => ({ active: true });
|
|
engine.input.suspendCameraInputs();
|
|
if (unmet === 'initialViewRestored')
|
|
styleManager.initialRestorePromise = new Promise(() => {});
|
|
if (unmet === 'noFlight')
|
|
engine.camera.flyTo({
|
|
destination: { x: 7000000, y: 0, z: 0 },
|
|
duration: 1,
|
|
});
|
|
t.mock.method(engine.render, 'request', () => {});
|
|
const pending = waitForSettled(createSettledState, {
|
|
frames: 3,
|
|
timeoutMs: 10,
|
|
});
|
|
await Promise.resolve();
|
|
if (unmet === 'engineStarted') {
|
|
engine.destroy();
|
|
t.mock.method(engine.camera, 'pose', () => {
|
|
throw Error('released camera');
|
|
});
|
|
} else engine.fake.frames(unmet === 'frameDrawn' ? 2 : 4);
|
|
await assert.rejects(pending, (error) => {
|
|
assert.match(error.message, new RegExp(`Readiness timeout: ${unmet}`));
|
|
assert.match(error.message, /"cockpitActive":true/);
|
|
return true;
|
|
});
|
|
assert.equal(engine.frames.postRender.numberOfListeners, 0);
|
|
});
|
|
}
|
|
});
|
|
|
|
test('unowned input locks and moving cameras time out with observed state', async (t) => {
|
|
for (const moving of [false, true]) {
|
|
await t.test(moving ? 'moving' : 'locked', async (t) => {
|
|
const { engine } = pageEngine(t);
|
|
if (!moving) engine.input.suspendCameraInputs();
|
|
t.mock.method(engine.render, 'request', () => {});
|
|
const pending = waitForSettled(createSettledState, {
|
|
frames: 3,
|
|
timeoutMs: 10,
|
|
});
|
|
await Promise.resolve();
|
|
for (let i = 0; i < 5; i++) {
|
|
if (moving)
|
|
engine.fake.setPose({ position: { x: 7000000 + i, y: 0, z: 0 } });
|
|
engine.fake.frame();
|
|
}
|
|
await assert.rejects(pending, (error) => {
|
|
assert.match(error.message, /cameraPoseStable/);
|
|
if (!moving) assert.match(error.message, /cameraInputsEnabled/);
|
|
const observed = JSON.parse(error.message.split('; observed: ')[1]);
|
|
assert.deepEqual(observed, {
|
|
inputsEnabled: moving,
|
|
following: false,
|
|
cockpitActive: false,
|
|
replaying: false,
|
|
lastPoseDelta: {
|
|
positionM: moving ? 1 : 0,
|
|
orientation: 0,
|
|
projection: 0,
|
|
},
|
|
});
|
|
return true;
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
test('leaving a camera owner restores strict input and pose requirements', async (t) => {
|
|
for (const owner of ['cockpit', 'follow']) {
|
|
await t.test(owner, async (t) => {
|
|
const { engine, styleManager } = pageEngine(t);
|
|
engine.input.suspendCameraInputs();
|
|
let active = owner === 'cockpit';
|
|
styleManager.getCockpitState = () => ({ active });
|
|
const follow =
|
|
owner === 'follow'
|
|
? engine.camera.follow({
|
|
position: () => ({ x: 6378137, y: 0, z: 0 }),
|
|
viewFrom: { x: 0, y: -100, z: 100 },
|
|
})
|
|
: null;
|
|
t.mock.method(engine.render, 'request', () => {});
|
|
const pending = waitForSettled(createSettledState, {
|
|
frames: 3,
|
|
timeoutMs: 10,
|
|
});
|
|
await Promise.resolve();
|
|
engine.fake.frames(2);
|
|
active = false;
|
|
follow?.stop();
|
|
engine.fake.frames(4);
|
|
await assert.rejects(pending, /cameraInputsEnabled, cameraPoseStable/);
|
|
});
|
|
}
|
|
});
|
|
|
|
test('frame readiness advances a request-mode engine and removes its listener', async (t) => {
|
|
const engine = createFakeEngine();
|
|
const previous = globalThis.window;
|
|
globalThis.window = { __godsEyeView: { engine } };
|
|
t.after(() => {
|
|
if (previous === undefined) delete globalThis.window;
|
|
else globalThis.window = previous;
|
|
engine.destroy();
|
|
});
|
|
let frames = 0;
|
|
t.mock.method(engine.render, 'request', () =>
|
|
queueMicrotask(() => {
|
|
frames++;
|
|
engine.fake.frame();
|
|
}),
|
|
);
|
|
const page = { evaluate: (fn, value) => fn(value) };
|
|
await whenFramesDrawn(page, { frames: 3, timeoutMs: 1000 });
|
|
assert.equal(frames, 3);
|
|
engine.fake.frame();
|
|
await Promise.resolve();
|
|
assert.equal(frames, 3, 'completion does not leave a render pump');
|
|
t.mock.method(engine.render, 'request', () => {});
|
|
await assert.rejects(
|
|
whenFramesDrawn(page, { timeoutMs: 5 }),
|
|
/Readiness timeout: drawn frames/,
|
|
);
|
|
});
|
|
|
|
test('page readiness disposes polling handles and only treats timeouts as absence', async () => {
|
|
let disposed = 0;
|
|
const page = {
|
|
waitForFunction: async () => ({
|
|
dispose: async () => {
|
|
disposed++;
|
|
},
|
|
}),
|
|
};
|
|
await whenTilesLoaded(page);
|
|
await whenUiSettled(page);
|
|
assert.equal(await pollPage(page, () => true, { timeoutMs: 10 }), true);
|
|
assert.equal(disposed, 3);
|
|
page.waitForFunction = async () => {
|
|
throw Object.assign(new Error('late'), { name: 'TimeoutError' });
|
|
};
|
|
assert.equal(await pollPage(page, () => false, { timeoutMs: 10 }), false);
|
|
page.waitForFunction = async () => {
|
|
throw new Error('page closed');
|
|
};
|
|
await assert.rejects(
|
|
pollPage(page, () => true, { timeoutMs: 10 }),
|
|
/page closed/,
|
|
);
|
|
});
|
|
|
|
test('scenario polling waits for evidence and identifies an unmet condition', async () => {
|
|
let calls = 0;
|
|
assert.equal(
|
|
await until(() => ++calls === 3 && 'ready', { intervalMs: 1 }),
|
|
'ready',
|
|
);
|
|
await assert.rejects(
|
|
until(() => false, {
|
|
timeoutMs: 5,
|
|
intervalMs: 1,
|
|
label: 'fixture response',
|
|
}),
|
|
/Readiness timeout: fixture response/,
|
|
);
|
|
await assert.rejects(
|
|
until(() => {
|
|
throw new Error('broken predicate');
|
|
}),
|
|
/broken predicate/,
|
|
);
|
|
});
|
|
|
|
test('camera readiness also waits for the startup cover to stop intercepting clicks', async (t) => {
|
|
pageEngine(t);
|
|
const previous = {
|
|
document: globalThis.document,
|
|
getComputedStyle: globalThis.getComputedStyle,
|
|
};
|
|
let visibility = 'visible';
|
|
globalThis.document = { querySelector: () => ({}) };
|
|
globalThis.getComputedStyle = () => ({ visibility });
|
|
t.after(() => {
|
|
for (const [key, value] of Object.entries(previous)) {
|
|
if (value === undefined) delete globalThis[key];
|
|
else globalThis[key] = value;
|
|
}
|
|
});
|
|
let disposed = false;
|
|
await whenSettled({
|
|
async waitForFunction(predicate) {
|
|
assert.equal(predicate(), false, 'a started engine still has a cover');
|
|
visibility = 'hidden';
|
|
assert.equal(predicate(), true);
|
|
return {
|
|
dispose: async () => {
|
|
disposed = true;
|
|
},
|
|
};
|
|
},
|
|
async evaluate() {
|
|
assert.equal(disposed, true);
|
|
},
|
|
});
|
|
});
|
|
|
|
test('tile and UI polling never request globe frames', async (t) => {
|
|
const { engine } = pageEngine(t);
|
|
const previous = globalThis.document;
|
|
globalThis.document = { getAnimations: () => [] };
|
|
t.after(() => {
|
|
if (previous === undefined) delete globalThis.document;
|
|
else globalThis.document = previous;
|
|
});
|
|
t.mock.method(engine.render, 'request', () => {
|
|
assert.fail('passive readiness requested a frame');
|
|
});
|
|
const page = {
|
|
async waitForFunction(predicate) {
|
|
assert.equal(predicate(), true);
|
|
return { dispose: async () => {} };
|
|
},
|
|
};
|
|
await whenTilesLoaded(page);
|
|
await whenUiSettled(page);
|
|
});
|