mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
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]>
224 lines
8.0 KiB
JavaScript
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');
|
|
}
|
|
});
|