Files
gods-eye-view/src/data/satellitesTrackedRefresh.test.mjs
T
German KhokhlovandClaude Opus 5.5 00704ddc89 i18n: fix review findings and polish ru/es strings
Fixes the 51 P0–P2 findings from the full review of the i18n branch and
the issues found while checking every new string on screen in ru and es.

- Translate errors and statuses that still reached ru/es in English:
  feed and tile errors, provider statuses, the ALPR chip, the traffic
  mode chip, voice plan step labels, fire feed values, receiver bands,
  scene save reasons and the Cesium attribution link.
- Show preset city names in the UI language (src/ui/placeNames.js);
  data and everything sent to the model stay English.
- Share layer names between the layer panel, HUD and voice card
  (src/ui/layerNames.js).
- Keep long ru/es strings whole: voice card error lines, analyst
  titles, the voice tier status, the Nepal evidence button, the ALPR
  retry chip. English layout is unchanged.
- Ask before a language switch drops unsaved work.
- Catalog fixes: glossary terms (пуск, пресет), gender agreement and
  consistent "peticiones" in es, fewer calques in both languages.
- Fix English text that was itself wrong (e.g. "Exit cockpit to fly to
  a weather", raw tool ids in voice plan steps).

Catalogs: es/ru 2535 messages each, complete.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 23:49:52 +04:00

403 lines
13 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import * as Cesium from 'cesium';
import { twoline2satrec } from 'satellite.js';
import {
ISS_OVERLAY_SOURCE_OPTIONS,
_applyPendingSatelliteTrackingRestoreForTest,
_clearSatelliteLabelLifecycleForTest,
_pendingSatelliteTrackingRestoreForTest,
_removeSatelliteTrackingCandidateForTest,
_runSatellitePreRenderForTest,
_setSatelliteLabelLifecycleStateForTest,
_setSatelliteTrackingRefreshOutcomeForTest,
_setTrackedSatelliteRefreshStateForTest,
_trackedFrameCartesianForTest,
_trackIssForTest,
createIssOverlayEntry,
} from './satellites.js';
import satellitesLayer from './satellites.js';
import { createTrackedOverlayEntry } from './trackedReadout.js';
const L1 =
'1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927';
const L2 =
'2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537';
test('partial and dense catalogs do not make an early false missing decision', async () => {
_setSatelliteTrackingRefreshOutcomeForTest({
status: 'partial',
failedGroups: ['stations.txt'],
});
const partial = await satellitesLayer.resolveTrackingRestoreTarget(987654);
assert.equal(partial.status, 'source-unavailable');
assert.match(partial.reason, /partial CelesTrak catalog/);
let releaseDense;
const densePromise = new Promise((resolve) => {
releaseDense = resolve;
});
_setSatelliteTrackingRefreshOutcomeForTest({
status: 'accepted',
catalog: 'dense',
densePromise,
});
let settled = false;
const resolution = satellitesLayer
.resolveTrackingRestoreTarget(987654)
.then((result) => {
settled = true;
return result;
});
await Promise.resolve();
assert.equal(settled, false, 'dense absence must wait for the dense catalog');
releaseDense({ status: 'ready' });
assert.equal((await resolution).status, 'missing');
_setSatelliteTrackingRefreshOutcomeForTest({
status: 'accepted',
catalog: 'core',
});
});
test('satellite first update aborts every parallel catalog request', async () => {
const realFetch = globalThis.fetch;
const observedSignals = [];
globalThis.fetch = (_url, options = {}) =>
new Promise((_resolve, reject) => {
observedSignals.push(options.signal);
options.signal?.addEventListener(
'abort',
() => {
reject(new DOMException('aborted', 'AbortError'));
},
{ once: true },
);
});
try {
const controller = new AbortController();
const work = satellitesLayer.update({}, { signal: controller.signal });
await Promise.resolve();
assert.ok(observedSignals.length > 1);
controller.abort();
await assert.rejects(work, { name: 'AbortError' });
assert.equal(
observedSignals.every((signal) => signal?.aborted),
true,
);
} finally {
globalThis.fetch = realFetch;
}
});
test('selected satellite params survive delayed arrival and yield to newer explicit intent', () => {
const point = {
position: Cesium.Cartesian3.fromDegrees(-97.7, 30.2, 420_000),
show: true,
pixelSize: 12,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
disableDepthTestDistance: 0,
};
const viewer = {
entities: new Cesium.EntityCollection(),
trackedEntity: undefined,
scene: {
frameState: { frameNumber: 1 },
primitives: { remove() {} },
},
};
_setSatelliteLabelLifecycleStateForTest({
viewer,
satrec: twoline2satrec(L1, L2),
point,
});
try {
assert.doesNotThrow(() =>
satellitesLayer.setParams({ selectedSatTrackingId: 25544 }),
);
assert.equal(satellitesLayer.getParams().selectedSatTrackingId, 25544);
assert.equal(viewer.trackedEntity?.gevTrackedId, 'satellites:25544');
satellitesLayer.stopTracking();
_removeSatelliteTrackingCandidateForTest(25544);
satellitesLayer.setParams(
{ selectedSatTrackingId: 25544 },
{ origin: 'share-restore' },
);
assert.equal(_pendingSatelliteTrackingRestoreForTest(), 25544);
_setSatelliteLabelLifecycleStateForTest({
viewer,
satrec: twoline2satrec(L1, L2),
point,
preservePending: true,
});
assert.equal(_applyPendingSatelliteTrackingRestoreForTest(), true);
assert.equal(satellitesLayer.getParams().selectedSatTrackingId, 25544);
satellitesLayer.setParams(
{ selectedSatTrackingId: 99999 },
{ origin: 'share-restore' },
);
assert.equal(_pendingSatelliteTrackingRestoreForTest(), 99999);
assert.equal(satellitesLayer.trackById(25544, { origin: 'user' }), true);
assert.equal(
_pendingSatelliteTrackingRestoreForTest(),
null,
'new user selection cancels stale restore',
);
_applyPendingSatelliteTrackingRestoreForTest();
assert.equal(satellitesLayer.getParams().selectedSatTrackingId, 25544);
satellitesLayer.stopTracking();
_removeSatelliteTrackingCandidateForTest(25544);
satellitesLayer.setParams(
{ selectedSatTrackingId: 25544 },
{ origin: 'local-restore' },
);
assert.equal(_pendingSatelliteTrackingRestoreForTest(), 25544);
satellitesLayer.stopTracking({ origin: 'user' });
assert.equal(
_pendingSatelliteTrackingRestoreForTest(),
null,
'explicit clear cancels stale restore',
);
_setSatelliteLabelLifecycleStateForTest({
viewer,
satrec: twoline2satrec(L1, L2),
point,
preservePending: true,
});
_applyPendingSatelliteTrackingRestoreForTest();
assert.equal(satellitesLayer.getParams().selectedSatTrackingId, null);
satellitesLayer.setParams(
{ selectedSatTrackingId: 50003 },
{ origin: 'share-restore' },
);
assert.equal(_pendingSatelliteTrackingRestoreForTest(), 50003);
satellitesLayer.setParams({ showPoints: true }, { origin: 'tool' });
assert.equal(
_pendingSatelliteTrackingRestoreForTest(),
null,
'a newer explicit non-selection option cancels stale restore',
);
_applyPendingSatelliteTrackingRestoreForTest();
assert.equal(satellitesLayer.getParams().selectedSatTrackingId, null);
} finally {
_clearSatelliteLabelLifecycleForTest();
}
});
test('a tracked docked cluster consolidates its companions onto one card', () => {
// ISS and everything berthed to it are separate real tracks at one position,
// so their ambient labels stack underneath the tracked card. Owner ruling:
// consolidate them as secondary info on that card, and suppress only those
// members — never unrelated satellites that merely happen to be nearby.
const entity = { gevLabelModel: { title: 'OLD', details: ['? km'] } };
const point = { position: new Cesium.Cartesian3() };
const viewer = { camera: null, scene: { frameState: { frameNumber: 1 } } };
let nowMs = Date.UTC(2008, 8, 20, 12, 30);
// The tracked satellite moves ~7.6 km/s, so neighbours are defined RELATIVE to
// wherever it is at scan time rather than pinned to a stale sample.
const near = (metres) => ({
get position() {
const tracked = _trackedFrameCartesianForTest();
return new Cesium.Cartesian3(tracked.x + metres, tracked.y, tracked.z);
},
});
_setTrackedSatelliteRefreshStateForTest({
noradId: 25544,
name: 'ISS (ZARYA)',
satrec: twoline2satrec(L1, L2),
entity,
point,
viewer,
now: () => nowMs,
neighbours: [
{ noradId: 55555, name: 'PROGRESS-MS 34', point: near(120) }, // berthed
{ noradId: 55556, name: 'SOYUZ-MS 12', point: near(-90) }, // berthed
{ noradId: 99999, name: 'UNRELATED SAT', point: near(50_000) }, // 50 km away
],
});
_runSatellitePreRenderForTest();
// The cluster scan is throttled, so advance past its interval before the frame
// that must observe the cluster.
nowMs += 2000;
viewer.scene.frameState.frameNumber = 2;
_runSatellitePreRenderForTest();
const details = entity.gevLabelModel.details;
// Class leads the detail block; the altitude line follows it, and the
// consolidated companions stay last.
assert.equal(
details[0],
'STATION · ISS',
'the class line names what this is',
);
assert.match(details[1], /NORAD 25544$/, 'the altitude line is unchanged');
assert.equal(
details[2],
'DOCKED · PROGRESS-MS 34 · +1',
'companions are consolidated, named, and counted on the tracked card',
);
// Scoping is encoded in that string: THREE neighbours were seeded but only two
// are counted (the named one plus "+1"). The satellite 50 km away is not part
// of the cluster and keeps its own identity and label — the owner's explicit
// constraint that unrelated nearby satellites are never suppressed.
assert.doesNotMatch(details[2], /UNRELATED/);
assert.doesNotMatch(details[2], /\+2/);
});
test('satellite pre-render refreshes the tracked altitude on each propagated frame', () => {
const entity = { gevLabelModel: { title: 'OLD', details: ['? km'] } };
const point = { position: new Cesium.Cartesian3() };
const viewer = {
camera: null,
scene: { frameState: { frameNumber: 1 } },
};
const epochs = [Date.UTC(2008, 8, 20, 12, 30), Date.UTC(2008, 8, 20, 13, 0)];
let epochIndex = 0;
_setTrackedSatelliteRefreshStateForTest({
noradId: 25544,
name: 'ISS (ZARYA)',
satrec: twoline2satrec(L1, L2),
entity,
point,
viewer,
now: () => epochs[epochIndex],
});
_runSatellitePreRenderForTest();
assert.equal(entity.gevLabelModel.title, 'ISS (ZARYA)');
assert.equal(entity.gevLabelModel.details[0], 'STATION · ISS');
// formatQuantity keeps the number and its unit together with a no-break space.
assert.equal(entity.gevLabelModel.details[1], '353\u00a0km · NORAD 25544');
epochIndex = 1;
viewer.scene.frameState.frameNumber = 2;
_runSatellitePreRenderForTest();
assert.equal(entity.gevLabelModel.details[1], '366\u00a0km · NORAD 25544');
assert.equal(
entity.gevLabelModel.details[0],
'STATION · ISS',
'the class line survives an altitude-only republish',
);
});
test('ISS ambient and tracked lifecycle uses cached host entries with no native labels or duplicate text', () => {
const calls = [];
const overlayHost = {
setEntries: (...args) => calls.push(['entries', ...args]),
setVisible: (...args) => calls.push(['visible', ...args]),
clearSource: (...args) => calls.push(['clear', ...args]),
};
const point = {
position: Cesium.Cartesian3.fromDegrees(-97.7, 30.2, 420_000),
show: true,
pixelSize: 12,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
disableDepthTestDistance: 0,
};
const viewer = {
entities: new Cesium.EntityCollection(),
trackedEntity: undefined,
scene: {
frameState: { frameNumber: 1 },
primitives: { remove() {} },
},
};
let cleaned = false;
_setSatelliteLabelLifecycleStateForTest({
viewer,
satrec: twoline2satrec(L1, L2),
point,
overlayHost,
});
try {
const ambientPublication = calls.find(([type]) => type === 'entries');
assert.ok(ambientPublication);
assert.equal(ambientPublication[1], 'satellites-iss');
assert.deepEqual(ambientPublication[3], ISS_OVERLAY_SOURCE_OPTIONS);
const ambient = ambientPublication[2][0];
assert.equal(ambient.title, 'ISS');
assert.equal(
ambient.position(),
point.position,
'ISS getter reads the existing point cache',
);
assert.equal(
point.label,
undefined,
'ISS point primitive carries no label graphic',
);
const trackedEntity = _trackIssForTest();
assert.ok(trackedEntity);
assert.equal(
viewer.entities.values.length,
1,
'runtime guard requires a real tracked entity',
);
assert.equal(trackedEntity.label, undefined);
assert.ok(
trackedEntity.point,
'tracked dot remains on the entity for camera framing',
);
const trackedEntry = createTrackedOverlayEntry(trackedEntity);
assert.ok(trackedEntry);
assert.equal(trackedEntry.id, 'satellites:25544');
assert.equal(trackedEntry.protected, true);
assert.equal(trackedEntry.paintLane, 'tracked');
assert.equal(
calls.filter(
([type, sourceId]) =>
type === 'entries' && sourceId === 'satellites-iss',
).length,
2,
'tracking may refresh before ownership changes but must not publish a second ISS entry',
);
assert.deepEqual(
calls.slice(-2),
[
['clear', 'satellites-iss'],
['visible', 'satellites-iss', false],
],
'tracked ISS suppresses ambient text so the tracked card is the only text surface',
);
_clearSatelliteLabelLifecycleForTest();
cleaned = true;
assert.equal(viewer.entities.values.length, 0);
assert.equal(
calls.filter(
([type, sourceId]) =>
type === 'entries' && sourceId === 'satellites-iss',
).length,
3,
'untracking republishes exactly one persistent ISS ambient entry before teardown',
);
} finally {
if (!cleaned) _clearSatelliteLabelLifecycleForTest();
}
});
test('ISS overlay entry retains the native distance scale and source text', () => {
const position = new Cesium.Cartesian3(1, 2, 3);
const entry = createIssOverlayEntry(() => position);
assert.equal(entry.title, 'ISS');
assert.equal(entry.position(), position);
assert.deepEqual(entry.distanceScale, {
near: 1_000_000,
nearValue: 1,
far: 30_000_000,
farValue: 0.4,
});
assert.equal(entry.edgeFade, 'keyhole');
assert.equal(entry.horizonCull, true);
});