Files
gods-eye-view/scripts/qa/ready.test.mjs
T
German Khokhlov b5cc4feacb Restore the conditions eight converted QA scripts were waiting for
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.
2026-11-10 20:14:50 +04:00

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);
});