mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
Merge main into prereq-10-osm-overlays and port Open Map Overlays
Main moved on since this PR opened; the overlay layer is ported to the current contracts: - Layer-state token 6 (j belongs to transit; new single-character tokens are digits) with a reservation-ledger row, and the option owner renamed to the layer id so the compact share codec can encode it. - Constructed per catalog by createOsmOverlaysLayer() in constructCatalog.js; src/standalone/data.js no longer registers layers. - Drapes through maps/imageryHost.js, so the overlays show on Google 3D (tileset imagery) as well as the globe stacks, and follow map-stack switches. Imagery layers are created on enable and destroyed on disable/destroy. - Translated chip labels and layer name in every locale, LAYER_NAME_KEYS, Infrastructure panel group, voice manifest entry. - Attribution: OpenSeaMap tiles CC BY-SA 2.0, OpenSnowMap credit as required, OSM ODbL credit for both; bulk-download note in DATA_SOURCES. - CURRENT-STATE row, changelog fragment, share-codec absent defaults, updated token-ledger fixtures and voice schema pins.
This commit is contained in:
commit
ce1ad3b95a
2330 files changed
+632956
-84310
No files matched your search
@@ -0,0 +1,220 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
resolveCatalogSources,
|
||||
CATALOG_SOURCE_CONTRACTS,
|
||||
} from './sourceComposition.js';
|
||||
import { createApplicationCatalog } from './constructCatalog.js';
|
||||
import { createSurfaceServices } from './surfaceServices.js';
|
||||
import { createStandaloneLayerSources } from '../standalone/layerSources.js';
|
||||
import { LayerLifecycle } from '../data/lifecycle.js';
|
||||
|
||||
function catalog(sources, t) {
|
||||
const lifetime = new AbortController();
|
||||
t.after(() => lifetime.abort());
|
||||
return createApplicationCatalog({
|
||||
sources,
|
||||
signal: lifetime.signal,
|
||||
surface: createSurfaceServices({
|
||||
terrainSource: { getHeights: async () => [] },
|
||||
signal: lifetime.signal,
|
||||
eventTarget: null,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
test('every acquisition source can be omitted without losing catalog membership', (t) => {
|
||||
const baseline = catalog(createStandaloneLayerSources(), t);
|
||||
for (const name of Object.keys(CATALOG_SOURCE_CONTRACTS)) {
|
||||
const sources = createStandaloneLayerSources({ [name]: null });
|
||||
const actual = catalog(sources, t);
|
||||
assert.deepEqual(actual.metadata, baseline.metadata, name);
|
||||
for (const id of CATALOG_SOURCE_CONTRACTS[name].layers) {
|
||||
assert.equal(actual.getSourceAvailability(id).available, false, id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('all acquisition sources may be absent, and enable fails before initializing or fetching', async (t) => {
|
||||
const actual = catalog({}, t);
|
||||
assert.equal(actual.layers.length, 34);
|
||||
const manager = new LayerLifecycle(
|
||||
{},
|
||||
{ getSourceAvailability: actual.getSourceAvailability },
|
||||
);
|
||||
for (const layer of actual.layers) manager.register(layer);
|
||||
for (const { layers: ids } of Object.values(CATALOG_SOURCE_CONTRACTS)) {
|
||||
for (const id of ids) {
|
||||
assert.equal(await manager.setEnabled(id, true), false, id);
|
||||
assert.equal(manager.layers.get(id).initialized, false, id);
|
||||
const row = manager.getAll().find((entry) => entry.id === id);
|
||||
assert.equal(row.enabled, false);
|
||||
assert.equal(row.stats.status, 'unavailable');
|
||||
assert.match(row.stats.error, /data source not configured/);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('configured sources retain identity; invalid implementations never become unavailable silently', () => {
|
||||
const source = { getSnapshot: async () => [] };
|
||||
assert.equal(
|
||||
resolveCatalogSources({ earthquakes: source }).sources.earthquakes,
|
||||
source,
|
||||
);
|
||||
for (const name of Object.keys(CATALOG_SOURCE_CONTRACTS)) {
|
||||
assert.throws(
|
||||
() => resolveCatalogSources({ [name]: {} }),
|
||||
/Invalid catalog source/,
|
||||
);
|
||||
}
|
||||
const missing = resolveCatalogSources({}).sources.earthquakes;
|
||||
assert.throws(() => missing.getSnapshot(), { code: 'unavailable' });
|
||||
});
|
||||
|
||||
test('standalone defaults allow exact replacements and explicit removal, and reject typos', () => {
|
||||
const source = { getSnapshot: async () => [] };
|
||||
const sources = createStandaloneLayerSources({
|
||||
flights: source,
|
||||
military: null,
|
||||
});
|
||||
assert.equal(sources.flights, source);
|
||||
assert.equal(sources.military, null);
|
||||
assert.equal(typeof sources.cctv.getCatalog, 'function');
|
||||
assert.throws(
|
||||
() => createStandaloneLayerSources({ fligts: source }),
|
||||
/Unknown source/,
|
||||
);
|
||||
});
|
||||
|
||||
test('GhostMaps obeys source omission, replacement and explicit removal', async (t) => {
|
||||
let fetches = 0;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
fetches++;
|
||||
throw new Error('Unexpected standalone acquisition');
|
||||
});
|
||||
const missing = catalog({}, t);
|
||||
assert.equal(missing.getSourceAvailability('s2-ghostmaps')?.available, false);
|
||||
const manager = new LayerLifecycle(
|
||||
{},
|
||||
{ getSourceAvailability: missing.getSourceAvailability },
|
||||
);
|
||||
manager.register(missing.get('s2-ghostmaps'));
|
||||
assert.equal(await manager.setEnabled('s2-ghostmaps', true), false);
|
||||
assert.equal(manager.layers.get('s2-ghostmaps').initialized, false);
|
||||
assert.equal(
|
||||
createStandaloneLayerSources({ 's2-ghostmaps': null })['s2-ghostmaps'],
|
||||
null,
|
||||
);
|
||||
|
||||
let reads = 0;
|
||||
let subscriptions = 0;
|
||||
let unsubscribed = 0;
|
||||
const source = {
|
||||
getCatalog: async () => {
|
||||
reads++;
|
||||
return { commit: 'a'.repeat(40), products: [] };
|
||||
},
|
||||
getFile: async () => {
|
||||
throw new Error('Empty catalog must not read files');
|
||||
},
|
||||
subscribe: () => {
|
||||
subscriptions++;
|
||||
return () => {
|
||||
unsubscribed++;
|
||||
};
|
||||
},
|
||||
};
|
||||
const supplied = createStandaloneLayerSources({ 's2-ghostmaps': source });
|
||||
assert.equal(supplied['s2-ghostmaps'], source);
|
||||
const configured = catalog(supplied, t);
|
||||
assert.equal(
|
||||
configured.getSourceAvailability('s2-ghostmaps').available,
|
||||
true,
|
||||
);
|
||||
const previous = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
||||
globalThis.document = { addEventListener() {}, removeEventListener() {} };
|
||||
t.after(() => {
|
||||
if (previous) Object.defineProperty(globalThis, 'document', previous);
|
||||
else delete globalThis.document;
|
||||
});
|
||||
const layer = configured.get('s2-ghostmaps');
|
||||
t.after(() => layer.destroy());
|
||||
layer.init({ scene: { requestRender() {} } });
|
||||
layer.enable();
|
||||
await layer.update();
|
||||
layer.disable();
|
||||
assert.equal(reads, 1);
|
||||
assert.equal(subscriptions, 1);
|
||||
assert.equal(unsubscribed, 1);
|
||||
assert.equal(layer.getStats().commit, 'a'.repeat(40));
|
||||
assert.equal(fetches, 0);
|
||||
});
|
||||
|
||||
test('empty Street Level uses the shared unavailable lifecycle without initialization', async (t) => {
|
||||
const actual = catalog({}, t);
|
||||
const manager = new LayerLifecycle(
|
||||
{},
|
||||
{ getSourceAvailability: actual.getSourceAvailability },
|
||||
);
|
||||
manager.register(actual.get('street-level'));
|
||||
assert.equal(await manager.setEnabled('street-level', true), false);
|
||||
assert.equal(manager.layers.get('street-level').initialized, false);
|
||||
const row = manager.getAll()[0];
|
||||
assert.equal(row.stats.status, 'unavailable');
|
||||
assert.equal(row.stats.error, 'No street-level imagery providers configured');
|
||||
assert.equal(await manager.setEnabled('street-level', false), true);
|
||||
const configured = catalog(createStandaloneLayerSources(), t);
|
||||
assert.equal(
|
||||
configured.get('street-level').getSourceAvailability().available,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('availability lookup accepts frozen layers and uses their display names', () => {
|
||||
const composition = resolveCatalogSources({});
|
||||
const layers = Object.values(CATALOG_SOURCE_CONTRACTS)
|
||||
.flatMap(({ layers }) => layers)
|
||||
.map((id) =>
|
||||
Object.freeze({
|
||||
id,
|
||||
name: id === 'fire-perimeters' ? 'Fire perimeters' : id,
|
||||
}),
|
||||
);
|
||||
const lookup = composition.createAvailabilityLookup(layers);
|
||||
assert.equal(
|
||||
lookup('fire-perimeters').reason,
|
||||
'Fire perimeters: data source not configured',
|
||||
);
|
||||
assert.equal(Object.hasOwn(layers[0], 'getSourceAvailability'), false);
|
||||
const manager = new LayerLifecycle({}, { getSourceAvailability: lookup });
|
||||
manager.register(layers.find(({ id }) => id === 'fire-perimeters'));
|
||||
assert.equal(
|
||||
manager.getAll()[0].stats.error,
|
||||
'Fire perimeters: data source not configured',
|
||||
);
|
||||
});
|
||||
|
||||
test('absent military acquisition does not poll, suppress flights, or erase supplied classification', async (t) => {
|
||||
let fetches = 0;
|
||||
t.mock.method(globalThis, 'fetch', async () => {
|
||||
fetches++;
|
||||
throw new Error('Unexpected fallback acquisition');
|
||||
});
|
||||
const actual = catalog(createStandaloneLayerSources({ military: null }), t);
|
||||
const registry = actual.militaryRegistry;
|
||||
assert.equal(actual.getSourceAvailability('flights').available, true);
|
||||
assert.equal(registry.isMilitaryLayerActive(), false);
|
||||
assert.equal(registry.isMilitaryIcao('abc123'), false);
|
||||
registry.registerMilitaryIcaos([' ABC123 ']);
|
||||
await registry.refreshMilitaryRegistryIfStale();
|
||||
assert.equal(registry.isMilitaryIcao('abc123'), true);
|
||||
assert.equal(registry.isMilitaryIcao('def456'), false);
|
||||
const manager = new LayerLifecycle(
|
||||
{},
|
||||
{ getSourceAvailability: actual.getSourceAvailability },
|
||||
);
|
||||
manager.register(actual.get('military'));
|
||||
assert.equal(await manager.setEnabled('military', true), false);
|
||||
assert.equal(registry.isMilitaryLayerActive(), false);
|
||||
assert.equal(fetches, 0);
|
||||
});
|
||||
Reference in new issue
Block a user