Files
gods-eye-view/src/creditKeyboard.test.mjs
T
Sameh Khamis b20c80e590 Switch language in place, without a reload
Choosing a language in the picker now applies it at once. The choice is
stored and ?lang is kept in step through replaceState, and the picker keeps
focus.

Code that writes translated text itself re-presents it on a switch:
onLocaleChange() lets an owner listen, removed at teardown, and status
writers hand over a function of the locale (voice cards, scene director,
SDR, draw hints, imagery split, map sources, Bhote Koshi media) so the
words are rebuilt rather than re-sent. The HUD, cockpit, radio, CCTV,
launch, awareness and credit texts follow the same hook, and
applyDocumentTranslations can be applied again without undoing text code
wrote since.

The unsaved-work registry and persistLocaleAndReload existed only to guard
the reload, so they go, with their catalog entries.
2026-11-01 16:47:39 -08:00

237 lines
8.4 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');
}
});
test('setting up again after a switch back to English restores the English texts', () => {
const f = textFixture();
setLocale('ru');
try {
configureCreditKeyboardAccess(f.root);
} finally {
setLocale('en');
}
configureCreditKeyboardAccess(f.root);
assert.equal(f.expand.textContent, 'Data attribution');
assert.equal(f.title.textContent, 'Data provided by:');
});