Files
gods-eye-view/src/ui/layerPanel.test.mjs
T
German KhokhlovandClaude Opus 5.5 3129e531e6 feat(i18n): translate the whole interface
Mark every UI string the unmarked-text check reported (534) plus text it
cannot see (helpers, joined labels, runtime-built panels), and translate
the result to Spanish and Russian: 1,679 messages each.

- Kept on purpose, with the reason where the text lives: provider and
  product names, place and camera names, codes and units, dB values, the HUD
  terminal readouts, demo shot titles that saved projects look up by title,
  and text for AI tools and the voice model.
- Modules shared with the server or AI tools stay free of the i18n import
  (local msg); the wind reading and voice session stay English and are
  translated where the page shows them.
- npm test now runs the unmarked-text check.
- Fixes on the way: a duplicated radio privacy paragraph, the contact panel
  toggle saying Expand while open, the CSS filter mistaking & for CSS.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 19:24:42 +04:00

263 lines
8.0 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';
test('panel presentation places Transit between Street Traffic and Bike Share in Movement', () => {
const source = readFileSync(
new URL('./layerPanel.js', import.meta.url),
'utf8',
);
const declarations = source.slice(
source.indexOf('const PANEL_GROUPS ='),
source.indexOf('const PANEL_POSITIONS ='),
);
const order = JSON.parse(
runInNewContext(
`const msg = (text) => text;\n${declarations}\nJSON.stringify(PANEL_ORDER)`,
),
);
assert.deepEqual(
order.filter(({ label }) => label === 'Movement').map(({ id }) => id),
[
'satellites',
'flights',
'military',
'local-adsb',
'ais-live-vessels',
'traffic',
'transit',
'bikeshare',
],
);
assert.equal(order.filter(({ id }) => id === 'transit').length, 1);
});
test('partial feed controls distinguish incomplete records from stale data and outages', async () => {
const { LayerPanel, layerFeedState } = await import('./layerPanel.js');
const classes = new Map();
const attrs = new Map();
const button = {
classList: { toggle: (key, value) => classes.set(key, value) },
dataset: {},
setAttribute: (key, value) => attrs.set(key, value),
};
const layer = {
id: 'ais-live-vessels',
name: 'Live Vessels',
source: 'AISStream',
enabled: true,
stats: {
partial: true,
stale: false,
count: 2,
acceptedRowCount: 2,
rawRowCount: 3,
lastUpdate: Date.now(),
},
};
const panel = LayerPanel.prototype;
panel._syncToggleButton(button, layer);
assert.equal(button.textContent, 'PARTIAL');
assert.equal(button.dataset.feedState, 'partial');
assert.equal(classes.get('feed-partial'), true);
assert.equal(classes.get('feed-stale'), false);
assert.match(attrs.get('aria-label'), /PARTIAL/);
assert.match(
panel._buildMetaText(layer),
/^PARTIAL · AISStream · 2 of 3 records accepted · /,
);
assert.match(
panel._buildMetaText({
...layer,
stats: { ...layer.stats, rawRowCount: 2 },
}),
/incomplete snapshot/,
);
assert.equal(layerFeedState({ ...layer.stats, stale: true }), 'stale');
assert.equal(
layerFeedState({ ...layer.stats, error: 'Connection lost' }),
'degraded',
);
assert.equal(
layerFeedState({ ...layer.stats, status: 'unavailable' }),
'unavailable',
);
assert.equal(layerFeedState({ ...layer.stats, loading: true }), 'loading');
layer.stats = { ...layer.stats, partial: false };
panel._syncToggleButton(button, layer);
assert.equal(button.textContent, 'ON');
assert.equal(classes.get('feed-partial'), false);
layer.enabled = false;
panel._syncToggleButton(button, layer);
assert.equal(button.textContent, 'OFF');
});
test('readout rows contain only toggles and metadata; ordinary rows retain controls', async () => {
const { LayerPanel } = await import('./layerPanel.js');
const { railFixture } = await import('./railTestFixture.mjs');
const f = railFixture();
const previousDocument = globalThis.document;
globalThis.document = f.document;
const ids = [
'wind',
'weather-radar',
'weather-satellite',
'weather-lightning',
'weather-cyclones',
'other',
];
let refresh;
const layers = ids.map((id) => ({
id,
name: id,
icon: '*',
source: 'Source',
enabled: true,
showInTogglePanel: true,
stats: {},
}));
const calls = [];
const panel = new LayerPanel({
getLayers: () => layers,
isEnabled: () => true,
setEnabled: (...args) => calls.push(args),
setLayerParams: (...args) => calls.push(args),
hasRowControls: () => true,
subscribeRowControls: (_id, callback) => {
refresh = callback;
},
getRowControls: (id) => ({
readout: id !== 'other',
summary: { status: 'Loading' },
info: 'Detailed time',
chips: [{ id: 'config', label: 'Soft', params: { opacity: 'light' } }],
legend: [{ label: 'Rain', color: '#abcdef' }],
list: {
items: [{ id: 'storm', text: 'Storm', params: { focus: true } }],
},
}),
});
try {
panel.mount(f.container);
for (const id of ids.slice(0, -1)) {
const row = f.find((n) => n.dataset.layerId === id);
assert.equal(row.children.length, 2);
assert.ok(row.querySelector('.data-name'));
assert.ok(row.querySelector('.data-toggle-meta'));
for (const cls of [
'data-toggle-chip',
'data-count',
'data-toggle-controls',
'data-toggle-controls-status',
'data-row-list',
])
assert.equal(row.querySelector(`.${cls}`), null);
row.querySelector('.data-toggle-btn').click();
}
assert.deepEqual(
calls.map(([id]) => id),
ids.slice(0, -1),
);
const ordinary = f.find((n) => n.dataset.layerId === 'other');
assert.equal(
ordinary.querySelector('.data-toggle-controls-info').textContent,
'Detailed time',
);
assert.ok(ordinary.querySelector('.data-toggle-legend-item'));
ordinary.querySelector('.data-toggle-chip').click();
assert.deepEqual(calls.at(-1), [
'other',
{ opacity: 'light' },
{ origin: 'user' },
]);
assert.equal(typeof refresh, 'function');
} finally {
panel.destroy();
globalThis.document = previousDocument;
}
});
test('the Recent Imagery readout mounts in its rail body like the weather readout and is rebuilt or released with the panel', async () => {
const { LayerPanel } = await import('./layerPanel.js');
const { railFixture } = await import('./railTestFixture.mjs');
const f = railFixture();
const body = f.document.createElement('div');
f.document.getElementById = (id) =>
id === 'recent-imagery-panel-body' ? body : null;
const previousDocument = globalThis.document;
globalThis.document = f.document;
const mounted = [];
const factory = (container) => {
const readout = {
container,
destroyed: false,
destroy() {
this.destroyed = true;
},
};
mounted.push(readout);
return readout;
};
const panel = new LayerPanel({
getLayers: () => [],
isEnabled: () => false,
setEnabled() {},
setLayerParams() {},
hasRowControls: () => false,
subscribeRowControls() {},
getRowControls: () => null,
});
try {
panel.attachRecentImagery(factory);
assert.equal(mounted.length, 0, 'no body before the panel mounts');
panel.mount(f.container);
assert.equal(mounted.length, 1);
assert.equal(mounted[0].container, body);
panel.mount(f.container);
assert.equal(mounted[0].destroyed, true, 'a remount rebuilds it');
assert.equal(mounted.length, 2);
panel.attachRecentImagery(null);
assert.equal(mounted[1].destroyed, true);
panel.attachRecentImagery(factory);
assert.equal(mounted.length, 3);
panel.destroy();
assert.equal(mounted[2].destroyed, true);
panel.attachRecentImagery(factory);
assert.equal(mounted.length, 3, 'a destroyed panel mounts nothing');
} finally {
panel.destroy();
globalThis.document = previousDocument;
}
});
test('an omitted source is visibly unavailable and cannot be toggled on', async () => {
const { LayerPanel } = await import('./layerPanel.js');
const attrs = new Map();
const button = {
classList: { toggle() {} },
dataset: {},
setAttribute: (key, value) => attrs.set(key, value),
};
const layer = {
id: 'flights',
name: 'Flights',
enabled: false,
stats: {
sourceUnavailable: true,
status: 'unavailable',
error: 'Data source not configured: flights',
},
};
LayerPanel.prototype._syncToggleButton(button, layer);
assert.equal(button.textContent, 'UNAVAILABLE');
assert.equal(attrs.get('aria-disabled'), 'true');
assert.match(button.title, /not configured/);
LayerPanel.prototype._syncToggleButton(button, { ...layer, enabled: true });
assert.equal(
attrs.get('aria-disabled'),
'false',
'an active layer can always be switched off',
);
});