Files
gods-eye-view/src/data/militaryAwarenessEngine.test.mjs
T
Sameh Khamis d14205160a Format every metre/kilometre distance through one shared helper
Directions, the Contacts marker and panel, placed annotation labels, the
drawn-line measure, the Cockpit and formatRouteDistance each chose the unit
and precision on their own, and most chose before rounding: #1450, #1433 and
formatDistanceText or kilometreDigits from src/data/distanceFormat.js, which
cut after rounding. Each keeps its own metre step and rendering.

That also fixes the 10 km cut the Contacts marker and panel, the drawn-line
measure and the Cockpit still had (9,960 m read 10.0 km). The helper returns
kilometres already rounded, so the plain marker and the localized panel no
longer disagree on half-tenths such as 1,450 m.

A negative annotation distance now leaves the label without a distance, and
a drawn line with a coordinate that is not a number shows no measure, rather
than printing 0 m or NaN km. The HUD altitude tag and the spoken view height
keep their own unit choice.
2026-11-01 22:40:28 -08:00

126 lines
5.5 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { setLocale, translateKnown } from '../i18n/index.js';
import {
AWARENESS_RELATIONSHIP,
findByDoublingRadius,
getAwarenessNavigationTargets,
formatAwarenessDistance,
formatAwarenessLabel,
summarizeAwarenessCohort,
} from './militaryAwarenessEngine.js';
test('awareness formatters preserve valid zeroes and replace missing or invalid values', () => {
assert.equal(formatAwarenessDistance(0), '0 m');
assert.equal(formatAwarenessDistance(950), '950 m');
assert.equal(formatAwarenessDistance(Number.NaN), '—');
assert.equal(formatAwarenessDistance(-1), '—');
assert.equal(formatAwarenessLabel(' UAL649 '), 'UAL649');
assert.equal(formatAwarenessLabel(0), '0');
assert.equal(formatAwarenessLabel({ callsign: ' UAL649 ', id: 'fallback' }), 'UAL649');
assert.equal(formatAwarenessLabel({ name: '', label: 'Mapped site' }), 'Mapped site');
assert.equal(formatAwarenessLabel({ id: 0 }), '0');
assert.equal(formatAwarenessLabel(''), '—');
assert.equal(formatAwarenessLabel({}), '—');
assert.equal(formatAwarenessLabel(undefined), '—');
});
test('a contact marker moves a distance that rounds to 1,000 m into kilometres', () => {
assert.equal(formatAwarenessDistance(999.4), '999 m');
assert.equal(formatAwarenessDistance(999.6), '1.0 km');
assert.equal(formatAwarenessDistance(4600), '4.6 km');
assert.equal(formatAwarenessDistance(9949), '9.9 km');
assert.equal(formatAwarenessDistance(9950), '10 km');
assert.equal(formatAwarenessDistance(9960), '10 km');
});
test('awareness cohorts sort nearest-first and do not turn empty data into out of range', () => {
const result = summarizeAwarenessCohort([{ id: 'far', distance: 9000 }, { id: 'near', distanceM: 1000 }]);
assert.equal(result.relationship, 'NEARBY');
assert.deepEqual(result.nearest.map((item) => item.id), ['near', 'far']);
assert.equal(summarizeAwarenessCohort([]).relationship, 'UNKNOWN');
});
test('awareness cohorts reject negative and non-finite distances as unavailable data', () => {
const result = summarizeAwarenessCohort([
{ id: 'negative', distanceM: -1 },
{ id: 'nan', distanceM: Number.NaN },
{ id: 'valid-zero', distanceM: 0 },
]);
assert.equal(result.relationship, 'NEARBY');
assert.deepEqual(result.nearest.map((item) => item.id), ['valid-zero']);
});
test('unavailable and stale feeds remain unknown', () => {
assert.equal(AWARENESS_RELATIONSHIP.OUTSIDE_RANGE, 'OUTSIDE_RANGE');
assert.equal(summarizeAwarenessCohort([], { available: false }).reason, 'feed unavailable');
assert.equal(summarizeAwarenessCohort([], { stale: true }).reason, 'feed stale');
});
test('cohort reasons stay English in data and translate where the panel shows them', (t) => {
t.after(() => setLocale('en'));
setLocale('ru');
const reasons = [
summarizeAwarenessCohort([], { available: false }).reason,
summarizeAwarenessCohort([], { stale: true }).reason,
summarizeAwarenessCohort([{ id: 'near', distanceM: 10 }]).reason,
summarizeAwarenessCohort([]).reason,
];
assert.deepEqual(reasons, [
'feed unavailable',
'feed stale',
'observed or mapped nearby context',
'no observed or mapped objects in current feeds',
]);
// The awareness panel shows a reason through translateKnown().
for (const reason of reasons) assert.notEqual(translateKnown(reason), reason);
});
test('navigation prioritizes nearby targets from the selected cohort', () => {
const cohorts = [
{ id: 'flights', summary: { nearest: [{ icao24: 'a1', distanceM: 1000 }, { icao24: 'a2', distanceM: 2000 }] } },
{ id: 'military', summary: { nearest: [{ icao24: 'm1', distanceM: 500 }] } },
];
const targets = getAwarenessNavigationTargets(cohorts, { layerId: 'flights', id: 'subject' }, ['flights:a1']);
// Cohort affinity still wins among UNVISITED targets: a2 leads at 2000 m even
// though military m1 sits at 500 m.
//
// Visited state now outranks that affinity, which is the corrected half of
// this ordering. Ranking the subject's own layer first unconditionally put
// the already-visited a1 ahead of the unvisited m1, so NEXT cycled inside the
// subject's layer and never reached military, vessels, or installations.
assert.deepEqual(targets.map((target) => `${target.layerId}:${target.id}`), ['flights:a2', 'military:m1', 'flights:a1']);
});
test('awareness cycling excludes satellites, which retain independent tracking', () => {
const cohorts = [
{ id: 'satellites', summary: { nearest: [{ id: 'iss', distanceM: 100 }] } },
{ id: 'ais-live-vessels', summary: { nearest: [{ mmsi: 'v1', distanceM: 1000 }] } },
];
const targets = getAwarenessNavigationTargets(cohorts);
assert.deepEqual(targets.map((target) => `${target.layerId}:${target.id}`), ['ais-live-vessels:v1']);
});
test('empty proximity searches double their radius until an aircraft is found', () => {
const searched = [];
const result = findByDoublingRadius((radiusM) => {
searched.push(radiusM);
return radiusM >= 1000000 ? { id: 'closest-flight' } : null;
});
assert.deepEqual(searched, [250000, 500000, 1000000]);
assert.deepEqual(result, {
candidate: { id: 'closest-flight' },
radiusM: 1000000,
});
});
test('expanding proximity search stops at its global bound when feeds are empty', () => {
const searched = [];
const result = findByDoublingRadius((radiusM) => {
searched.push(radiusM);
return null;
}, { initialRadiusM: 250000, maxRadiusM: 1000000 });
assert.equal(result, null);
assert.deepEqual(searched, [250000, 500000, 1000000]);
});