mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
LocalePicker renders the existing picker model with the same markup and listbox behaviour; ProviderSettingsView shows it while the shell publishes the active language and its choice handler to the gev-locale store. A host passing localePicker: false publishes nothing. A choice reaches the shell after the list is closed on screen, and every opening keeps the active option in view.
165 lines
4.9 KiB
JavaScript
165 lines
4.9 KiB
JavaScript
// The language picker reloads the page, so it asks first while the
|
|
// reload would lose work (unsaved CCTV calibration, the open cockpit, anything
|
|
// a module registers). Choosing the language already active does nothing.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { StyleManager } from './applicationShell.js';
|
|
import { useLocaleStore } from './stores/locale.js';
|
|
import { applicationPinia } from './vueRuntime.js';
|
|
import {
|
|
describeUnsavedWork,
|
|
msg,
|
|
registerUnsavedWork,
|
|
setLocale,
|
|
} from '../i18n/index.js';
|
|
|
|
/** Wire the shell's language choice with the given shell state. No picker
|
|
* markup here: the picker's own behaviour is covered by localePicker.test
|
|
* and localePickerView.test. */
|
|
function renderSwitch({ calDirty = false, cockpit = false } = {}) {
|
|
const previous = {
|
|
document: globalThis.document,
|
|
location: globalThis.location,
|
|
history: globalThis.history,
|
|
confirm: globalThis.confirm,
|
|
};
|
|
const reloads = [];
|
|
const prompts = [];
|
|
let answer = true;
|
|
globalThis.document = { getElementById: () => null };
|
|
globalThis.location = {
|
|
href: 'https://gev.example/#v=2',
|
|
assign() {},
|
|
reload: () => reloads.push(true),
|
|
};
|
|
globalThis.history = { replaceState() {} };
|
|
globalThis.confirm = (text) => {
|
|
prompts.push(text);
|
|
return answer;
|
|
};
|
|
const shell = {
|
|
services: {
|
|
cctvLayer: {
|
|
getUIState: () => ({ cameras: [{ calDirty: false }, { calDirty }] }),
|
|
},
|
|
},
|
|
cockpitView: { active: cockpit },
|
|
_confirmLocaleReload: StyleManager.prototype._confirmLocaleReload,
|
|
_chooseLocale: StyleManager.prototype._chooseLocale,
|
|
};
|
|
StyleManager.prototype._initLocaleSelector.call(shell);
|
|
return {
|
|
choose: (locale) => shell._chooseLocale(locale),
|
|
reloads,
|
|
prompts,
|
|
answer: (value) => (answer = value),
|
|
restore() {
|
|
for (const unregister of shell._unsavedWorkChecks) unregister();
|
|
Object.assign(globalThis, previous);
|
|
},
|
|
};
|
|
}
|
|
|
|
const storage = globalThis.localStorage;
|
|
test.before(() => {
|
|
globalThis.localStorage = { setItem() {}, getItem: () => null };
|
|
});
|
|
test.after(() => {
|
|
globalThis.localStorage = storage;
|
|
});
|
|
|
|
test('nothing to lose: the switch reloads without asking', () => {
|
|
const view = renderSwitch();
|
|
try {
|
|
view.choose('es');
|
|
assert.deepEqual(view.prompts, []);
|
|
assert.equal(view.reloads.length, 1);
|
|
} finally {
|
|
view.restore();
|
|
}
|
|
});
|
|
|
|
test('unsaved calibration or the open cockpit: the switch asks first', () => {
|
|
const view = renderSwitch({ calDirty: true, cockpit: true });
|
|
try {
|
|
view.answer(false);
|
|
view.choose('ru');
|
|
assert.equal(view.prompts.length, 1);
|
|
assert.match(view.prompts[0], /unsaved CCTV calibration/);
|
|
assert.match(view.prompts[0], /the open cockpit/);
|
|
assert.equal(view.reloads.length, 0, 'declined: no reload');
|
|
view.answer(true);
|
|
view.choose('ru');
|
|
assert.equal(view.reloads.length, 1, 'confirmed: reload');
|
|
} finally {
|
|
view.restore();
|
|
}
|
|
});
|
|
|
|
test('modules register their own unsaved work', () => {
|
|
const unregister = registerUnsavedWork(() => msg('drawings on the map'));
|
|
const failing = registerUnsavedWork(() => {
|
|
throw new Error('broken check');
|
|
});
|
|
const view = renderSwitch();
|
|
try {
|
|
view.answer(false);
|
|
view.choose('es');
|
|
assert.match(view.prompts[0], /drawings on the map/);
|
|
assert.equal(view.reloads.length, 0);
|
|
} finally {
|
|
view.restore();
|
|
unregister();
|
|
failing();
|
|
}
|
|
assert.deepEqual(describeUnsavedWork(), []);
|
|
});
|
|
|
|
test('the prompt reads in the active language', () => {
|
|
setLocale('es');
|
|
const view = renderSwitch({ calDirty: true });
|
|
try {
|
|
view.answer(false);
|
|
view.choose('en');
|
|
assert.equal(view.prompts.length, 1);
|
|
assert.doesNotMatch(view.prompts[0], /Switching language/);
|
|
} finally {
|
|
view.restore();
|
|
setLocale('en');
|
|
}
|
|
});
|
|
|
|
test('the language already active neither asks nor reloads', () => {
|
|
const view = renderSwitch({ calDirty: true });
|
|
try {
|
|
assert.equal(view.choose('en'), 'unchanged');
|
|
assert.deepEqual(view.prompts, []);
|
|
assert.equal(view.reloads.length, 0);
|
|
} finally {
|
|
view.restore();
|
|
}
|
|
});
|
|
|
|
test('a host without Core locales shows no picker; otherwise the shell publishes one', () => {
|
|
const store = useLocaleStore(applicationPinia());
|
|
store.publishPicker(null);
|
|
const disabled = { services: {}, _localePickerEnabled: false };
|
|
const enabled = {
|
|
services: {},
|
|
_chooseLocale: (locale) => chosen.push(locale),
|
|
};
|
|
const chosen = [];
|
|
try {
|
|
StyleManager.prototype._initLocaleSelector.call(disabled);
|
|
assert.equal(store.picker, null);
|
|
StyleManager.prototype._initLocaleSelector.call(enabled);
|
|
assert.equal(store.picker.current, 'en');
|
|
store.picker.onSelect('fr');
|
|
assert.deepEqual(chosen, ['fr']);
|
|
} finally {
|
|
store.publishPicker(null);
|
|
for (const shell of [disabled, enabled])
|
|
for (const unregister of shell._unsavedWorkChecks ?? []) unregister();
|
|
}
|
|
});
|