Files
gods-eye-view/scripts/qa-contact-switch.mjs
T
German Khokhlov 7abf74518f Cut duplicated and dead code across the engines, the layers and the QA scripts
- src/geo drops the ported debug argument checks and the tests that
  bitExact already covers; package boundaries accept a directory entry,
  so src/geo/ is listed once per group.
- Cesium no longer carries movers and lines nobody draws with: they are
  optional engine capabilities. The fake engine and the Cesium camera use
  the shared cameraMath; unused contract tables, clock.ticked, the
  'finest' surface detail and Navara click statistics are gone.
- ALPR keeps one overlay core and one presentation model for both
  engines, bikeshare one station bookkeeping, the Navara ground effects
  one definition helper; image atlases, view centres, mover banks and
  colours are shared.
- The legacy switch route, the clock offset and rate controls and other
  unused exports are removed.
- The acceptance runs the Navara draw, geo import and scope checks;
  scenarios use the harness switch and page helpers; the main chunk
  check keeps the size budget only and runs once in CI.
2026-11-05 13:32:39 +04:00

268 lines
9.3 KiB
JavaScript

import assert from 'node:assert/strict';
import { fromDegrees } from '../src/geo/cartesian3.js';
import {
createStrict,
localPreview,
openPage,
waitForEngine,
} from './qa-strict-harness.mjs';
const base = localPreview(process.argv[2]);
base.search = '?engine=navara&welcome=0&look=cinematic';
base.hash =
'v=2&lat=43.6500&lon=-79.3800&alt=46000&heading=0&pitch=-90&roll=0&map=sentinel2&l=4';
const strict = createStrict({
origin: base.origin,
layers: ['military-awareness', 'military-installations'],
});
await strict.run({}, async (b) => {
const { page: p, errors } = await openPage(b);
await p.evaluateOnNewDocument(() => {
const fetch = window.fetch.bind(window);
window.fetch = (input, options) => {
const { pathname } = new URL(input.url ?? input, document.baseURI);
if (pathname === '/api/vessels')
return Promise.resolve(
Response.json({
status: 'live',
rows: ['123456789', '223456789'].map((mmsi, i) => ({
mmsi,
name: 'Toronto ' + i,
lat: 43.65,
lon: -79.38 + i * 0.05,
last_position_epoch: Date.now() / 1000,
})),
}),
);
// Contacts reads air quality near the subject; no station here.
if (
new URL(input.url ?? input, document.baseURI).hostname ===
'api.weather.gc.ca'
)
return Promise.resolve(
Response.json({ type: 'FeatureCollection', features: [] }),
);
if (pathname === '/api/vessels/track')
return Promise.resolve(Response.json({ samples: [] }));
if (pathname === '/api/flights')
return Promise.resolve(
Response.json({ time: Date.now() / 1000, states: [] }),
);
if (pathname === '/api/military')
return Promise.resolve(Response.json({ now: Date.now(), ac: [] }));
// One mapped station next to the Contacts subject, with a footprint.
if (pathname === '/api/military-installations') {
window.qaInstallationReads = (window.qaInstallationReads ?? 0) + 1;
const ring = [
[43.665, -79.345],
[43.665, -79.315],
[43.68, -79.315],
[43.68, -79.345],
[43.665, -79.345],
];
return Promise.resolve(
Response.json({
status: 'ok',
elements: [
{
type: 'way',
id: 424242,
tags: { military: 'naval_base', name: 'QA Harbour Station' },
center: { lat: 43.6725, lon: -79.33 },
geometry: ring.map(([lat, lon]) => ({ lat, lon })),
},
],
}),
);
}
return fetch(input, options);
};
window.qaSelected = [];
for (const type of ['gev:entity-selected', 'gev:entity-selection-cleared'])
window.addEventListener(type, ({ detail }) =>
window.qaSelected.push({
type,
id: detail?.id,
layerId: detail?.layerId,
}),
);
});
await p.goto(base.href);
await waitForEngine(p, 'navara');
await p.evaluate(async () => {
const { dataManager, styleManager, tileset } = window.__godsEyeView;
if (tileset || window.__GOOGLE_MAPS_API_KEY__)
throw Error('Require keyless preview');
await dataManager.setEnabled('ais-live-vessels', true, { origin: 'user' });
window.qaBefore = [...dataManager.getEnabledLayerIds()].sort();
const outcome = await styleManager.setContextMode('contacts');
if (!outcome.ok) throw Error(JSON.stringify(outcome));
window.qaContext = dataManager.layers.get('military-awareness').module;
window.qaSites = dataManager.layers.get('military-installations').module;
const vessels = dataManager.layers.get('ais-live-vessels').module;
if (!vessels.selectById('123456789')) throw Error('Missing first vessel');
if (!window.qaContext.navigateNext({ targetLayer: 'ais-live-vessels' }))
throw Error('Cannot navigate to the second vessel');
});
const sample = () =>
p.evaluate(() => {
const { dataManager, styleManager, engine } = window.__godsEyeView;
return {
context:
window.qaContext ===
dataManager.layers.get('military-awareness').module,
sites:
window.qaSites ===
dataManager.layers.get('military-installations').module,
mode: styleManager.getContextModeState().mode,
subject: window.qaContext.getContextSnapshot()?.subject?.id,
history:
window.qaContext._getAwarenessNavigationStateForTest().historyKeys,
enabled: [...dataManager.getEnabledLayerIds()].sort(),
ring:
engine.kind === 'navara'
? engine.lines
.list()
.filter((line) => line.id === 'gev-awareness-rim').length
: null,
};
});
// The station zone: the fixture's installation is the one mapped site
// within the Contacts radius of the subject, and the engine draws it where
// a pointer can reach it (the engine's own picker returns it at an
// uncovered canvas point at its marker or inside its footprint).
const inside = [fromDegrees(-79.33, 43.6725)];
for (let i = 1; i < 6; i++)
for (let k = 1; k < 6; k++)
inside.push(fromDegrees(-79.345 + 0.005 * k, 43.665 + 0.0025 * i));
const zone = async () => {
await p.waitForFunction(
() =>
!window.qaSites.getStats().loading &&
window.qaSites
.getAnalystRecords()
.some((r) => r.name === 'QA Harbour Station'),
{ timeout: 30000 },
);
const drawn = await p
.waitForFunction(
(points) => {
const { engine } = window.__godsEyeView;
const canvas = engine.host.canvas;
const box = canvas.getBoundingClientRect();
for (const world of points) {
const centre = engine.projector.worldToWindow(world, {});
if (!centre) continue;
for (const [dx, dy] of [
[0, 0],
[0, -8],
[8, 0],
[-8, 0],
[0, 8],
]) {
const at = { x: centre.x + dx, y: centre.y + dy };
if (
!canvas.contains(
document.elementFromPoint(box.left + at.x, box.top + at.y),
)
)
continue;
const id = engine.picker.pick(at)?.id;
if (String(id?.id ?? id) === 'osm:way:424242')
return [Math.round(at.x), Math.round(at.y)];
}
}
return false;
},
{ timeout: 20000 },
inside,
)
.then((handle) => handle.jsonValue())
.catch(() => null);
return p.evaluate(
(drawn) => ({
drawn,
records: window.qaSites.getAnalystRecords().map((r) => r.name),
coverage: window.qaSites.getStats().coverage,
count: window.qaSites.getStats().count,
reads: window.qaInstallationReads,
subject: window.qaContext.getContextSnapshot()?.subject?.id,
}),
drawn,
);
};
const checkZone = async () => {
const station = await zone();
await strict.check(
p,
'military-installations',
'the station zone holds the fixture station, drawn and pickable',
station.count === 1 &&
station.records.length === 1 &&
station.records[0] === 'QA Harbour Station' &&
station.coverage?.kind === 'subject' &&
Array.isArray(station.drawn),
station,
);
return station;
};
const firstZone = await checkZone();
const baseline = await sample();
console.log('baseline', JSON.stringify(baseline));
assert.equal(baseline.mode, 'flights');
assert.equal(baseline.subject, '223456789');
assert.ok(baseline.history.includes('ais-live-vessels:123456789'));
assert.ok(baseline.ring >= 1);
await strict.passed(
p,
'military-awareness',
'Contacts subject, navigation history and awareness rim',
baseline,
);
for (const kind of ['cesium', 'navara']) {
await strict.switchTo(p, kind);
const current = await sample();
console.log('handoff', kind, JSON.stringify(current));
assert.deepEqual(current, {
...baseline,
ring: kind === 'navara' ? baseline.ring : null,
});
await strict.passed(
p,
'military-awareness',
'same awareness model, Contacts mode, subject, history and enabled layers',
);
await strict.passed(
p,
'military-installations',
'same installations model and enabled through the switch',
);
const station = await checkZone();
await strict.check(
p,
'military-installations',
'the switch reads no installation snapshot',
station.reads === firstZone.reads,
{ before: firstZone.reads, after: station.reads },
);
}
await checkZone();
const restored = await p.evaluate(async () => {
const { dataManager, styleManager } = window.__godsEyeView;
const result = await styleManager.setContextMode('off');
return (
result.ok &&
JSON.stringify([...dataManager.getEnabledLayerIds()].sort()) ===
JSON.stringify(window.qaBefore)
);
});
assert.ok(
restored,
'Restore layers enabled by the user before entering Contacts',
);
assert.deepEqual(errors, []);
console.log(
'PASS Contacts history, dependency ownership and exit restore across both globes',
);
});