mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Check that an engine switch draws what a cold start from the share link draws
qa-switch-equals-cold-start switches engines in both directions with each layer on deterministic fixtures, then opens the share link of the same state in a new page on the target engine, and compares the draw ledgers and the probe's view of the layer. It runs as 19 acceptance groups covering all 38 layers. State a layer keeps through a switch but the share link does not carry is declared as sessionState (the directions route, Radio's selected station and audio state, the wind reading, local ADS-B's and OSM Places' local toggles and results); a test checks each declaration against what the layer really retains, so a declaration cannot hide a regression.
This commit is contained in:
1 parent
59ff0b38a2
commit
7c5ebf4814
12 files changed
+2020
No files matched your search
@@ -0,0 +1,258 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import vm from 'node:vm';
|
||||
import { createFakeEngine } from '../../src/globe/testing/fakeEngine.js';
|
||||
import { APPLICATION_LAYER_DIRECTORY } from '../../src/app/layers/directory.js';
|
||||
import {
|
||||
COLD_START_GROUPS,
|
||||
coldStartUrl,
|
||||
compareProbe,
|
||||
compareColdStart,
|
||||
sessionExclusion,
|
||||
probeSnapshot,
|
||||
} from './coldStart.mjs';
|
||||
|
||||
const snapshot = () => ({
|
||||
layers: {
|
||||
radio: {
|
||||
enabled: true,
|
||||
objects: [],
|
||||
selected: null,
|
||||
session: { selected: null, audioState: 'idle' },
|
||||
sessionState: ['selected', 'audioState'],
|
||||
},
|
||||
},
|
||||
ledger: [
|
||||
{
|
||||
family: 'movers',
|
||||
id: 'radio-selected',
|
||||
shown: true,
|
||||
count: 1,
|
||||
kind: 'point',
|
||||
},
|
||||
],
|
||||
movers: [{ id: 'radio-selected', count: 1, kind: 'point', capacity: 1 }],
|
||||
following: null,
|
||||
});
|
||||
const options = {
|
||||
direction: 'cesium -> navara',
|
||||
owners: { 'movers:radio-selected': 'radio' },
|
||||
};
|
||||
|
||||
test('groups cover every strict catalog layer once and are registered with flags', () => {
|
||||
const ids = Object.values(COLD_START_GROUPS).flatMap((group) => group.layers);
|
||||
assert.deepEqual(
|
||||
ids.sort(),
|
||||
APPLICATION_LAYER_DIRECTORY.map(({ id }) => id).sort(),
|
||||
);
|
||||
assert.ok(
|
||||
Object.values(COLD_START_GROUPS).every(({ layers }) => layers.length <= 4),
|
||||
);
|
||||
const runner = readFileSync(
|
||||
new URL('../qa-all-layer-switches.mjs', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const registered = JSON.parse(
|
||||
JSON.stringify(
|
||||
vm.runInNewContext(runner.match(/const runs = (\[[\s\S]*?\n\]);/)[1], {
|
||||
soakRuns: [],
|
||||
longRun: '',
|
||||
}),
|
||||
),
|
||||
);
|
||||
for (const [group, { layers }] of Object.entries(COLD_START_GROUPS)) {
|
||||
assert.deepEqual(
|
||||
registered.find(
|
||||
([label]) => label === `switch-equals-cold-start--${group}`,
|
||||
),
|
||||
[
|
||||
`switch-equals-cold-start--${group}`,
|
||||
'switch-equals-cold-start',
|
||||
layers,
|
||||
[`--group=${group}`],
|
||||
],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('cold URL preserves the app-written hash, path and other query settings', () => {
|
||||
const from =
|
||||
'http://127.0.0.1:4309/share/ABC?engine=cesium&lang=de#v=2&l=f&lo=f.t.71be26';
|
||||
const result = new URL(coldStartUrl(from, 'navara'));
|
||||
assert.equal(result.pathname, '/share/ABC');
|
||||
assert.equal(result.hash, new URL(from).hash);
|
||||
assert.equal(result.searchParams.get('engine'), 'navara');
|
||||
assert.equal(result.searchParams.get('lang'), 'de');
|
||||
});
|
||||
|
||||
test('probe comparison tolerates camera quantization but rejects lost objects and count changes', () => {
|
||||
const a = {
|
||||
objects: [
|
||||
{
|
||||
id: 'a',
|
||||
position: { x: 6378000, y: 0, z: 0 },
|
||||
screen: { x: 500, y: 400 },
|
||||
},
|
||||
],
|
||||
};
|
||||
const b = structuredClone(a);
|
||||
b.objects[0].position.x += 0.5;
|
||||
b.objects[0].screen.x += 1.5;
|
||||
assert.deepEqual(compareProbe(a, b), []);
|
||||
b.objects[0].position.x += 1;
|
||||
assert.equal(compareProbe(a, b)[0].field, 'objects["a"].position.x');
|
||||
assert.ok(
|
||||
compareProbe(a, { objects: [] }).some(
|
||||
({ field }) => field === 'objects["a"]',
|
||||
),
|
||||
);
|
||||
assert.equal(compareProbe({ count: 1 }, { count: 1.0000001 }).length, 1);
|
||||
assert.equal(compareProbe({ selected: null }, {}).length, 1);
|
||||
assert.equal(
|
||||
compareProbe({ position: { x: NaN } }, { position: { x: 0 } }).length,
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test('probe arrays compare identities without hiding duplicate ids or reordered paths', () => {
|
||||
assert.deepEqual(
|
||||
compareProbe([{ id: 'b' }, { id: 'a' }], [{ id: 'a' }, { id: 'b' }]),
|
||||
[],
|
||||
);
|
||||
assert.ok(
|
||||
compareProbe([{ id: 'a' }, { id: 'a' }], [{ id: 'a' }, { id: 'a' }]).some(
|
||||
(row) => row.error === 'duplicate ids',
|
||||
),
|
||||
);
|
||||
assert.equal(compareProbe([1, 2], [2, 1]).length, 2);
|
||||
assert.equal(
|
||||
compareProbe(
|
||||
{ selected: { id: 'a' }, objects: [] },
|
||||
{ selected: null, objects: [1] },
|
||||
{ skip: ['selected'] },
|
||||
).length,
|
||||
2,
|
||||
);
|
||||
});
|
||||
|
||||
test('comparison returns every ledger family and field difference, with attribution', () => {
|
||||
const a = snapshot(),
|
||||
b = snapshot();
|
||||
b.ledger[0].count = 2;
|
||||
b.ledger[0].shown = false;
|
||||
b.ledger.push({ family: 'lines', id: 'extra', shown: true });
|
||||
b.layers.radio.objects = [{ id: 'new' }];
|
||||
b.following = { label: 'lost', native: null };
|
||||
const result = compareColdStart(a, b, { ...options, sessionState: false });
|
||||
assert.ok(
|
||||
result.differences.some(
|
||||
(row) => row.field === 'count' && row.layer === 'radio',
|
||||
),
|
||||
);
|
||||
assert.ok(result.differences.some((row) => row.field === 'shown'));
|
||||
assert.ok(
|
||||
result.differences.some(
|
||||
(row) => row.family === 'lines' && row.id === 'extra',
|
||||
),
|
||||
);
|
||||
assert.ok(result.differences.some((row) => row.id === 'following'));
|
||||
assert.ok(
|
||||
result.differences.some(
|
||||
(row) => row.field === 'objects["new"]' && row.id === 'new',
|
||||
),
|
||||
);
|
||||
assert.equal(result.differences.length, 6);
|
||||
});
|
||||
|
||||
test('declared session fields do not hide other probe fields or other layers', () => {
|
||||
const a = snapshot(),
|
||||
b = snapshot();
|
||||
a.layers.radio.session.selected = { id: 'station' };
|
||||
a.layers.radio.session.audioState = 'playing';
|
||||
b.movers[0].count = b.ledger[0].count = 0;
|
||||
b.layers.radio.enabled = false;
|
||||
const result = compareColdStart(a, b, options);
|
||||
assert.equal(result.skipped.length, 4);
|
||||
assert.deepEqual(
|
||||
result.differences.map((row) => row.field),
|
||||
['enabled'],
|
||||
);
|
||||
b.layers.radio.sessionState.push('objects');
|
||||
assert.ok(
|
||||
compareColdStart(a, b, options).differences.some((row) =>
|
||||
row.field.startsWith('sessionState'),
|
||||
),
|
||||
);
|
||||
assert.equal(
|
||||
sessionExclusion(
|
||||
{ layer: 'wind', family: 'movers', id: 'radio-selected', field: 'count' },
|
||||
a,
|
||||
),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
test('a lost session field still fails the source-to-switch retention check', () => {
|
||||
const a = snapshot(),
|
||||
b = snapshot();
|
||||
a.layers.radio.session.selected = { id: 'station' };
|
||||
b.movers[0].count = b.ledger[0].count = 0;
|
||||
const retention = compareColdStart(a, b, {
|
||||
...options,
|
||||
sessionState: false,
|
||||
}).differences.filter((row) => sessionExclusion(row, a));
|
||||
assert.equal(retention.length, 3);
|
||||
});
|
||||
|
||||
test('allowlisted absences keep their reason and never hide changed visibility or counts', () => {
|
||||
const a = snapshot(),
|
||||
b = snapshot();
|
||||
a.ledger.push({ family: 'lines', id: 'overlay', shown: true });
|
||||
b.ledger[0].shown = false;
|
||||
const result = compareColdStart(a, b, {
|
||||
...options,
|
||||
sessionState: false,
|
||||
allow: [{ family: 'lines', id: 'overlay', reason: 'Engine overlay' }],
|
||||
});
|
||||
assert.equal(result.ledger.allowed[0].reason, 'Engine overlay');
|
||||
assert.deepEqual(
|
||||
result.differences.map((row) => row.field),
|
||||
['shown'],
|
||||
);
|
||||
});
|
||||
|
||||
test('the production probe captures all requested fields and releases its frame listeners', async () => {
|
||||
const engine = createFakeEngine();
|
||||
const position = { x: 6378137, y: 0, z: 0 };
|
||||
const dataManager = {
|
||||
layers: new Map([
|
||||
[
|
||||
'fixture',
|
||||
{
|
||||
enabled: true,
|
||||
module: {
|
||||
getAllPositions: () => [{ id: 'one', position }],
|
||||
getSelectedInfo: () => ({ id: 'one' }),
|
||||
},
|
||||
},
|
||||
],
|
||||
]),
|
||||
};
|
||||
const context = vm.createContext({
|
||||
window: { __godsEyeView: { engine, dataManager } },
|
||||
});
|
||||
const page = {
|
||||
evaluate: async (code) =>
|
||||
vm.runInContext(typeof code === 'string' ? code : `(${code})()`, context),
|
||||
};
|
||||
const listeners = engine.frames.postRender.numberOfListeners;
|
||||
const snapshot = await probeSnapshot(page, ['fixture']);
|
||||
assert.equal(snapshot.layers.fixture.objects[0].id, 'one');
|
||||
assert.equal(snapshot.layers.fixture.selected.id, 'one');
|
||||
assert.equal(snapshot.following, null);
|
||||
assert.equal(snapshot.movers.length, 0);
|
||||
assert.equal(snapshot.ledger.length, 0);
|
||||
assert.equal(engine.frames.postRender.numberOfListeners, listeners);
|
||||
engine.destroy();
|
||||
});
|
||||
Reference in new issue
Block a user