mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Han and kana have no word edges, so a CJK forbidden word now counts anywhere
in the reply. The proxy no longer counts words: the model reads Rue de Rivoli
as one, so only the 80-character word-boundary cap remains.
Also restore src/voice/gevActions.test.mjs, reformatted by mistake in 818d2c82.
301 lines
9.8 KiB
JavaScript
301 lines
9.8 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import './testSupport/stubMgrs.mjs';
|
|
import { hudReadouts } from './testSupport/hudReadouts.mjs';
|
|
|
|
const { IntelHUD } = await import('./hud.js');
|
|
const { setLocale } = await import('./i18n/index.js');
|
|
const { hudSummaryHasEnglish, hudSummaryInput } =
|
|
await import('./hudSummaryResponse.js');
|
|
|
|
const PARTIAL_FLIGHTS = {
|
|
id: 'flights',
|
|
name: 'Live Flights',
|
|
enabled: true,
|
|
stats: { count: 4, partial: true, source: 'opensky' },
|
|
};
|
|
|
|
function createHud(
|
|
t,
|
|
reply,
|
|
{ places = ['Остин'], locale = 'ru', layers = () => [PARTIAL_FLIGHTS] } = {},
|
|
) {
|
|
const { elements, publish } = hudReadouts();
|
|
const previous = { document: globalThis.document, window: globalThis.window };
|
|
globalThis.document = {
|
|
hidden: false,
|
|
visibilityState: 'visible',
|
|
getElementById: (id) => elements.get(id) ?? null,
|
|
querySelectorAll: () => [],
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
};
|
|
globalThis.window ??= {
|
|
setTimeout,
|
|
clearTimeout,
|
|
setInterval,
|
|
clearInterval,
|
|
};
|
|
t.mock.timers.enable({ apis: ['setInterval'] });
|
|
const viewer = {
|
|
scene: { canvas: { width: 0, height: 0 }, pickPositionSupported: false },
|
|
camera: {
|
|
pitch: -Math.PI / 2,
|
|
heading: 0,
|
|
positionCartographic: { latitude: 0.53, longitude: -1.7, height: 600 },
|
|
computeViewRectangle: () => undefined,
|
|
moveStart: { addEventListener() {}, removeEventListener() {} },
|
|
moveEnd: { addEventListener() {}, removeEventListener() {} },
|
|
},
|
|
};
|
|
const sent = [];
|
|
const hud = new IntelHUD(viewer, {
|
|
publish,
|
|
summaryService: {
|
|
configured: async () => true,
|
|
async summarize(context) {
|
|
sent.push(context);
|
|
return { ok: true, status: 200, data: { error: null, ...reply } };
|
|
},
|
|
},
|
|
});
|
|
t.after(() => {
|
|
hud.destroy();
|
|
setLocale('en');
|
|
for (const [key, value] of Object.entries(previous)) {
|
|
if (value === undefined) delete globalThis[key];
|
|
else globalThis[key] = value;
|
|
}
|
|
});
|
|
// The test viewer has no view target, so no place lookup runs.
|
|
const buildContext = hud._summaryContext.bind(hud);
|
|
hud._summaryContext = async () =>
|
|
Object.assign(await buildContext(), { placeLabels: places });
|
|
hud._dataManager = { getAll: layers };
|
|
hud._latestMetrics = { altM: 600 };
|
|
setLocale(locale);
|
|
return {
|
|
hud,
|
|
sent,
|
|
shown: () => elements.get('hud-summary').textContent,
|
|
};
|
|
}
|
|
|
|
test('a ru HUD request carries no English layer name', async (t) => {
|
|
const { hud } = createHud(t, {});
|
|
const context = await hud._summaryContext();
|
|
assert.equal(context.locale, 'ru');
|
|
assert.deepEqual(context.enabledLayerLabels, ['Рейсы онлайн']);
|
|
const input = hudSummaryInput(context);
|
|
assert.doesNotMatch(input, /Live Flights/, input);
|
|
// The locale signs the dedupe signature; the English names stay local.
|
|
assert.match(JSON.stringify(context), /"locale":"ru"/);
|
|
assert.doesNotMatch(JSON.stringify(context), /englishLayerWords|warning/);
|
|
});
|
|
|
|
test('a ru summary shows the state word first and the layer names last', async (t) => {
|
|
const { hud, shown } = createHud(t, {
|
|
summary: 'Аэропорт задерживает рейсы',
|
|
feedState: 'partial',
|
|
});
|
|
await hud._updateSummary(false, true);
|
|
assert.equal(shown(), 'ЧАСТИЧНО | Аэропорт задерживает рейсы | РЕЙСЫ ОНЛАЙН');
|
|
});
|
|
|
|
for (const words of [
|
|
'Live Flights частично онлайн',
|
|
'Рейсы онлайн stale данные',
|
|
'Остин flights частично онлайн',
|
|
'Austin flights partially online',
|
|
]) {
|
|
test(`a ru reply echoing English falls back: ${words}`, async (t) => {
|
|
const { hud, shown } = createHud(t, {
|
|
summary: words,
|
|
feedState: 'partial',
|
|
});
|
|
await hud._updateSummary(false, true);
|
|
assert.ok(shown().startsWith('ЧАСТИЧНО | '), shown());
|
|
assert.ok(!shown().includes(words), shown());
|
|
});
|
|
}
|
|
|
|
test('a view with layers but no place label does not ask the model', async (t) => {
|
|
const { hud, sent, shown } = createHud(
|
|
t,
|
|
{ summary: 'Москва Рейсы онлайн частично', feedState: 'partial' },
|
|
{ places: [] },
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.equal(sent.length, 0);
|
|
assert.equal(shown(), hud._composeSummary());
|
|
});
|
|
|
|
test('a ja request leaves the layer state ids to `overall`', async (t) => {
|
|
const { hud } = createHud(t, {}, { locale: 'ja' });
|
|
const context = await hud._summaryContext();
|
|
assert.equal(context.feedProvenance.overall, 'partial');
|
|
assert.ok(!('feedState' in context.enabledLayers[0]));
|
|
assert.ok(!('feedState' in context.feedProvenance.layers[0]));
|
|
});
|
|
|
|
for (const [locale, summary] of [
|
|
['ja', '東京上空のstale航空情報'],
|
|
['ja', '東京上空のLive Flights'],
|
|
['zh-CN', '北京航班stale数据'],
|
|
['ru', 'Остин Live-Flights частично'],
|
|
]) {
|
|
test(`English flush against other text is an echo: ${summary}`, () => {
|
|
assert.ok(hudSummaryHasEnglish(summary, locale, ['stale', 'Live Flights']));
|
|
});
|
|
}
|
|
|
|
test('English inside a longer Latin word is not an echo', () => {
|
|
assert.ok(!hudSummaryHasEnglish('Datos stalesque', 'es', ['stale']));
|
|
});
|
|
|
|
for (const [locale, summary, places] of [
|
|
['es', 'Tráfico aéreo nominal sobre Madrid', ['Madrid']],
|
|
['de', 'Fallback Daten über Berlin', ['Berlin']],
|
|
['ja', 'Live Flights Plaza周辺', ['Live Flights Plaza']],
|
|
['es', 'Estación Loading Dock Madrid', ['Estación Loading Dock']],
|
|
]) {
|
|
test(`a ${locale} word that is not an echo is shown: ${summary}`, async (t) => {
|
|
const { hud, shown } = createHud(
|
|
t,
|
|
{ summary, feedState: 'partial' },
|
|
{ places, locale },
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.ok(shown().includes(summary), shown());
|
|
});
|
|
}
|
|
|
|
test('a language switch drops the reply in flight and asks again', async (t) => {
|
|
const { hud, sent, shown } = createHud(t, {});
|
|
let answer;
|
|
hud.summaryService.summarize = (context, { signal }) => {
|
|
sent.push(context);
|
|
return new Promise((resolve, reject) => {
|
|
answer = () =>
|
|
resolve({
|
|
ok: true,
|
|
status: 200,
|
|
data: {
|
|
error: null,
|
|
summary: 'Остин рейсы частично',
|
|
feedState: 'partial',
|
|
},
|
|
});
|
|
signal.addEventListener('abort', () =>
|
|
reject(Object.assign(new Error('aborted'), { name: 'AbortError' })),
|
|
);
|
|
});
|
|
};
|
|
const pending = hud._updateSummary(false, true);
|
|
while (!answer) await new Promise((resolve) => setImmediate(resolve));
|
|
setLocale('es');
|
|
answer();
|
|
await pending;
|
|
assert.ok(!shown().includes('Остин'), shown());
|
|
assert.equal(shown(), hud._composeSummary());
|
|
|
|
hud.summaryService.summarize = async (context) => {
|
|
sent.push(context);
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
data: {
|
|
error: null,
|
|
summary: 'Austin vuelos parciales',
|
|
feedState: 'partial',
|
|
},
|
|
};
|
|
};
|
|
await hud._updateSummary(false);
|
|
assert.equal(sent.at(-1).locale, 'es');
|
|
assert.ok(shown().startsWith('PARCIAL | Austin vuelos parciales'), shown());
|
|
});
|
|
|
|
test('a reused ru reply carries the current warning, not the cached one', async (t) => {
|
|
// Both layers load an empty view: no count, no lastUpdate yet.
|
|
let flights = { loading: true, count: 0 };
|
|
const { hud, shown } = createHud(
|
|
t,
|
|
{ summary: 'Остин небо над городом', feedState: 'loading' },
|
|
{
|
|
layers: () => [
|
|
{ id: 'flights', name: 'Live Flights', enabled: true, stats: flights },
|
|
{
|
|
id: 'ais',
|
|
name: 'Live AIS Vessels',
|
|
enabled: true,
|
|
stats: { loading: true, count: 0 },
|
|
},
|
|
],
|
|
},
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.match(shown(), /РЕЙСЫ ОНЛАЙН/, shown());
|
|
// Live Flights finishes with nothing to show; AIS is still loading.
|
|
flights = { count: 0 };
|
|
hud._markSummaryDirty();
|
|
await hud._updateSummary(false);
|
|
assert.match(
|
|
shown(),
|
|
/^ЗАГРУЗКА \| Остин небо над городом \| СУДА AIS/,
|
|
shown(),
|
|
);
|
|
assert.doesNotMatch(shown(), /РЕЙСЫ ОНЛАЙН/, shown());
|
|
});
|
|
|
|
test('with every feed healthy a reply calling one unavailable falls back', async (t) => {
|
|
const { hud, shown } = createHud(
|
|
t,
|
|
{ summary: 'Остин рейсы недоступно сейчас', feedState: null },
|
|
{ layers: () => [] },
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.equal(shown(), hud._composeSummary());
|
|
});
|
|
|
|
for (const [locale, place, summary] of [
|
|
['zh-CN', '北京', '北京航班不可用'],
|
|
['zh-Hant', '北京', '北京航班不可用'],
|
|
['ja', '東京', '東京航空便利用不可'],
|
|
]) {
|
|
test(`a ${locale} reply calling a feed unavailable flush against Han falls back`, async (t) => {
|
|
const { hud, shown } = createHud(
|
|
t,
|
|
{ summary, feedState: null },
|
|
{ locale, places: [place], layers: () => [] },
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.equal(shown(), hud._composeSummary());
|
|
});
|
|
}
|
|
|
|
test('an ar reply opening with a Latin name keeps reading right to left', async (t) => {
|
|
const { hud, shown } = createHud(
|
|
t,
|
|
{ summary: 'Times Square نيويورك الولايات المتحدة', feedState: null },
|
|
{ locale: 'ar', places: ['Times Square'], layers: () => [] },
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.equal(shown(), '\u200fTimes Square نيويورك الولايات المتحدة');
|
|
});
|
|
|
|
test('a one-word layer name that is a French word is not an echo', async (t) => {
|
|
const summary = 'Bus en transit à Paris';
|
|
const { hud, shown } = createHud(
|
|
t,
|
|
{ summary, feedState: null },
|
|
{
|
|
locale: 'fr',
|
|
places: ['Paris'],
|
|
layers: () => [{ id: 'transit', name: 'Transit', enabled: true }],
|
|
},
|
|
);
|
|
await hud._updateSummary(false, true);
|
|
assert.equal(shown(), summary);
|
|
});
|