mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
i18n: fix the repeat-review findings
- Status pieces translate in one place (src/ui/statusText.js): a Local
ADS-B error with a single piece now translates, and the analyst card
splits empty-layer notes into pieces (Transit, Local ADS-B).
- Translate the ALPR retry chip text, the ECCC error on the Air quality
card, "Upstream response too large" and the "entry {n}" feed label.
- The unmarked-text check also sees error/loadingLabel/statusMessage/
status assignments, `status:` properties and showToast?.( /
this._showToast( calls; the scene import preview marks its pack
statuses, data and model-facing text carry i18n-ignore with a reason.
- Tests check the real code: the HUD locality formatter moves to
src/hudLocalityFormat.js, the unsaved-keys test pins the teardown.
- One copy each: city names come from src/cityPois.js, the GNSS HTTP
template and wind "field unavailable" texts; dead fallbacks removed.
- Leftovers of the reverted vessel-label change and the reformatting of
detectionHost.test.mjs / keySetup.test.mjs are back to main.
Catalogs: es/ru 2617 messages each, complete.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a2463ca2d2
commit
3157888780
60 files changed
+1577
-1606
No files matched your search
@@ -249,6 +249,7 @@ export function createAisWatchdog(options = {}) {
|
||||
if (!env.hasKey) {
|
||||
const actions = terminateOwned('key-absent');
|
||||
status = 'missing-key';
|
||||
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
|
||||
error = 'AISSTREAM_API_KEY is not set';
|
||||
reconnectAttempt = 0;
|
||||
keyFingerprint = null;
|
||||
@@ -257,6 +258,7 @@ export function createAisWatchdog(options = {}) {
|
||||
if (env.hasTransport === false) {
|
||||
const actions = terminateOwned('no-transport');
|
||||
status = 'unsupported';
|
||||
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
|
||||
error = 'Node WebSocket transport is unavailable';
|
||||
return actions;
|
||||
}
|
||||
@@ -329,6 +331,7 @@ export function createAisWatchdog(options = {}) {
|
||||
if (!silenceWatchArmed) return [];
|
||||
const silentFor = monoNow - silenceSinceMono;
|
||||
if (silentFor >= recycleAfterMs) {
|
||||
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
|
||||
error = `AISStream delivered no data for ${Math.round(silentFor / 1000)}s`;
|
||||
const actions = terminateOwned('silent');
|
||||
scheduleRetry('transport');
|
||||
@@ -401,6 +404,7 @@ export function createAisWatchdog(options = {}) {
|
||||
function onClose(eventGeneration) {
|
||||
if (!ownsGeneration(eventGeneration)) return [];
|
||||
release();
|
||||
// i18n-ignore: server detail; the chip shows a translated reason (vessels/queries.js)
|
||||
if (!error) error = 'AISStream websocket closed';
|
||||
scheduleRetry('transport');
|
||||
return [];
|
||||
|
||||
+154
-431
@@ -27,25 +27,17 @@ import { DETECTION_THEME_MAP } from '../overlays/worldOverlayTokens.js';
|
||||
import { composeLabel, resolveTier } from './detectionDraw.js';
|
||||
|
||||
test('detection diagnostics count rendered fading rows instead of absent selected identities', () => {
|
||||
assert.equal(
|
||||
countFadingRenderEntries([
|
||||
{ selected: true },
|
||||
{ selected: false },
|
||||
{ selected: true },
|
||||
{ selected: false },
|
||||
]),
|
||||
2,
|
||||
);
|
||||
assert.equal(
|
||||
countFadingRenderEntries([{ selected: true }, { selected: true }]),
|
||||
0,
|
||||
);
|
||||
assert.equal(countFadingRenderEntries([
|
||||
{ selected: true },
|
||||
{ selected: false },
|
||||
{ selected: true },
|
||||
{ selected: false },
|
||||
]), 2);
|
||||
assert.equal(countFadingRenderEntries([{ selected: true }, { selected: true }]), 0);
|
||||
});
|
||||
|
||||
class MockEvent {
|
||||
constructor() {
|
||||
this.listeners = new Set();
|
||||
}
|
||||
constructor() { this.listeners = new Set(); }
|
||||
|
||||
addEventListener(listener) {
|
||||
this.listeners.add(listener);
|
||||
@@ -77,127 +69,62 @@ function mockContext(target, trace) {
|
||||
return {
|
||||
calls,
|
||||
font: '',
|
||||
get filter() {
|
||||
return filter;
|
||||
},
|
||||
set filter(value) {
|
||||
filter = value;
|
||||
record('filter', value);
|
||||
},
|
||||
get filter() { return filter; },
|
||||
set filter(value) { filter = value; record('filter', value); },
|
||||
fillStyle: '',
|
||||
get strokeStyle() {
|
||||
return strokeStyle;
|
||||
},
|
||||
set strokeStyle(value) {
|
||||
strokeStyle = value;
|
||||
record('strokeStyle', value);
|
||||
},
|
||||
get strokeStyle() { return strokeStyle; },
|
||||
set strokeStyle(value) { strokeStyle = value; record('strokeStyle', value); },
|
||||
globalAlpha: 1,
|
||||
get globalCompositeOperation() {
|
||||
return globalCompositeOperation;
|
||||
},
|
||||
set globalCompositeOperation(value) {
|
||||
globalCompositeOperation = value;
|
||||
record('globalCompositeOperation', value);
|
||||
},
|
||||
get globalCompositeOperation() { return globalCompositeOperation; },
|
||||
set globalCompositeOperation(value) { globalCompositeOperation = value; record('globalCompositeOperation', value); },
|
||||
lineWidth: 1,
|
||||
measureText(text) {
|
||||
return { width: String(text).length * 6 };
|
||||
},
|
||||
setTransform(...args) {
|
||||
record('setTransform', ...args);
|
||||
},
|
||||
clearRect(...args) {
|
||||
record('clearRect', ...args);
|
||||
},
|
||||
save() {
|
||||
record('save');
|
||||
},
|
||||
restore() {
|
||||
record('restore');
|
||||
},
|
||||
translate(...args) {
|
||||
record('translate', ...args);
|
||||
},
|
||||
scale(...args) {
|
||||
record('scale', ...args);
|
||||
},
|
||||
beginPath() {
|
||||
record('beginPath');
|
||||
},
|
||||
rect(...args) {
|
||||
record('rect', ...args);
|
||||
},
|
||||
clip(...args) {
|
||||
record('clip', ...args);
|
||||
},
|
||||
moveTo(...args) {
|
||||
record('moveTo', ...args);
|
||||
},
|
||||
lineTo(...args) {
|
||||
record('lineTo', ...args);
|
||||
},
|
||||
arc(...args) {
|
||||
record('arc', ...args);
|
||||
},
|
||||
arcTo(...args) {
|
||||
record('arcTo', ...args);
|
||||
},
|
||||
closePath() {
|
||||
record('closePath');
|
||||
},
|
||||
measureText(text) { return { width: String(text).length * 6 }; },
|
||||
setTransform(...args) { record('setTransform', ...args); },
|
||||
clearRect(...args) { record('clearRect', ...args); },
|
||||
save() { record('save'); },
|
||||
restore() { record('restore'); },
|
||||
translate(...args) { record('translate', ...args); },
|
||||
scale(...args) { record('scale', ...args); },
|
||||
beginPath() { record('beginPath'); },
|
||||
rect(...args) { record('rect', ...args); },
|
||||
clip(...args) { record('clip', ...args); },
|
||||
moveTo(...args) { record('moveTo', ...args); },
|
||||
lineTo(...args) { record('lineTo', ...args); },
|
||||
arc(...args) { record('arc', ...args); },
|
||||
arcTo(...args) { record('arcTo', ...args); },
|
||||
closePath() { record('closePath'); },
|
||||
// The style is recorded AT fill time, not on assignment, so a pin can prove
|
||||
// which plate token actually reached the canvas rather than which one was
|
||||
// set at some point during the frame. globalAlpha rides along for the same
|
||||
// reason: the backdrop feather is an alpha, not a colour.
|
||||
fill(path) {
|
||||
record('fill', path, this.fillStyle, this.globalAlpha);
|
||||
},
|
||||
stroke(path) {
|
||||
record('stroke', path, this.strokeStyle, this.lineWidth);
|
||||
},
|
||||
fillRect(...args) {
|
||||
record('fillRect', ...args);
|
||||
},
|
||||
fillText(...args) {
|
||||
record('fillText', ...args);
|
||||
},
|
||||
drawImage(...args) {
|
||||
record('drawImage', ...args);
|
||||
},
|
||||
fill(path) { record('fill', path, this.fillStyle, this.globalAlpha); },
|
||||
stroke(path) { record('stroke', path, this.strokeStyle, this.lineWidth); },
|
||||
fillRect(...args) { record('fillRect', ...args); },
|
||||
fillText(...args) { record('fillText', ...args); },
|
||||
drawImage(...args) { record('drawImage', ...args); },
|
||||
};
|
||||
}
|
||||
|
||||
// `search` seeds window.location.search. The detection mode banner is developer
|
||||
// telemetry gated behind ?detectDebug=1, so any test that uses the banner as its
|
||||
// "did this frame repaint" probe must opt in explicitly.
|
||||
function installEnvironment({
|
||||
width = 800,
|
||||
height = 600,
|
||||
dpr = 2,
|
||||
search = '',
|
||||
} = {}) {
|
||||
function installEnvironment({ width = 800, height = 600, dpr = 2, search = '' } = {}) {
|
||||
const byId = new Map();
|
||||
const paintTrace = [];
|
||||
const ctx = mockContext('shared', paintTrace);
|
||||
const detectionCtx = mockContext('detection', paintTrace);
|
||||
const originalPerformance = Object.getOwnPropertyDescriptor(
|
||||
globalThis,
|
||||
'performance',
|
||||
);
|
||||
const originalPerformance = Object.getOwnPropertyDescriptor(globalThis, 'performance');
|
||||
const originalDateNow = Date.now;
|
||||
const originalPath2D = globalThis.Path2D;
|
||||
let currentTime = 1_000;
|
||||
let performanceStep = 0;
|
||||
Object.defineProperty(globalThis, 'performance', {
|
||||
configurable: true,
|
||||
value: {
|
||||
now: () => {
|
||||
const value = currentTime;
|
||||
currentTime += performanceStep;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
value: { now: () => {
|
||||
const value = currentTime;
|
||||
currentTime += performanceStep;
|
||||
return value;
|
||||
} },
|
||||
});
|
||||
Date.now = () => currentTime;
|
||||
globalThis.Path2D = MockPath2D;
|
||||
@@ -240,9 +167,7 @@ function installEnvironment({
|
||||
register(this);
|
||||
}
|
||||
|
||||
getBoundingClientRect() {
|
||||
return this._rect;
|
||||
}
|
||||
getBoundingClientRect() { return this._rect; }
|
||||
getContext() {
|
||||
if (this.tagName !== 'CANVAS') return null;
|
||||
return this.id === 'world-overlay-detection-surface' ? detectionCtx : ctx;
|
||||
@@ -255,17 +180,13 @@ function installEnvironment({
|
||||
|
||||
remove() {
|
||||
if (this.parentElement) {
|
||||
this.parentElement.children = this.parentElement.children.filter(
|
||||
(child) => child !== this,
|
||||
);
|
||||
this.parentElement.children = this.parentElement.children.filter((child) => child !== this);
|
||||
}
|
||||
unregister(this);
|
||||
this.parentElement = null;
|
||||
}
|
||||
|
||||
get nextSibling() {
|
||||
return null;
|
||||
}
|
||||
get nextSibling() { return null; }
|
||||
}
|
||||
|
||||
function register(element) {
|
||||
@@ -279,26 +200,14 @@ function installEnvironment({
|
||||
}
|
||||
|
||||
const body = new MockElement('body');
|
||||
body.classList = {
|
||||
contains() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
body.classList = { contains() { return false; } };
|
||||
const document = {
|
||||
body,
|
||||
createElement(tagName) {
|
||||
return new MockElement(tagName);
|
||||
},
|
||||
getElementById(id) {
|
||||
return byId.get(id) || null;
|
||||
},
|
||||
querySelector(selector) {
|
||||
return byId.get(selector.slice(1)) || null;
|
||||
},
|
||||
createElement(tagName) { return new MockElement(tagName); },
|
||||
getElementById(id) { return byId.get(id) || null; },
|
||||
querySelector(selector) { return byId.get(selector.slice(1)) || null; },
|
||||
querySelectorAll(selector) {
|
||||
const element = selector.startsWith('#')
|
||||
? byId.get(selector.slice(1))
|
||||
: null;
|
||||
const element = selector.startsWith('#') ? byId.get(selector.slice(1)) : null;
|
||||
return element ? [element] : [];
|
||||
},
|
||||
};
|
||||
@@ -307,18 +216,10 @@ function installEnvironment({
|
||||
location: { search },
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
getComputedStyle() {
|
||||
return { display: 'block', visibility: 'visible', opacity: '1' };
|
||||
},
|
||||
};
|
||||
globalThis.ResizeObserver = class {
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
};
|
||||
globalThis.MutationObserver = class {
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
getComputedStyle() { return { display: 'block', visibility: 'visible', opacity: '1' }; },
|
||||
};
|
||||
globalThis.ResizeObserver = class { observe() {} disconnect() {} };
|
||||
globalThis.MutationObserver = class { observe() {} disconnect() {} };
|
||||
globalThis.document = document;
|
||||
globalThis.window = window;
|
||||
|
||||
@@ -340,9 +241,7 @@ function installEnvironment({
|
||||
positionWC: new Cesium.Cartesian3(0, 0, 10_000_000),
|
||||
positionCartographic: { height: 1_000_000 },
|
||||
viewMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
|
||||
frustum: {
|
||||
projectionMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
|
||||
},
|
||||
frustum: { projectionMatrix: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY) },
|
||||
moveEnd: new MockEvent(),
|
||||
},
|
||||
scene: { postRender, requestRender() {} },
|
||||
@@ -355,12 +254,8 @@ function installEnvironment({
|
||||
ctx,
|
||||
detectionCtx,
|
||||
paintTrace,
|
||||
advance(ms) {
|
||||
currentTime += ms;
|
||||
},
|
||||
setPerformanceStep(ms) {
|
||||
performanceStep = ms;
|
||||
},
|
||||
advance(ms) { currentTime += ms; },
|
||||
setPerformanceStep(ms) { performanceStep = ms; },
|
||||
cleanup() {
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
@@ -371,8 +266,7 @@ function installEnvironment({
|
||||
delete globalThis.window;
|
||||
delete globalThis.ResizeObserver;
|
||||
delete globalThis.MutationObserver;
|
||||
if (originalPerformance)
|
||||
Object.defineProperty(globalThis, 'performance', originalPerformance);
|
||||
if (originalPerformance) Object.defineProperty(globalThis, 'performance', originalPerformance);
|
||||
else delete globalThis.performance;
|
||||
},
|
||||
};
|
||||
@@ -401,16 +295,14 @@ function militaryLayer() {
|
||||
return {
|
||||
id: 'military',
|
||||
getDetectableObjects() {
|
||||
return [
|
||||
{
|
||||
position: new Cesium.Cartesian3(0, 0, 6_356_752),
|
||||
sourceId: 'military-pin',
|
||||
id: 'MIL-PIN',
|
||||
metric: 'FL450',
|
||||
type: 'AIR',
|
||||
tier: 'military',
|
||||
},
|
||||
];
|
||||
return [{
|
||||
position: new Cesium.Cartesian3(0, 0, 6_356_752),
|
||||
sourceId: 'military-pin',
|
||||
id: 'MIL-PIN',
|
||||
metric: 'FL450',
|
||||
type: 'AIR',
|
||||
tier: 'military',
|
||||
}];
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -466,9 +358,7 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
assert.equal(env.postRender.listeners.size, 1);
|
||||
assert.equal(env.document.getElementById('detection-overlay'), null);
|
||||
assert.ok(env.document.getElementById('world-overlay-canvas'));
|
||||
const surface = env.document.getElementById(
|
||||
'world-overlay-detection-surface',
|
||||
);
|
||||
const surface = env.document.getElementById('world-overlay-detection-surface');
|
||||
const root = env.document.getElementById('world-overlay-root');
|
||||
const mainCanvas = env.document.getElementById('world-overlay-canvas');
|
||||
assert.ok(surface);
|
||||
@@ -491,36 +381,19 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
assert.equal(first.profile, 'DENSE');
|
||||
assert.ok(Array.isArray(first.calloutRects));
|
||||
if (first.calloutRects.length) {
|
||||
const x = first.calloutRects[0].x;
|
||||
first.calloutRects[0].x = NaN;
|
||||
assert.equal(
|
||||
getDetectionDiagnostics().calloutRects[0].x,
|
||||
x,
|
||||
'QA owns a copy, never a live plate',
|
||||
);
|
||||
const x=first.calloutRects[0].x;
|
||||
first.calloutRects[0].x=NaN;
|
||||
assert.equal(getDetectionDiagnostics().calloutRects[0].x,x,'QA owns a copy, never a live plate');
|
||||
}
|
||||
assert.equal(first.observationCount, 2);
|
||||
assert.equal(first.didSolve, true);
|
||||
assert.ok(first.solveRevision > 0);
|
||||
assert.ok(first.collectiveLabelBudget > 0);
|
||||
assert.ok(
|
||||
first.bracketOpacityCounts.full + first.bracketOpacityCounts.partial > 0,
|
||||
);
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(
|
||||
([name, text]) =>
|
||||
name === 'fillText' && String(text).startsWith('DENSE VIS:'),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(
|
||||
([name, path]) => name === 'stroke' && path instanceof MockPath2D,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(([name]) => name === 'fillRect'),
|
||||
'scanlines and banner still paint',
|
||||
);
|
||||
assert.ok(first.bracketOpacityCounts.full + first.bracketOpacityCounts.partial > 0);
|
||||
assert.ok(env.detectionCtx.calls.some(([name, text]) => name === 'fillText'
|
||||
&& String(text).startsWith('DENSE VIS:')));
|
||||
assert.ok(env.detectionCtx.calls.some(([name, path]) => name === 'stroke' && path instanceof MockPath2D));
|
||||
assert.ok(env.detectionCtx.calls.some(([name]) => name === 'fillRect'), 'scanlines and banner still paint');
|
||||
assert.equal(env.detectionCtx.globalCompositeOperation, 'source-over');
|
||||
assert.equal(env.detectionCtx.filter, 'none');
|
||||
assert.equal(surface.style.display, 'block');
|
||||
@@ -536,16 +409,8 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
// screen-blended sensor surface: `screen` can only lighten, so a callout's
|
||||
// dark backing plate is a no-op over sunlit ground and the text dissolves
|
||||
// into it. Brackets, banner and scanlines stay on the sensor surface.
|
||||
assert.ok(
|
||||
env.ctx.calls.some(
|
||||
([name, text]) => name === 'fillText' && text === 'TEST0',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
!env.detectionCtx.calls.some(
|
||||
([name, text]) => name === 'fillText' && text === 'TEST0',
|
||||
),
|
||||
);
|
||||
assert.ok(env.ctx.calls.some(([name, text]) => name === 'fillText' && text === 'TEST0'));
|
||||
assert.ok(!env.detectionCtx.calls.some(([name, text]) => name === 'fillText' && text === 'TEST0'));
|
||||
env.advance(1);
|
||||
env.postRender.raise();
|
||||
assert.ok(getDetectionDiagnostics().solveRevision > first.solveRevision);
|
||||
@@ -555,10 +420,7 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
assert.equal(getMode(), 'SPARSE');
|
||||
assert.equal(getDetectionDiagnostics().profile, 'SPARSE');
|
||||
assert.equal(getDetectionDiagnostics().allocationStrategy, 'WEIGHTED');
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(([name]) => name === 'arc'),
|
||||
'sparse focus ring remains available',
|
||||
);
|
||||
assert.ok(env.detectionCtx.calls.some(([name]) => name === 'arc'), 'sparse focus ring remains available');
|
||||
|
||||
setMode('BALANCED');
|
||||
env.postRender.raise();
|
||||
@@ -567,12 +429,9 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
env.postRender.raise();
|
||||
assert.equal(getDetectionDiagnostics().profile, 'DENSE');
|
||||
|
||||
const bannerCount = () =>
|
||||
env.detectionCtx.calls.filter(
|
||||
([name, text]) =>
|
||||
name === 'fillText' &&
|
||||
/^(SPARSE|BALANCED|DENSE) VIS:/.test(String(text)),
|
||||
).length;
|
||||
const bannerCount = () => env.detectionCtx.calls.filter(([name, text]) => (
|
||||
name === 'fillText' && /^(SPARSE|BALANCED|DENSE) VIS:/.test(String(text))
|
||||
)).length;
|
||||
const beforeSuspend = bannerCount();
|
||||
suspendDetection('intercity');
|
||||
assert.equal(isDetectionSuspended(), true);
|
||||
@@ -589,12 +448,9 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
// These are the exact engine calls made by military style presets after
|
||||
// their production UI gate chooses CRT/NVG/FLIR defaults.
|
||||
const expectedThemes = {
|
||||
retro:
|
||||
'contrast(1.08) saturate(1.04) drop-shadow(0 0 3px rgba(255, 176, 56, 0.45))',
|
||||
surveillance:
|
||||
'contrast(1.12) saturate(1.12) drop-shadow(0 0 3px rgba(120, 255, 120, 0.42))',
|
||||
thermal:
|
||||
'contrast(1.1) saturate(1.08) drop-shadow(0 0 3px rgba(255, 224, 170, 0.42))',
|
||||
retro: 'contrast(1.08) saturate(1.04) drop-shadow(0 0 3px rgba(255, 176, 56, 0.45))',
|
||||
surveillance: 'contrast(1.12) saturate(1.12) drop-shadow(0 0 3px rgba(120, 255, 120, 0.42))',
|
||||
thermal: 'contrast(1.1) saturate(1.08) drop-shadow(0 0 3px rgba(255, 224, 170, 0.42))',
|
||||
};
|
||||
for (const style of ['retro', 'surveillance', 'thermal']) {
|
||||
setMode('OFF');
|
||||
@@ -620,28 +476,17 @@ test('detection lifecycle re-hosts unchanged painters behind the sole host liste
|
||||
assert.equal(getMode(), 'OFF');
|
||||
assert.equal(surface.style.display, 'none');
|
||||
assert.equal(env.postRender.listeners.size, 1);
|
||||
assert.ok(
|
||||
modes.includes('SPARSE') &&
|
||||
modes.includes('BALANCED') &&
|
||||
modes.includes('DENSE'),
|
||||
);
|
||||
assert.ok(modes.includes('SPARSE') && modes.includes('BALANCED') && modes.includes('DENSE'));
|
||||
|
||||
destroyDetection();
|
||||
assert.equal(
|
||||
env.postRender.listeners.size,
|
||||
1,
|
||||
'detection never owns the listener',
|
||||
);
|
||||
assert.equal(env.postRender.listeners.size, 1, 'detection never owns the listener');
|
||||
setMode('DENSE');
|
||||
env.postRender.raise();
|
||||
assert.equal(env.document.getElementById('detection-overlay'), null);
|
||||
destroyWorldOverlay();
|
||||
assert.equal(env.postRender.listeners.size, 0);
|
||||
assert.equal(env.document.getElementById('world-overlay-canvas'), null);
|
||||
assert.equal(
|
||||
env.document.getElementById('world-overlay-detection-surface'),
|
||||
null,
|
||||
);
|
||||
assert.equal(env.document.getElementById('world-overlay-detection-surface'), null);
|
||||
} finally {
|
||||
env.cleanup();
|
||||
}
|
||||
@@ -654,22 +499,16 @@ test('callouts stop painting the moment the last detectable object goes away', (
|
||||
// clear the replay buffer, or the final callsigns stay stranded on the
|
||||
// shared canvas after the last data layer is switched off.
|
||||
const env = installEnvironment();
|
||||
let objects = [
|
||||
{
|
||||
position: new Cesium.Cartesian3(0, 0, 6_356_752),
|
||||
sourceId: 'lonely-1',
|
||||
id: 'LASTONE',
|
||||
metric: 'FL120',
|
||||
type: 'AIR',
|
||||
},
|
||||
];
|
||||
let objects = [{
|
||||
position: new Cesium.Cartesian3(0, 0, 6_356_752),
|
||||
sourceId: 'lonely-1',
|
||||
id: 'LASTONE',
|
||||
metric: 'FL120',
|
||||
type: 'AIR',
|
||||
}];
|
||||
try {
|
||||
initWorldOverlay(env.viewer);
|
||||
initDetection(
|
||||
env.viewer,
|
||||
[{ id: 'flights', getDetectableObjects: () => objects }],
|
||||
() => {},
|
||||
);
|
||||
initDetection(env.viewer, [{ id: 'flights', getDetectableObjects: () => objects }], () => {});
|
||||
setMode('DENSE');
|
||||
env.advance(250);
|
||||
env.postRender.raise();
|
||||
@@ -678,9 +517,7 @@ test('callouts stop painting the moment the last detectable object goes away', (
|
||||
env.advance(250);
|
||||
env.postRender.raise();
|
||||
assert.ok(
|
||||
env.ctx.calls.some(
|
||||
([name, text]) => name === 'fillText' && text === 'LASTONE',
|
||||
),
|
||||
env.ctx.calls.some(([name, text]) => name === 'fillText' && text === 'LASTONE'),
|
||||
'the callout paints on the shared canvas while the contact exists',
|
||||
);
|
||||
|
||||
@@ -691,9 +528,7 @@ test('callouts stop painting the moment the last detectable object goes away', (
|
||||
env.advance(250);
|
||||
env.postRender.raise();
|
||||
assert.ok(
|
||||
!env.ctx.calls.some(
|
||||
([name, text]) => name === 'fillText' && text === 'LASTONE',
|
||||
),
|
||||
!env.ctx.calls.some(([name, text]) => name === 'fillText' && text === 'LASTONE'),
|
||||
'an empty field must drop the replay buffer, not strand the last callsign',
|
||||
);
|
||||
} finally {
|
||||
@@ -760,24 +595,12 @@ test('space-tier callouts take the heavier plate while air contacts keep the lig
|
||||
theme.calloutPlateSpace,
|
||||
'the fixture is only meaningful while the two tiers differ',
|
||||
);
|
||||
const painted = (text) =>
|
||||
env.ctx.calls.some(
|
||||
([name, value]) => name === 'fillText' && value === text,
|
||||
);
|
||||
assert.ok(
|
||||
painted('AIRONE') && painted('SATONE'),
|
||||
'both tiers reached the frame under test',
|
||||
);
|
||||
const painted = (text) => env.ctx.calls.some(([name, value]) => name === 'fillText' && value === text);
|
||||
assert.ok(painted('AIRONE') && painted('SATONE'), 'both tiers reached the frame under test');
|
||||
|
||||
const fills = sharedFillStyles(env);
|
||||
assert.ok(
|
||||
fills.includes(theme.calloutPlate),
|
||||
'the air contact keeps the light plate',
|
||||
);
|
||||
assert.ok(
|
||||
fills.includes(theme.calloutPlateSpace),
|
||||
'the space contact takes the heavier plate',
|
||||
);
|
||||
assert.ok(fills.includes(theme.calloutPlate), 'the air contact keeps the light plate');
|
||||
assert.ok(fills.includes(theme.calloutPlateSpace), 'the space contact takes the heavier plate');
|
||||
} finally {
|
||||
env.cleanup();
|
||||
}
|
||||
@@ -794,16 +617,11 @@ test('surveillance military tier reaches the dedicated render target as shipped
|
||||
env.postRender.raise();
|
||||
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(
|
||||
([name, value]) => name === 'strokeStyle' && value === '#ff5a47',
|
||||
),
|
||||
env.detectionCtx.calls.some(([name, value]) => name === 'strokeStyle' && value === '#ff5a47'),
|
||||
'the production projection/tier/batched-bracket paint path emits surveillance military red',
|
||||
);
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(
|
||||
([name, path]) => name === 'stroke' && path instanceof MockPath2D,
|
||||
),
|
||||
);
|
||||
assert.ok(env.detectionCtx.calls.some(([name, path]) => name === 'stroke'
|
||||
&& path instanceof MockPath2D));
|
||||
} finally {
|
||||
env.cleanup();
|
||||
}
|
||||
@@ -815,71 +633,44 @@ test('pathological detection paint holds alternate frames without freezing share
|
||||
try {
|
||||
initWorldOverlay(env.viewer);
|
||||
initDetection(env.viewer, [detectableLayer()], () => {});
|
||||
setOverlayEntries('valve-card', [
|
||||
{
|
||||
id: 'ambient-card',
|
||||
position: new Cesium.Cartesian3(0, 0, 0),
|
||||
variant: 'label',
|
||||
title: 'AMBIENT-CONTINUES',
|
||||
selected: true,
|
||||
protected: true,
|
||||
horizonCull: false,
|
||||
edgeFade: 'none',
|
||||
},
|
||||
]);
|
||||
setOverlayEntries('valve-card', [{
|
||||
id: 'ambient-card',
|
||||
position: new Cesium.Cartesian3(0, 0, 0),
|
||||
variant: 'label',
|
||||
title: 'AMBIENT-CONTINUES',
|
||||
selected: true,
|
||||
protected: true,
|
||||
horizonCull: false,
|
||||
edgeFade: 'none',
|
||||
}]);
|
||||
setMode('DENSE');
|
||||
env.advance(250);
|
||||
env.setPerformanceStep(30);
|
||||
|
||||
env.postRender.raise();
|
||||
env.postRender.raise();
|
||||
const detectionClears = env.detectionCtx.calls.filter(
|
||||
([name]) => name === 'clearRect',
|
||||
).length;
|
||||
const detectionBanners = env.detectionCtx.calls.filter(
|
||||
([name, text]) =>
|
||||
name === 'fillText' && String(text).startsWith('DENSE VIS:'),
|
||||
).length;
|
||||
const sharedClears = env.ctx.calls.filter(
|
||||
([name]) => name === 'clearRect',
|
||||
).length;
|
||||
const sharedCards = env.ctx.calls.filter(
|
||||
([name, text]) => name === 'fillText' && text === 'AMBIENT-CONTINUES',
|
||||
).length;
|
||||
const detectionClears = env.detectionCtx.calls.filter(([name]) => name === 'clearRect').length;
|
||||
const detectionBanners = env.detectionCtx.calls.filter(([name, text]) => name === 'fillText'
|
||||
&& String(text).startsWith('DENSE VIS:')).length;
|
||||
const sharedClears = env.ctx.calls.filter(([name]) => name === 'clearRect').length;
|
||||
const sharedCards = env.ctx.calls.filter(([name, text]) => name === 'fillText'
|
||||
&& text === 'AMBIENT-CONTINUES').length;
|
||||
|
||||
env.postRender.raise();
|
||||
assert.equal(
|
||||
env.detectionCtx.calls.filter(([name]) => name === 'clearRect').length,
|
||||
detectionClears,
|
||||
'the host preserves detection pixels on the held odd frame',
|
||||
);
|
||||
assert.equal(
|
||||
env.detectionCtx.calls.filter(
|
||||
([name, text]) =>
|
||||
name === 'fillText' && String(text).startsWith('DENSE VIS:'),
|
||||
).length,
|
||||
detectionBanners,
|
||||
);
|
||||
assert.equal(
|
||||
env.ctx.calls.filter(([name]) => name === 'clearRect').length,
|
||||
sharedClears + 1,
|
||||
'the shared surface still clears',
|
||||
);
|
||||
assert.equal(
|
||||
env.ctx.calls.filter(
|
||||
([name, text]) => name === 'fillText' && text === 'AMBIENT-CONTINUES',
|
||||
).length,
|
||||
sharedCards + 1,
|
||||
'the shared lane still repaints',
|
||||
);
|
||||
assert.equal(env.detectionCtx.calls.filter(([name]) => name === 'clearRect').length, detectionClears,
|
||||
'the host preserves detection pixels on the held odd frame');
|
||||
assert.equal(env.detectionCtx.calls.filter(([name, text]) => name === 'fillText'
|
||||
&& String(text).startsWith('DENSE VIS:')).length, detectionBanners);
|
||||
assert.equal(env.ctx.calls.filter(([name]) => name === 'clearRect').length, sharedClears + 1,
|
||||
'the shared surface still clears');
|
||||
assert.equal(env.ctx.calls.filter(([name, text]) => name === 'fillText'
|
||||
&& text === 'AMBIENT-CONTINUES').length, sharedCards + 1,
|
||||
'the shared lane still repaints');
|
||||
assert.equal(getDetectionDiagnostics().throttleSkipCount, 1);
|
||||
|
||||
env.postRender.raise();
|
||||
assert.equal(
|
||||
env.detectionCtx.calls.filter(([name]) => name === 'clearRect').length,
|
||||
detectionClears + 1,
|
||||
'the next even frame repaints detection',
|
||||
);
|
||||
assert.equal(env.detectionCtx.calls.filter(([name]) => name === 'clearRect').length,
|
||||
detectionClears + 1, 'the next even frame repaints detection');
|
||||
assert.equal(getDetectionDiagnostics().throttleSkipCount, 1);
|
||||
} finally {
|
||||
env.cleanup();
|
||||
@@ -887,14 +678,9 @@ test('pathological detection paint holds alternate frames without freezing share
|
||||
});
|
||||
|
||||
test('detection cannot resurrect a private canvas, listener, matrix, resize, clear, or UI inventory', () => {
|
||||
const source = readFileSync(
|
||||
new URL('./detection.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const uiSource =
|
||||
readShellSource() +
|
||||
'\n' +
|
||||
readFileSync(new URL('../ui/visualPresets.js', import.meta.url), 'utf8');
|
||||
const source = readFileSync(new URL('./detection.js', import.meta.url), 'utf8');
|
||||
const uiSource = readShellSource()
|
||||
+ '\n' + readFileSync(new URL('../ui/visualPresets.js', import.meta.url), 'utf8');
|
||||
assert.doesNotMatch(source, /createElement\(\s*['"]canvas['"]\s*\)/);
|
||||
assert.doesNotMatch(source, /postRender\.addEventListener/);
|
||||
assert.doesNotMatch(source, /['"]detection-overlay['"]/);
|
||||
@@ -904,20 +690,11 @@ test('detection cannot resurrect a private canvas, listener, matrix, resize, cle
|
||||
assert.doesNotMatch(source, /CALLOUT_OCCLUSION_SELECTORS/);
|
||||
assert.doesNotMatch(source, /_ctx\.(?:filter|globalCompositeOperation)\s*=/);
|
||||
assert.match(source, /_hostSurface\.style\.mixBlendMode = _theme\.blend;/);
|
||||
assert.match(
|
||||
source,
|
||||
/_hostSurface\.style\.filter = `\$\{_theme\.filter\} drop-shadow\(0 0 \$\{GLOW_PX\}px \$\{_theme\.glow\}\)`;/,
|
||||
);
|
||||
assert.match(source, /_hostSurface\.style\.filter = `\$\{_theme\.filter\} drop-shadow\(0 0 \$\{GLOW_PX\}px \$\{_theme\.glow\}\)`;/);
|
||||
assert.match(source, /const LABEL_SOLVE_INTERVAL_MS = 125;/);
|
||||
assert.match(source, /const _labelArbiter = new LabelArbiter\(\);/);
|
||||
assert.match(
|
||||
source,
|
||||
/const clipW = vp3 \* px \+ vp7 \* py \+ vp11 \* pz \+ vp15;/,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/registerWorldOverlayPaintLane\('detection', _paintDetectionLane/,
|
||||
);
|
||||
assert.match(source, /const clipW = vp3 \* px \+ vp7 \* py \+ vp11 \* pz \+ vp15;/);
|
||||
assert.match(source, /registerWorldOverlayPaintLane\('detection', _paintDetectionLane/);
|
||||
assert.match(source, /target: 'detection'/);
|
||||
assert.match(source, /shouldPaint: _shouldPaintDetectionLane/);
|
||||
// The three military styles used to carry three copies of these numbers.
|
||||
@@ -934,10 +711,7 @@ test('detection cannot resurrect a private canvas, listener, matrix, resize, cle
|
||||
3,
|
||||
'CRT, NVG, and FLIR retain their Dense auto-enable defaults',
|
||||
);
|
||||
assert.match(
|
||||
uiSource,
|
||||
/preset\.detection && !this\._detectionUserOverridden/,
|
||||
);
|
||||
assert.match(uiSource, /preset\.detection && !this\._detectionUserOverridden/);
|
||||
});
|
||||
|
||||
// ── Developer telemetry gate (2026-08-20 QA hunt) ───────────────────────────
|
||||
@@ -951,22 +725,15 @@ test('detectionDebugRequested parses the query-string gate and nothing else', ()
|
||||
assert.equal(detectionDebugRequested(''), false);
|
||||
assert.equal(detectionDebugRequested('?detectDebug=0'), false);
|
||||
assert.equal(detectionDebugRequested('?detectDebug'), false);
|
||||
assert.equal(
|
||||
detectionDebugRequested('?detectdebug=1'),
|
||||
false,
|
||||
'the flag is case-sensitive',
|
||||
);
|
||||
assert.equal(detectionDebugRequested('?detectdebug=1'), false, 'the flag is case-sensitive');
|
||||
assert.equal(detectionDebugRequested(undefined), false);
|
||||
assert.equal(detectionDebugRequested(null), false);
|
||||
});
|
||||
|
||||
test('the mode banner is absent by default and present behind the flag', () => {
|
||||
const bannerPaints = (env) =>
|
||||
env.detectionCtx.calls.filter(
|
||||
([name, text]) =>
|
||||
name === 'fillText' &&
|
||||
/^(SPARSE|BALANCED|DENSE) VIS:/.test(String(text)),
|
||||
).length;
|
||||
const bannerPaints = (env) => env.detectionCtx.calls.filter(([name, text]) => (
|
||||
name === 'fillText' && /^(SPARSE|BALANCED|DENSE) VIS:/.test(String(text))
|
||||
)).length;
|
||||
|
||||
const painted = (search) => {
|
||||
const env = installEnvironment({ search });
|
||||
@@ -978,9 +745,7 @@ test('the mode banner is absent by default and present behind the flag', () => {
|
||||
env.postRender.raise();
|
||||
// The overlay is alive either way — brackets still stroke.
|
||||
assert.ok(
|
||||
env.detectionCtx.calls.some(
|
||||
([name, path]) => name === 'stroke' && path instanceof MockPath2D,
|
||||
),
|
||||
env.detectionCtx.calls.some(([name, path]) => name === 'stroke' && path instanceof MockPath2D),
|
||||
'detection still paints its contacts regardless of the debug gate',
|
||||
);
|
||||
return bannerPaints(env);
|
||||
@@ -991,15 +756,8 @@ test('the mode banner is absent by default and present behind the flag', () => {
|
||||
}
|
||||
};
|
||||
|
||||
assert.equal(
|
||||
painted(''),
|
||||
0,
|
||||
'users must never see the engine telemetry banner',
|
||||
);
|
||||
assert.ok(
|
||||
painted('?detectDebug=1') > 0,
|
||||
'the flag brings the telemetry back',
|
||||
);
|
||||
assert.equal(painted(''), 0, 'users must never see the engine telemetry banner');
|
||||
assert.ok(painted('?detectDebug=1') > 0, 'the flag brings the telemetry back');
|
||||
});
|
||||
|
||||
test('detection telemetry stays reachable programmatically with the banner hidden', () => {
|
||||
@@ -1036,11 +794,7 @@ test('civilian and military AIR brackets cover front, left, and right at Sparse
|
||||
type: 'AIR',
|
||||
}));
|
||||
initWorldOverlay(env.viewer);
|
||||
initDetection(
|
||||
env.viewer,
|
||||
[{ id: layerId, getDetectableObjects: () => objects }],
|
||||
() => {},
|
||||
);
|
||||
initDetection(env.viewer, [{ id: layerId, getDetectableObjects: () => objects }], () => {});
|
||||
setDetectionTuning({ densityPct: 25 });
|
||||
setMode('SPARSE');
|
||||
settleFrame(env);
|
||||
@@ -1053,10 +807,8 @@ test('civilian and military AIR brackets cover front, left, and right at Sparse
|
||||
assert.equal(diagnostics.visibleCount, 3);
|
||||
assert.equal(diagnostics.densityPct, 25);
|
||||
assert.equal(diagnostics.profile, 'SPARSE');
|
||||
assert.ok(
|
||||
diagnostics.selectedCount <= diagnostics.collectiveLabelBudget,
|
||||
'Sparse keeps its documented budget instead of silently becoming Dense',
|
||||
);
|
||||
assert.ok(diagnostics.selectedCount <= diagnostics.collectiveLabelBudget,
|
||||
'Sparse keeps its documented budget instead of silently becoming Dense');
|
||||
} finally {
|
||||
destroyDetection();
|
||||
destroyWorldOverlay();
|
||||
@@ -1081,17 +833,11 @@ function backdropLayer() {
|
||||
return [
|
||||
{
|
||||
position: new Cesium.Cartesian3(-0.4, 0.2, 6_356_752),
|
||||
sourceId: 'ground-1',
|
||||
id: 'GROUNDED',
|
||||
metric: 'FL100',
|
||||
type: 'AIR',
|
||||
sourceId: 'ground-1', id: 'GROUNDED', metric: 'FL100', type: 'AIR',
|
||||
},
|
||||
{
|
||||
position: new Cesium.Cartesian3(0.4, -0.2, 20_000_000),
|
||||
sourceId: 'sky-1',
|
||||
id: 'SKYBACK',
|
||||
metric: 'FL400',
|
||||
type: 'AIR',
|
||||
sourceId: 'sky-1', id: 'SKYBACK', metric: 'FL400', type: 'AIR',
|
||||
},
|
||||
];
|
||||
},
|
||||
@@ -1113,14 +859,8 @@ test('the backdrop feather reaches the canvas as a lighter plate against sky', (
|
||||
env.ctx.calls.length = 0;
|
||||
settleFrame(env);
|
||||
|
||||
const painted = (text) =>
|
||||
env.ctx.calls.some(
|
||||
([name, value]) => name === 'fillText' && value === text,
|
||||
);
|
||||
assert.ok(
|
||||
painted('GROUNDED') && painted('SKYBACK'),
|
||||
'both backdrops reached the frame under test',
|
||||
);
|
||||
const painted = (text) => env.ctx.calls.some(([name, value]) => name === 'fillText' && value === text);
|
||||
assert.ok(painted('GROUNDED') && painted('SKYBACK'), 'both backdrops reached the frame under test');
|
||||
|
||||
const plate = DETECTION_THEME_MAP._default.calloutPlate;
|
||||
const plateAlphas = env.ctx.calls
|
||||
@@ -1131,10 +871,7 @@ test('the backdrop feather reaches the canvas as a lighter plate against sky', (
|
||||
const heaviest = Math.max(...plateAlphas);
|
||||
const lightest = Math.min(...plateAlphas);
|
||||
assert.ok(heaviest > 0, 'the grounded contact still gets a plate');
|
||||
assert.ok(
|
||||
lightest > 0,
|
||||
'the sky contact keeps a whisper rather than vanishing',
|
||||
);
|
||||
assert.ok(lightest > 0, 'the sky contact keeps a whisper rather than vanishing');
|
||||
// Reverting the feature makes both plates equal, which fails here first.
|
||||
assert.ok(
|
||||
lightest < heaviest * 0.5,
|
||||
@@ -1200,23 +937,9 @@ test('a transit vehicle is a contact: box, route, mode and operator on the share
|
||||
painted.some((text) => String(text).includes('34 KM/H')),
|
||||
'and so does what the vehicle is doing',
|
||||
);
|
||||
assert.ok(
|
||||
env.ctx.calls.some(
|
||||
([name, , color, width]) =>
|
||||
name === 'stroke' && color === '#5EF08A' && width === 1.25,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
env.ctx.calls.some(
|
||||
([name, , color, width]) =>
|
||||
name === 'stroke' && color === '#05080C' && width === 3.25,
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
!env.detectionCtx.calls.some(
|
||||
([name, , color]) => name === 'stroke' && color === '#5EF08A',
|
||||
),
|
||||
);
|
||||
assert.ok(env.ctx.calls.some(([name, , color, width]) => name === 'stroke' && color === '#5EF08A' && width === 1.25));
|
||||
assert.ok(env.ctx.calls.some(([name, , color, width]) => name === 'stroke' && color === '#05080C' && width === 3.25));
|
||||
assert.ok(!env.detectionCtx.calls.some(([name, , color]) => name === 'stroke' && color === '#5EF08A'));
|
||||
// The operator travels on the object for the two-tier card form, which is
|
||||
// where `klass` is read; the host's track label does not paint it.
|
||||
assert.equal(composeLabel(objects[0]).secondary, 'MBTA · BUS 34 KM/H');
|
||||
|
||||
Reference in new issue
Block a user