Files
gods-eye-view/src/data/contextStore.test.mjs
T

202 lines
7.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
clearTrackedSubjectContext,
getContextStore,
getSelectedEntityContext,
refreshTrackedSubjectContext,
registerEntityContext,
selectEntityContext,
selectTrackedSubjectContext,
} from './contextStore.js';
/**
* The tracking layers (aircraft) publish selection on their own awareness
* lane, which the readout card and Contacts panel consume. These tests pin the
* OTHER half of that contract: the same click must also reach the shared
* selection slot, because voice's `get_entity_context {scope:'selected'}` and
* the Cockpit's selected-target lookup read only this slot. When they did not,
* a plainly selected plane answered "there isn't a plane currently selected".
*/
function withWindow(run) {
const realWindow = globalThis.window;
const host = new EventTarget();
globalThis.window = host;
try {
return run(host);
} finally {
globalThis.window = realWindow;
}
}
const flightSubject = (id, extra = {}) => ({
id,
layerId: 'flights',
layerName: 'Live Flights',
source: 'OpenSky Network',
label: id.toUpperCase(),
latitude: 30.2672,
longitude: -97.7431,
...extra,
});
test('a tracking layer subject becomes the selected entity context', () => {
withWindow(() => {
const record = selectTrackedSubjectContext(flightSubject('aaa001'));
assert.equal(record?.id, 'aaa001');
const selected = getSelectedEntityContext();
assert.equal(selected?.id, 'aaa001');
assert.equal(selected?.layerId, 'flights');
assert.equal(selected?.latitude, 30.2672);
});
});
test('selecting a tracking subject stays off the overlay-click event lane', () => {
withWindow((host) => {
const seen = [];
host.addEventListener('gev:entity-selected', () => seen.push('selected'));
host.addEventListener('gev:entity-selection-cleared', () => seen.push('cleared'));
selectTrackedSubjectContext(flightSubject('aaa001'));
clearTrackedSubjectContext('flights');
assert.deepEqual(
seen,
[],
'aircraft already publish gev:awareness-subject-* — a second lane would make two surfaces fight over one subject',
);
});
});
test('switching contacts moves the subject and leaves no stale record behind', () => {
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
selectTrackedSubjectContext(flightSubject('aaa002', { latitude: 31.1, longitude: -96.9 }));
const store = getContextStore();
const flightIds = [...store.entities.values()]
.filter((record) => record.layerId === 'flights')
.map((record) => record.id);
assert.deepEqual(flightIds, ['aaa002']);
assert.equal(getSelectedEntityContext()?.id, 'aaa002');
});
});
test('a sibling tracking layer keeps its own subject slot', () => {
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
selectTrackedSubjectContext({
id: 'bbb101', layerId: 'military', label: 'MIL101', latitude: 30.27, longitude: -97.75,
});
const store = getContextStore();
assert.equal(getSelectedEntityContext()?.id, 'bbb101');
assert.ok(store.entities.has('aaa001'), 'the flights record survives; only the SELECTION moved');
});
});
test('the per-poll refresh updates values without stealing the selection', () => {
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
const other = registerEntityContext({ __gevContextId: 'dc-7' }, {
id: 'dc-7', layerId: 'local-datacenters', label: 'Datacenter 7',
});
selectEntityContext(other.entity);
assert.equal(getSelectedEntityContext()?.id, 'dc-7');
refreshTrackedSubjectContext(flightSubject('aaa001', { latitude: 31.5 }));
assert.equal(
getSelectedEntityContext()?.id,
'dc-7',
'a background position refresh must not resurrect a subject the operator replaced',
);
assert.equal(getContextStore().entities.get('aaa001').latitude, 31.5);
});
});
test('the per-poll refresh will not register a subject that was never selected', () => {
withWindow(() => {
assert.equal(refreshTrackedSubjectContext(flightSubject('aaa009')), null);
assert.equal(getContextStore().entities.has('aaa009'), false);
});
});
test('deselecting a contact releases the shared slot', () => {
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
clearTrackedSubjectContext('flights');
assert.equal(getSelectedEntityContext(), null);
assert.equal(getContextStore().entities.has('aaa001'), false);
});
});
test('clearing one tracking layer leaves another layer selection alone', () => {
withWindow(() => {
const other = registerEntityContext({ __gevContextId: 'dam-3' }, {
id: 'dam-3', layerId: 'local-dams', label: 'Dam 3',
});
selectEntityContext(other.entity);
clearTrackedSubjectContext('flights');
assert.equal(getSelectedEntityContext()?.id, 'dam-3');
});
});
test('the tracking-subject helpers are inert with no window to host the store', () => {
const realWindow = globalThis.window;
globalThis.window = undefined;
try {
// The aircraft layers call these inside their per-poll refresh; a throw
// here would abort the rest of the poll.
assert.equal(selectTrackedSubjectContext(flightSubject('aaa001')), null);
assert.equal(refreshTrackedSubjectContext(flightSubject('aaa001')), null);
assert.doesNotThrow(() => clearTrackedSubjectContext('flights'));
} finally {
globalThis.window = realWindow;
}
});
test('a satellite subject uses the same shared slot as aircraft', () => {
// Satellites were named in the same PRD gap: they publish only the awareness
// event, so a tracked satellite was invisible to the voice entity tools.
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
selectTrackedSubjectContext({
id: '25544',
layerId: 'satellites',
layerName: 'Satellites',
source: 'CelesTrak',
label: 'ISS (ZARYA)',
latitude: 12.5,
longitude: -45.2,
properties: { name: 'ISS (ZARYA)', noradId: '25544', altitude: '420 km' },
});
const selected = getSelectedEntityContext();
assert.equal(selected?.id, '25544');
assert.equal(selected?.layerId, 'satellites');
assert.equal(selected?.properties?.noradId, '25544');
});
});
test('switching satellites leaves no stale orbit record behind', () => {
withWindow(() => {
const sat = (id, label) => ({
id, layerId: 'satellites', label, latitude: 1, longitude: 2,
});
selectTrackedSubjectContext(sat('25544', 'ISS (ZARYA)'));
selectTrackedSubjectContext(sat('20580', 'HST'));
const ids = [...getContextStore().entities.values()]
.filter((record) => record.layerId === 'satellites')
.map((record) => record.id);
assert.deepEqual(ids, ['20580']);
assert.equal(getSelectedEntityContext()?.id, '20580');
});
});
test('deselecting a satellite releases the slot without touching aircraft', () => {
withWindow(() => {
selectTrackedSubjectContext(flightSubject('aaa001'));
selectTrackedSubjectContext({ id: '25544', layerId: 'satellites', label: 'ISS', latitude: 1, longitude: 2 });
clearTrackedSubjectContext('satellites');
assert.equal(getSelectedEntityContext(), null, 'the satellite gave the slot back');
assert.ok(getContextStore().entities.has('aaa001'), 'and the aircraft record is untouched');
});
});