Files
gods-eye-view/src/creditKeyboard.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

224 lines
8.0 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { configureCreditKeyboardAccess } from './creditKeyboard.js';
import { setLocale } from './i18n/index.js';
function fakeElement() {
const attributes = new Map();
const listeners = new Map();
return {
dataset: {},
focused: false,
id: '',
setAttribute(name, value) { attributes.set(name, String(value)); },
getAttribute(name) { return attributes.get(name) ?? null; },
addEventListener(type, listener) {
if (!listeners.has(type)) listeners.set(type, []);
listeners.get(type).push(listener);
},
dispatch(type, event = {}) {
const dispatched = {
currentTarget: this,
target: this,
defaultPrevented: false,
propagationStopped: false,
preventDefault() { this.defaultPrevented = true; },
stopPropagation() { this.propagationStopped = true; },
...event,
};
for (const listener of listeners.get(type) || []) listener(dispatched);
return dispatched;
},
click() {
this.onclick?.();
this.dispatch('click');
},
focus() { this.focused = true; },
listenerCount(type) { return listeners.get(type)?.length || 0; },
};
}
function fixture() {
const expand = fakeElement();
const close = fakeElement();
const lightbox = fakeElement();
lightbox.querySelector = () => close;
lightbox.contains = (target) => target === lightbox || target === close;
const overlay = fakeElement();
lightbox.parentElement = overlay;
const root = {
querySelector(selector) {
if (selector === '#cesium-credits .cesium-credit-expand-link') return expand;
if (selector === '.cesium-credit-lightbox') return lightbox;
return null;
},
};
return { close, expand, lightbox, overlay, root };
}
test('Cesium Data attribution and close controls join the keyboard tab order', () => {
const f = fixture();
assert.equal(configureCreditKeyboardAccess(f.root), true);
assert.equal(f.expand.getAttribute('role'), 'button');
assert.equal(f.expand.getAttribute('tabindex'), '0');
assert.equal(f.expand.getAttribute('aria-haspopup'), 'dialog');
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
assert.equal(f.close.getAttribute('role'), 'button');
assert.equal(f.close.getAttribute('tabindex'), '0');
assert.equal(f.close.getAttribute('aria-label'), 'Close data attribution');
});
test('Enter opens and closes attribution on initial keydown', () => {
const f = fixture();
let opened = 0;
let closed = 0;
f.expand.onclick = () => { opened++; };
f.close.onclick = () => { closed++; };
configureCreditKeyboardAccess(f.root);
const openEvent = f.expand.dispatch('keydown', { key: 'Enter' });
assert.equal(openEvent.defaultPrevented, true);
assert.equal(opened, 1);
assert.equal(f.expand.getAttribute('aria-expanded'), 'true');
assert.equal(f.close.focused, true);
const closeEvent = f.close.dispatch('keydown', { key: 'Enter' });
assert.equal(closeEvent.defaultPrevented, true);
assert.equal(closed, 1);
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
assert.equal(f.expand.focused, true);
});
test('Space opens and closes attribution only on key release', () => {
const f = fixture();
let opened = 0;
let closed = 0;
f.expand.onclick = () => { opened++; };
f.close.onclick = () => { closed++; };
configureCreditKeyboardAccess(f.root);
const openDown = f.expand.dispatch('keydown', { key: ' ' });
assert.equal(openDown.defaultPrevented, true);
assert.equal(opened, 0);
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
const openUp = f.expand.dispatch('keyup', { key: ' ' });
assert.equal(openUp.defaultPrevented, true);
assert.equal(opened, 1);
assert.equal(f.expand.getAttribute('aria-expanded'), 'true');
assert.equal(f.close.focused, true);
const closeDown = f.close.dispatch('keydown', { key: ' ' });
assert.equal(closeDown.defaultPrevented, true);
assert.equal(closed, 0);
const closeUp = f.close.dispatch('keyup', { key: ' ' });
assert.equal(closeUp.defaultPrevented, true);
assert.equal(closed, 1);
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
assert.equal(f.expand.focused, true);
});
test('held activation keys do not repeat open or close actions', () => {
const f = fixture();
let opened = 0;
f.expand.onclick = () => { opened++; };
configureCreditKeyboardAccess(f.root);
f.expand.dispatch('keydown', { key: ' ' });
const repeated = f.expand.dispatch('keydown', { key: ' ', repeat: true });
assert.equal(repeated.defaultPrevented, true);
assert.equal(opened, 0);
f.expand.dispatch('keyup', { key: ' ' });
assert.equal(opened, 1);
f.expand.dispatch('keyup', { key: ' ' });
assert.equal(opened, 1);
const g = fixture();
let enterOpened = 0;
g.expand.onclick = () => { enterOpened++; };
configureCreditKeyboardAccess(g.root);
g.expand.dispatch('keydown', { key: 'Enter' });
g.expand.dispatch('keydown', { key: 'Enter', repeat: true });
assert.equal(enterOpened, 1);
});
test('losing focus during a Space hold cancels release activation', () => {
const f = fixture();
let opened = 0;
f.expand.onclick = () => { opened++; };
configureCreditKeyboardAccess(f.root);
f.expand.dispatch('keydown', { key: ' ' });
f.expand.dispatch('blur');
const release = f.expand.dispatch('keyup', { key: ' ' });
assert.equal(release.defaultPrevented, true);
assert.equal(opened, 0);
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
});
test('Escape and backdrop dismissal close attribution and restore its disclosure', () => {
const f = fixture();
let closed = 0;
f.close.onclick = () => { closed++; };
configureCreditKeyboardAccess(f.root);
f.expand.click();
const escape = f.lightbox.dispatch('keydown', { key: 'Escape', target: f.close });
assert.equal(escape.defaultPrevented, true);
assert.equal(escape.propagationStopped, true);
assert.equal(closed, 1);
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
assert.equal(f.expand.focused, true);
f.expand.focused = false;
f.expand.click();
f.overlay.dispatch('click', { target: f.overlay });
assert.equal(f.expand.getAttribute('aria-expanded'), 'false');
assert.equal(f.expand.focused, true);
});
test('credit keyboard setup is idempotent and safely declines incomplete markup', () => {
const f = fixture();
assert.equal(configureCreditKeyboardAccess(f.root), true);
assert.equal(configureCreditKeyboardAccess(f.root), true);
assert.equal(f.expand.listenerCount('keydown'), 1);
assert.equal(f.expand.listenerCount('keyup'), 1);
assert.equal(f.expand.listenerCount('blur'), 1);
assert.equal(f.close.listenerCount('keydown'), 1);
assert.equal(f.close.listenerCount('keyup'), 1);
assert.equal(f.close.listenerCount('blur'), 1);
assert.equal(f.lightbox.listenerCount('keydown'), 1);
assert.equal(f.overlay.listenerCount('click'), 1);
assert.equal(configureCreditKeyboardAccess({ querySelector: () => null }), false);
});
function textFixture() {
const f = fixture();
const title = fakeElement();
title.textContent = 'Data provided by:';
f.expand.textContent = 'Data attribution';
f.lightbox.querySelector = (selector) =>
selector === '.cesium-credit-lightbox-title' ? title : f.close;
return { ...f, title };
}
test('English keeps Cesium attribution texts', () => {
setLocale('en');
const f = textFixture();
configureCreditKeyboardAccess(f.root);
assert.equal(f.expand.textContent, 'Data attribution');
assert.equal(f.title.textContent, 'Data provided by:');
});
for (const [locale, link, title] of [
['ru', 'Источники данных', 'Данные предоставлены:'],
['es', 'Atribución de datos', 'Datos proporcionados por:'],
])
test(`${locale}: Cesium attribution link and lightbox title are translated`, () => {
setLocale(locale);
try {
const f = textFixture();
configureCreditKeyboardAccess(f.root);
assert.equal(f.expand.textContent, link);
assert.equal(f.title.textContent, title);
} finally {
setLocale('en');
}
});