mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
main moved the panels to Vue views and a one-open-panel rail, and removed
the two templates this branch edited. This merge carries the branch onto
that architecture; it adds no behaviour of its own beyond the port.
- The Utilities panel is a RailPanel in LayerPanelsView with its own rail
item (handyman). LayerPanel renders the Directions rows into its list with
LayerRows, and falls back to Data Layers without it.
- Draw leaves DisplayPanel for DrawControls, DrawMarkList and DrawSwatches,
which render what the draw tool publishes to the display store and call
its actions, as GeoImport does. drawMarks.js words the rows; the DOM-built
drawMarkList.js is gone. A row's fly-to is an immediate navigation like
Import's ('drawing' gets its Cockpit refusal), the name field keeps a
draft through v-field-value, and the colours are one radio group with a
single tab stop.
- UiSegmented draws an option's icon, named by its label, for the shape
buttons.
- The catalogs take main's languages with this branch's strings, Arabic
included, and the tests follow the views to their new homes.
138 lines
4.8 KiB
JavaScript
138 lines
4.8 KiB
JavaScript
// The Cockpit refusal names its destination in the UI language. The scan
|
|
// below collects every noun the callers hand to the navigation policy, so a
|
|
// noun without its own sentence fails here instead of reaching the page.
|
|
import assert from 'node:assert/strict';
|
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
import { join, relative } from 'node:path';
|
|
import test from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { TRACKABLE_FAMILIES } from './agent/worldPacket.js';
|
|
import { setLocale } from './i18n/index.js';
|
|
import {
|
|
COCKPIT_REFUSAL_NOUNS,
|
|
runExplicitNavigation,
|
|
} from './navigationPolicy.js';
|
|
import { WORLD_FOCUS_FRAMING } from './worldFocus.js';
|
|
|
|
const SRC = fileURLToPath(new URL('.', import.meta.url));
|
|
|
|
test.afterEach(() => setLocale('en'));
|
|
|
|
function cockpitToast(noun) {
|
|
const log = [];
|
|
runExplicitNavigation({
|
|
cockpitActive: true,
|
|
noun,
|
|
showToast: (text) => log.push(text),
|
|
stamp: () => 1,
|
|
release: () => {},
|
|
navigate: () => {},
|
|
});
|
|
assert.equal(log.length, 1);
|
|
return log[0];
|
|
}
|
|
|
|
test('es: the Cockpit refusal for frame_overhead has no English id', () => {
|
|
setLocale('es');
|
|
const shown = cockpitToast('frame');
|
|
assert.doesNotMatch(shown, /\bframe\b/, shown);
|
|
assert.equal(shown, 'Sal de la cabina para encuadrar contactos');
|
|
});
|
|
|
|
test('en: the voice nouns read as English sentences', () => {
|
|
setLocale('en');
|
|
assert.equal(cockpitToast('frame'), 'Exit cockpit to frame contacts');
|
|
assert.equal(cockpitToast('aircraft'), 'Exit cockpit to fly to an aircraft');
|
|
assert.equal(cockpitToast('satellite'), 'Exit cockpit to fly to a satellite');
|
|
});
|
|
|
|
// ── Every noun a caller passes ─────────────────────────────────────────────
|
|
|
|
function sourceFiles(dir) {
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
const path = join(dir, entry.name);
|
|
if (entry.isDirectory()) return sourceFiles(path);
|
|
return /\.js$/.test(entry.name) && !/\.test\./.test(entry.name)
|
|
? [path]
|
|
: [];
|
|
});
|
|
}
|
|
|
|
// The destination noun: the first argument of a navigation call, or the
|
|
// second of runManagedVoiceNavigation(styleManager, noun, …) in gevActions.js.
|
|
// `runNavigation(fly)` layer seams take only the flight (group 1).
|
|
const NOUN_ARGUMENT =
|
|
/\b(?:(runNavigation)\s*\(|(?:runImmediateNavigation|_runExplicitNavigation|runOrientation|_beginDeferredNavigation|cockpitRefusal|navigation\??\.(?:run|begin))\s*\(|runManagedVoiceNavigation\s*\(\s*[\w.?]+\s*,)\s*([^,)]*)/g;
|
|
// Literal nouns elsewhere: a parameter default (`noun = 'location'`) or both
|
|
// branches of `const noun = … ? 'cyclone' : 'weather'`.
|
|
const NOUN_LITERAL =
|
|
/\bnoun\s*=\s*'([^']+)'|\bconst noun\s*=\s*[^?;]*\?\s*'([^']+)'\s*:\s*'([^']+)'/g;
|
|
|
|
function trackableKinds() {
|
|
// This is shared with voice; test the exported owner, not its file position.
|
|
return TRACKABLE_FAMILIES.map(({ kind }) => kind);
|
|
}
|
|
|
|
// Non-literal arguments: what each one can be. A wrapper's own `noun` (or all
|
|
// its arguments) passes on; its default is a NOUN_LITERAL. Anything else is a
|
|
// new dynamic noun: list its values here.
|
|
const DYNAMIC_NOUNS = new Map([
|
|
['family.kind', trackableKinds],
|
|
[
|
|
"detail?.kind || 'target'",
|
|
() => [...Object.keys(WORLD_FOCUS_FRAMING), 'target'],
|
|
],
|
|
['noun', () => []],
|
|
['...args', () => []],
|
|
['...arguments', () => []],
|
|
]);
|
|
|
|
function collectNouns() {
|
|
const nouns = new Set();
|
|
const unknown = [];
|
|
for (const file of sourceFiles(SRC)) {
|
|
const text = readFileSync(file, 'utf8');
|
|
for (const [, seam, raw] of text.matchAll(NOUN_ARGUMENT)) {
|
|
const arg = raw.trim();
|
|
const literal = arg.match(/^'([^']*)'$/)?.[1];
|
|
if (literal) nouns.add(literal);
|
|
else if (DYNAMIC_NOUNS.has(arg))
|
|
DYNAMIC_NOUNS.get(arg)().forEach((noun) => nouns.add(noun));
|
|
else if (!seam && !/^noun\s*=/.test(arg))
|
|
unknown.push(`${relative(SRC, file)}: ${arg}`);
|
|
}
|
|
for (const m of text.matchAll(NOUN_LITERAL))
|
|
m.slice(1).forEach((noun) => noun && nouns.add(noun));
|
|
}
|
|
return { nouns, unknown };
|
|
}
|
|
|
|
test('every noun the callers pass has its own Cockpit refusal', () => {
|
|
const { nouns, unknown } = collectNouns();
|
|
assert.deepEqual(unknown, [], 'a new dynamic noun: add it to DYNAMIC_NOUNS');
|
|
// The scan still sees the known callers (a guard against a broken regex).
|
|
for (const noun of [
|
|
'location',
|
|
'camera',
|
|
'photo',
|
|
'shared view',
|
|
'file',
|
|
'drawing',
|
|
'scene',
|
|
'route',
|
|
'weather',
|
|
'cyclone',
|
|
'vessel',
|
|
'fire',
|
|
'frame',
|
|
'aircraft',
|
|
'satellite',
|
|
'target',
|
|
])
|
|
assert.ok(nouns.has(noun), `found ${noun}`);
|
|
const missing = [...nouns].filter(
|
|
(noun) => !COCKPIT_REFUSAL_NOUNS.includes(noun),
|
|
);
|
|
assert.deepEqual(missing, [], 'add these nouns to COCKPIT_REFUSALS');
|
|
});
|