mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Mark every UI string the unmarked-text check reported (534) plus text it cannot see (helpers, joined labels, runtime-built panels), and translate the result to Spanish and Russian: 1,679 messages each. - Kept on purpose, with the reason where the text lives: provider and product names, place and camera names, codes and units, dB values, the HUD terminal readouts, demo shot titles that saved projects look up by title, and text for AI tools and the voice model. - Modules shared with the server or AI tools stay free of the i18n import (local msg); the wind reading and voice session stay English and are translated where the page shows them. - npm test now runs the unmarked-text check. - Fixes on the way: a duplicated radio privacy paragraph, the contact panel toggle saying Expand while open, the CSS filter mistaking & for CSS. Co-Authored-By: Claude Opus 5.5 <[email protected]>
120 lines
6.2 KiB
JavaScript
120 lines
6.2 KiB
JavaScript
import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs';
|
|
import { StyleManager } from './ui/applicationShell.js';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const ui = readShellSource();
|
|
const director = fs.readFileSync(path.join(ROOT, 'src', 'scenes', 'director.js'), 'utf8');
|
|
|
|
/** Source of the free-text LOCATION search handler (Enter on #location-search). */
|
|
function locationSearchHandler() {
|
|
const start = ui.indexOf(' _handleLocationSearchState(state, change) {');
|
|
assert.ok(start > 0, 'free-text location search handler is missing');
|
|
const end = ui.indexOf('_beginWorldJumpTransition() {', start);
|
|
assert.ok(end > start, 'could not bound the location search handler');
|
|
return ui.slice(start, end);
|
|
}
|
|
|
|
test('the ACTIVE STYLE indicator is written from the style name and nothing else', () => {
|
|
// A free-text location search used to write the searched CITY into the
|
|
// top-right style slot, so the corner read "ACTIVE STYLE / TOKYO".
|
|
const writes = [...ui.matchAll(/this\._styleIndicator\.textContent\s*=/g)];
|
|
assert.equal(writes.length, 1, 'the style indicator must have exactly one writer');
|
|
assert.match(
|
|
ui.slice(writes[0].index, writes[0].index + 160),
|
|
/this\._styleIndicator\.textContent = STYLE_STATUS_LABELS\[styleName\]\s*\?\s*t\(STYLE_STATUS_LABELS\[styleName\]\)\s*:\s*styleName\.toUpperCase\(\);/,
|
|
);
|
|
|
|
const handler = locationSearchHandler();
|
|
assert.doesNotMatch(handler, /_styleIndicator/, 'location search must not touch the style indicator');
|
|
assert.doesNotMatch(handler, /active-style-name/, 'location search must not touch the style indicator');
|
|
});
|
|
|
|
test('a free-text search records its destination for the LOCATION mini-status', () => {
|
|
const handler = locationSearchHandler();
|
|
// The destination has to be recorded BEFORE _setActiveLocation(null), whose
|
|
// own refresh would otherwise repaint the readout as "Location: --".
|
|
assert.match(
|
|
handler,
|
|
/this\._searchedLocationLabel = state\.destination\.label[\s\S]{0,120}?this\._setActiveLocation\(null\);/,
|
|
);
|
|
assert.match(handler, /this\._updateLocationMiniStatus\(\);/);
|
|
});
|
|
|
|
test('the mini-status reads its copy from the shared formatter', () => {
|
|
const controls = fs.readFileSync(path.join(ROOT, 'src', 'ui', 'locationControls.js'), 'utf8');
|
|
assert.match(controls, /import \{ locationMiniStatus \} from '\.\.\/locationStatus\.js';/);
|
|
assert.match(controls, /const lines = locationMiniStatus\(state\)/);
|
|
const start = ui.indexOf(' _updateLocationMiniStatus() {');
|
|
assert.ok(start > 0, '_updateLocationMiniStatus is missing');
|
|
const body = ui.slice(start, ui.indexOf('\n }', start));
|
|
assert.match(body, /_locationControls\?\.renderStatus\(\{[\s\S]*?searchedLabel: this\._searchedLocationLabel,[\s\S]*?\}\)/);
|
|
// No second copy of the placeholder strings to drift out of sync.
|
|
assert.doesNotMatch(body, /Location: --/);
|
|
});
|
|
|
|
test('selecting a preset location clears the superseded search label', () => {
|
|
const start = ui.indexOf(' _setActiveLocation(locationId) {');
|
|
assert.ok(start > 0, '_setActiveLocation is missing');
|
|
const body = ui.slice(start, ui.indexOf('\n }', start));
|
|
assert.match(body, /if \(locationId\) this\._searchedLocationLabel = null;/);
|
|
});
|
|
|
|
test('any other camera destination clears the search label too', () => {
|
|
// Voice navigation, the globe reset, camera takeover and entity selection
|
|
// all funnel through _stampNavigation; without a clear there, a searched
|
|
// label outlives the place it named.
|
|
const start = ui.indexOf(' _stampNavigation(');
|
|
assert.ok(start > 0, '_stampNavigation is missing');
|
|
assert.match(shellMethod('_stampNavigation').toString(), /if \(clearSearchedLocation\) this\.clearLocation\(\);/);
|
|
|
|
// The shared funnel is what the reset and voice seams actually reach.
|
|
for (const seam of ['resetToGlobeView() {', 'beginLocationNavigation() {', '_runExplicitNavigation(']) {
|
|
const at = ui.indexOf(seam);
|
|
assert.ok(at > 0, `missing navigation seam "${seam}"`);
|
|
assert.match(shellMethod(seam.match(/^(\w+)/)[1]).toString(), /_stampNavigation\(/, `"${seam}" must stamp navigation`);
|
|
}
|
|
|
|
// Public seam, so a camera owner that flies on its own can invalidate it.
|
|
assert.match(ui, /\n {2}clearSearchedLocation\(\) \{\n[\s\S]{0,240}?this\._searchedLocationLabel = null;/);
|
|
});
|
|
|
|
test('a deferred lookup that never flies leaves the readout standing', () => {
|
|
// A geocode stamps navigation on the way OUT and resolves later. Clearing at
|
|
// the stamp blanked a still-true readout whenever the lookup failed, was
|
|
// superseded, or was refused — no camera ever moved. The deferred begin opts
|
|
// out; the reassert seam, reached only once the flight is granted, clears.
|
|
const begin = ui.indexOf(' _beginDeferredNavigation(');
|
|
assert.ok(begin > 0, '_beginDeferredNavigation is missing');
|
|
assert.match(
|
|
shellMethod('_beginDeferredNavigation').toString(),
|
|
/stamp: \(\) =>\s*this\._stampNavigation\(\{\s*cancelPendingSelection,\s*clearSearchedLocation: false,?\s*\}\)/,
|
|
);
|
|
|
|
const reassert = ui.indexOf(' _reassertNavigationHandoff(generation) {');
|
|
assert.ok(reassert > 0, '_reassertNavigationHandoff is missing');
|
|
assert.match(
|
|
ui.slice(reassert, reassert + 900),
|
|
/release: \(\) => \{[\s\S]{0,320}?this\.clearLocation\(\);[\s\S]{0,200}?this\._releaseFollowCamera\(\)/,
|
|
);
|
|
|
|
// …and the policy only reaches `release` after its authority checks pass.
|
|
const policy = fs.readFileSync(path.join(ROOT, 'src', 'navigationPolicy.js'), 'utf8');
|
|
const fn = policy.slice(policy.indexOf('export function reassertNavigationHandoff'));
|
|
assert.match(fn, /if \(disposed \|\| generation !== currentGeneration\) return false;[\s\S]*?release\?\.\(\);/);
|
|
});
|
|
|
|
test('scene playback invalidates the search label on every shot', () => {
|
|
// The director drives viewer.camera itself and never reaches _stampNavigation.
|
|
const start = director.indexOf(' async _flyCamera(cameraState, durationSec, token) {');
|
|
assert.ok(start > 0, 'scene camera flight is missing');
|
|
assert.match(
|
|
director.slice(start, start + 700),
|
|
/this\.styleManager\?\.clearSearchedLocation\?\.\(\);/,
|
|
);
|
|
});
|