mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Share copied test fixtures through src/testSupport
Environment overrides, manual timers, a cancellable streaming response, GhostMaps KML/KMZ fixtures and the satellite service doubles move into five shared helpers; 21 tests drop 418 lines of copies.
This commit is contained in:
1 parent
6c416f3fe6
commit
df02033465
26 files changed
+167
-418
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { streamingResponse } from '../testSupport/streamingResponse.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
@@ -19,24 +20,6 @@ import { allocateSourceCap } from '../../server/providers/cctv/cap.js';
|
||||
import { createCctvCatalog } from '../../server/providers/cctv/catalog.js';
|
||||
import { directionToHeading } from './directionText.js';
|
||||
|
||||
/**
|
||||
* A response whose body is a live stream, plus a flag that flips when the
|
||||
* stream is cancelled. A rejection path that returns without cancelling holds
|
||||
* the transport open, so the flag is what the refusal tests actually assert.
|
||||
*/
|
||||
const streamingResponse = (init = {}) => {
|
||||
const state = { cancelled: false };
|
||||
const body = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode('['));
|
||||
},
|
||||
cancel() {
|
||||
state.cancelled = true;
|
||||
},
|
||||
});
|
||||
return { response: new Response(body, init), state };
|
||||
};
|
||||
|
||||
/** One Open Calgary row, shaped like the live `k7p9-kppz` payload. */
|
||||
const row = (overrides = {}) => ({
|
||||
camera_url: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -17,17 +18,6 @@ import {
|
||||
} from '../../server/providers/cctv/sources.js';
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence the loaders' progress and failure logging. */
|
||||
function quiet(t) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -28,17 +29,6 @@ import {
|
||||
} from '../../server/providers/cctv/sources.js';
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence the loaders' progress and failure logging. */
|
||||
function quiet(t) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -19,17 +20,6 @@ import {
|
||||
} from '../../server/providers/cctv/sources.js';
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** One row shaped like the live `vefmyndavelar2014_1` payload. */
|
||||
const row = (over = {}) => ({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
@@ -14,17 +15,6 @@ import { cctvProxy } from '../../server/providers/cctv.js';
|
||||
import { createCatalog } from '../layers/cctv/catalog.js';
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence the loaders' progress and failure logging. */
|
||||
function quiet(t) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
@@ -22,17 +23,6 @@ import { createModel } from '../layers/cctv/model.js';
|
||||
const ABSENT = [null, undefined, '', ' ', false, true];
|
||||
|
||||
/** Set env vars for one test and restore them afterwards. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence the loaders' progress and failure logging. */
|
||||
function quiet(t) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { streamingResponse } from '../testSupport/streamingResponse.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
@@ -22,24 +23,6 @@ import {
|
||||
} from '../../server/providers/cctv/normalize.js';
|
||||
import { createCctvCatalog } from '../../server/providers/cctv/catalog.js';
|
||||
|
||||
/**
|
||||
* A response whose body is a live stream, plus a flag that flips when the
|
||||
* stream is cancelled. A rejection path that returns without cancelling holds
|
||||
* the transport open, so the flag is what the refusal tests actually assert.
|
||||
*/
|
||||
const streamingResponse = (init = {}) => {
|
||||
const state = { cancelled: false };
|
||||
const body = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode('['));
|
||||
},
|
||||
cancel() {
|
||||
state.cancelled = true;
|
||||
},
|
||||
});
|
||||
return { response: new Response(body, init), state };
|
||||
};
|
||||
|
||||
const MIDTOWN_ID = '8a6bc417-4877-4ebe-8052-88c1b261baf1';
|
||||
const BROOKLYN_ID = '11111111-2222-3333-4444-555555555555';
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -21,17 +22,6 @@ import {
|
||||
const CERT = { OTTAWA_CAMERA_CERTIFICATE: 'CERT123' };
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence the loader's progress and failure logging. */
|
||||
function quiet(t) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -29,17 +30,6 @@ function quiet(t) {
|
||||
}
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** A temporary source root holding the given World and Québec catalogs. */
|
||||
function sourceRoot(t, { world = [], quebec = null } = {}) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { withEnv } from '../testSupport/environment.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -15,17 +16,6 @@ import {
|
||||
} from '../../server/providers/cctv/sources.js';
|
||||
|
||||
/** Set (or, for `undefined`, delete) environment variables for one test. */
|
||||
function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence the loaders' progress and failure logging. */
|
||||
function quiet(t) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import './testSupport/stubMgrs.mjs';
|
||||
// The HUD's ALT readout datum. Field report (2026-08-22, cockpit parked at
|
||||
// SFO): the bottom-right OSD read "ALT: -15M" — and JFK, earlier, "ALT: -18M".
|
||||
// Cesium reports the camera's height against the WGS84 ELLIPSOID, and San
|
||||
@@ -25,27 +26,8 @@ import { createFakeEngine } from './globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import { hudReadouts } from './testSupport/hudReadouts.mjs';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { registerHooks } from 'node:module';
|
||||
import { ensureGeoidReady } from './data/geoid.js';
|
||||
|
||||
const MGRS_STUB_URL = 'gev-test-stub:mgrs';
|
||||
registerHooks({
|
||||
resolve(specifier, context, next) {
|
||||
if (specifier === 'mgrs') return { url: MGRS_STUB_URL, shortCircuit: true };
|
||||
return next(specifier, context);
|
||||
},
|
||||
load(url, context, next) {
|
||||
if (url === MGRS_STUB_URL) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source:
|
||||
'export function forward() { return "10SEG55776339"; }\nexport default { forward };\n',
|
||||
};
|
||||
}
|
||||
return next(url, context);
|
||||
},
|
||||
});
|
||||
|
||||
const { IntelHUD } = await import('./hud.js');
|
||||
|
||||
|
||||
+1
-20
@@ -1,3 +1,4 @@
|
||||
import './testSupport/stubMgrs.mjs';
|
||||
import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
// The HUD's LAT/LON readout in degrees-minutes-seconds. Seconds are printed to
|
||||
// two decimals, so a value within 0.005" of the next whole minute rounds up.
|
||||
@@ -11,26 +12,6 @@ import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { hudReadouts } from './testSupport/hudReadouts.mjs';
|
||||
import { registerHooks } from 'node:module';
|
||||
|
||||
const MGRS_STUB_URL = 'gev-test-stub:mgrs';
|
||||
registerHooks({
|
||||
resolve(specifier, context, next) {
|
||||
if (specifier === 'mgrs') return { url: MGRS_STUB_URL, shortCircuit: true };
|
||||
return next(specifier, context);
|
||||
},
|
||||
load(url, context, next) {
|
||||
if (url === MGRS_STUB_URL) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source:
|
||||
'export function forward() { return "10SEG55776339"; }\nexport default { forward };\n',
|
||||
};
|
||||
}
|
||||
return next(url, context);
|
||||
},
|
||||
});
|
||||
|
||||
const { IntelHUD } = await import('./hud.js');
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import './testSupport/stubMgrs.mjs';
|
||||
import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
// The HUD's 250 ms telemetry readout skips ticks in a hidden page, except in
|
||||
// an inline panel, whose host may report it hidden while it is on screen.
|
||||
@@ -8,25 +9,7 @@ import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { hudReadouts } from './testSupport/hudReadouts.mjs';
|
||||
import { registerHooks } from 'node:module';
|
||||
|
||||
const MGRS_STUB_URL = 'gev-test-stub:mgrs';
|
||||
registerHooks({
|
||||
resolve(specifier, context, next) {
|
||||
if (specifier === 'mgrs') return { url: MGRS_STUB_URL, shortCircuit: true };
|
||||
return next(specifier, context);
|
||||
},
|
||||
load(url, context, next) {
|
||||
if (url === MGRS_STUB_URL) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source: 'export function forward() { return "10SEG55776339"; }\nexport default { forward };\n',
|
||||
};
|
||||
}
|
||||
return next(url, context);
|
||||
},
|
||||
});
|
||||
|
||||
const { IntelHUD } = await import('./hud.js');
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import './testSupport/stubMgrs.mjs';
|
||||
import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
// The HUD's place lookups are paid Google requests made only to feed the AI
|
||||
// summary, so a server without one must not be asked to make them.
|
||||
@@ -8,26 +9,6 @@ import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { hudReadouts } from './testSupport/hudReadouts.mjs';
|
||||
import { registerHooks } from 'node:module';
|
||||
|
||||
const MGRS_STUB_URL = 'gev-test-stub:mgrs';
|
||||
registerHooks({
|
||||
resolve(specifier, context, next) {
|
||||
if (specifier === 'mgrs') return { url: MGRS_STUB_URL, shortCircuit: true };
|
||||
return next(specifier, context);
|
||||
},
|
||||
load(url, context, next) {
|
||||
if (url === MGRS_STUB_URL) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source:
|
||||
'export function forward() { return "10SEG55776339"; }\nexport default { forward };\n',
|
||||
};
|
||||
}
|
||||
return next(url, context);
|
||||
},
|
||||
});
|
||||
|
||||
const { IntelHUD } = await import('./hud.js');
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
element,
|
||||
kmz,
|
||||
deferred,
|
||||
} from '../../testSupport/ghostMapsFixtures.mjs';
|
||||
// Edge cases for s2-ghostmaps:
|
||||
// change the KMZ edition and the engine before parsing finishes; only the
|
||||
// selected edition is shown, and a bad archive does not erase the working one.
|
||||
@@ -5,25 +10,6 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createGhostMapsLayer } from './index.js';
|
||||
|
||||
function element(localName, children = [], value = '') {
|
||||
const node = {
|
||||
localName,
|
||||
children,
|
||||
get textContent() {
|
||||
return value + children.map((child) => child.textContent).join('');
|
||||
},
|
||||
getAttribute: () => null,
|
||||
getElementsByTagName(name) {
|
||||
return children.flatMap((child) => [
|
||||
...(name === '*' || name === child.localName ? [child] : []),
|
||||
...child.getElementsByTagName(name),
|
||||
]);
|
||||
},
|
||||
};
|
||||
for (const child of children) child.parentElement = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
// The XML double turns `<kml>NAME</kml>` into one placemark called NAME.
|
||||
function documentFor(xml) {
|
||||
const name = /<kml>(.*)<\/kml>/.exec(xml)?.[1] ?? 'unnamed';
|
||||
@@ -39,37 +25,6 @@ function documentFor(xml) {
|
||||
]);
|
||||
}
|
||||
|
||||
/** A stored (uncompressed) KMZ holding doc.kml only. */
|
||||
function kmz(name) {
|
||||
const key = Buffer.from('doc.kml');
|
||||
const bytes = Buffer.from(`<kml>${name}</kml>`);
|
||||
const header = Buffer.alloc(30);
|
||||
header.writeUInt32LE(0x04034b50);
|
||||
header.writeUInt32LE(bytes.length, 18);
|
||||
header.writeUInt32LE(bytes.length, 22);
|
||||
header.writeUInt16LE(key.length, 26);
|
||||
const directory = Buffer.alloc(46);
|
||||
directory.writeUInt32LE(0x02014b50);
|
||||
directory.writeUInt32LE(bytes.length, 20);
|
||||
directory.writeUInt32LE(bytes.length, 24);
|
||||
directory.writeUInt16LE(key.length, 28);
|
||||
directory.writeUInt32LE(0, 42);
|
||||
const central = Buffer.concat([directory, key]);
|
||||
const end = Buffer.alloc(22);
|
||||
end.writeUInt32LE(0x06054b50);
|
||||
end.writeUInt16LE(1, 8);
|
||||
end.writeUInt16LE(1, 10);
|
||||
end.writeUInt32LE(central.length, 12);
|
||||
end.writeUInt32LE(header.length + key.length + bytes.length, 16);
|
||||
return new Uint8Array(Buffer.concat([header, key, bytes, central, end]));
|
||||
}
|
||||
|
||||
function deferred() {
|
||||
let resolve;
|
||||
const promise = new Promise((done) => (resolve = done));
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
const catalog = (version) => ({
|
||||
products: [{ id: 'cip', version, defaultOn: true }],
|
||||
});
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
import {
|
||||
element,
|
||||
kmz,
|
||||
deferred,
|
||||
} from '../../testSupport/ghostMapsFixtures.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createGhostMapsLayer } from './index.js';
|
||||
|
||||
function element(localName, children = [], value = '') {
|
||||
const node = {
|
||||
localName,
|
||||
children,
|
||||
get textContent() {
|
||||
return value + children.map((child) => child.textContent).join('');
|
||||
},
|
||||
getAttribute: () => null,
|
||||
getElementsByTagName(name) {
|
||||
return children.flatMap((child) => [
|
||||
...(name === '*' || name === child.localName ? [child] : []),
|
||||
...child.getElementsByTagName(name),
|
||||
]);
|
||||
},
|
||||
};
|
||||
for (const child of children) child.parentElement = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
// The XML double turns `<kml>NAME</kml>` into one placemark called NAME.
|
||||
function documentFor(xml) {
|
||||
const name = /<kml>(.*)<\/kml>/.exec(xml)?.[1] ?? 'unnamed';
|
||||
@@ -41,37 +27,6 @@ function documentFor(xml) {
|
||||
]);
|
||||
}
|
||||
|
||||
/** A stored (uncompressed) KMZ holding doc.kml only. */
|
||||
function kmz(name) {
|
||||
const key = Buffer.from('doc.kml');
|
||||
const bytes = Buffer.from(`<kml>${name}</kml>`);
|
||||
const header = Buffer.alloc(30);
|
||||
header.writeUInt32LE(0x04034b50);
|
||||
header.writeUInt32LE(bytes.length, 18);
|
||||
header.writeUInt32LE(bytes.length, 22);
|
||||
header.writeUInt16LE(key.length, 26);
|
||||
const directory = Buffer.alloc(46);
|
||||
directory.writeUInt32LE(0x02014b50);
|
||||
directory.writeUInt32LE(bytes.length, 20);
|
||||
directory.writeUInt32LE(bytes.length, 24);
|
||||
directory.writeUInt16LE(key.length, 28);
|
||||
directory.writeUInt32LE(0, 42);
|
||||
const central = Buffer.concat([directory, key]);
|
||||
const end = Buffer.alloc(22);
|
||||
end.writeUInt32LE(0x06054b50);
|
||||
end.writeUInt16LE(1, 8);
|
||||
end.writeUInt16LE(1, 10);
|
||||
end.writeUInt32LE(central.length, 12);
|
||||
end.writeUInt32LE(header.length + key.length + bytes.length, 16);
|
||||
return new Uint8Array(Buffer.concat([header, key, bytes, central, end]));
|
||||
}
|
||||
|
||||
function deferred() {
|
||||
let resolve;
|
||||
const promise = new Promise((done) => (resolve = done));
|
||||
return { promise, resolve };
|
||||
}
|
||||
|
||||
const catalog = (version) => ({
|
||||
products: [{ id: 'cip', version, defaultOn: true }],
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import * as geo from '../../geo/index.js';
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import { createSatellitesDisplay } from './render/display.js';
|
||||
import { satelliteServices } from '../../testSupport/satelliteServices.mjs';
|
||||
import { constantRgba } from '../shared/color.js';
|
||||
import { createState } from './state.js';
|
||||
import { createControls } from './controls.js';
|
||||
@@ -98,31 +98,9 @@ async function fixture(t) {
|
||||
readSensors: async () => SENSORS,
|
||||
};
|
||||
const noop = () => {};
|
||||
const services = {
|
||||
renderer: { create: ({ engine }) => createSatellitesDisplay(engine) },
|
||||
overlays: {
|
||||
setEntries: noop,
|
||||
setVisible: noop,
|
||||
clearSource: noop,
|
||||
},
|
||||
layerState: { isExplicitLayerStateOrigin: () => false },
|
||||
focus: {
|
||||
clearFocusTarget: noop,
|
||||
publishFocusTargetFromCachedPosition: noop,
|
||||
getFocusTarget: () => null,
|
||||
focusNowMs: (now) => now,
|
||||
focusPassIsNeeded: () => false,
|
||||
},
|
||||
context: {
|
||||
clearTrackedSubjectContext: noop,
|
||||
getContextStore: () => ({ entities: new Map() }),
|
||||
refreshTrackedSubjectContext: noop,
|
||||
selectTrackedSubjectContext: noop,
|
||||
},
|
||||
readout: { refreshTrackedReadout: noop },
|
||||
render: { holdContinuousRender: noop, releaseContinuousRender: noop },
|
||||
picking: { registerPickOwner: noop, unregisterPickOwner: noop },
|
||||
};
|
||||
const services = satelliteServices();
|
||||
services.focus.focusNowMs = (now) => now;
|
||||
services.focus.focusPassIsNeeded = () => false;
|
||||
const previousDocument = globalThis.document;
|
||||
globalThis.document = new EventTarget();
|
||||
const state = createState({ services });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createSatellitesDisplay } from './render/display.js';
|
||||
import { satelliteServices } from '../../testSupport/satelliteServices.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import http from 'node:http';
|
||||
import { EventEmitter, once } from 'node:events';
|
||||
@@ -76,32 +76,7 @@ async function fixture(t) {
|
||||
// Application host services and native EventTargets; the display on the
|
||||
// fake engine, its input, events, SGP4, ingestion and lifecycle run without
|
||||
// a WebGL scene.
|
||||
const noop = () => {};
|
||||
const services = {
|
||||
renderer: {
|
||||
create: ({ engine }) => createSatellitesDisplay(engine),
|
||||
},
|
||||
overlays: {
|
||||
setEntries: noop,
|
||||
setVisible: noop,
|
||||
clearSource: noop,
|
||||
},
|
||||
layerState: { isExplicitLayerStateOrigin: () => false },
|
||||
focus: {
|
||||
clearFocusTarget: noop,
|
||||
publishFocusTargetFromCachedPosition: noop,
|
||||
getFocusTarget: () => null,
|
||||
},
|
||||
context: {
|
||||
clearTrackedSubjectContext: noop,
|
||||
getContextStore: () => ({ entities: new Map() }),
|
||||
refreshTrackedSubjectContext: noop,
|
||||
selectTrackedSubjectContext: noop,
|
||||
},
|
||||
readout: { refreshTrackedReadout: noop },
|
||||
render: { holdContinuousRender: noop, releaseContinuousRender: noop },
|
||||
picking: { registerPickOwner: noop, unregisterPickOwner: noop },
|
||||
};
|
||||
const services = satelliteServices();
|
||||
const previousDocument = globalThis.document;
|
||||
globalThis.document = new EventTarget();
|
||||
const state = createState({ services });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { installManualTimers } from './testSupport/manualTimers.mjs';
|
||||
import { viewerEngine } from './testSupport/viewerEngine.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -72,23 +73,6 @@ function installTab(t, initialHash = '') {
|
||||
};
|
||||
}
|
||||
|
||||
/** Queue timers instead of running them, so delayed work runs only on demand. */
|
||||
function installManualTimers(t) {
|
||||
const prior = {
|
||||
setTimeout: globalThis.setTimeout,
|
||||
clearTimeout: globalThis.clearTimeout,
|
||||
};
|
||||
const timers = new Map();
|
||||
let timerId = 0;
|
||||
globalThis.setTimeout = (fn) => {
|
||||
timers.set(++timerId, fn);
|
||||
return timerId;
|
||||
};
|
||||
globalThis.clearTimeout = (id) => timers.delete(id);
|
||||
t.after(() => Object.assign(globalThis, prior));
|
||||
return timers;
|
||||
}
|
||||
|
||||
function fakeViewer() {
|
||||
return viewerEngine({
|
||||
camera: {
|
||||
@@ -175,7 +159,7 @@ test('a non-share hash is ignored and Back/Forward restores each entry once', (t
|
||||
|
||||
test('a newer navigation supersedes an older restore and its pending write', (t) => {
|
||||
const tab = installTab(t, SHARE_A);
|
||||
const timers = installManualTimers(t);
|
||||
const timers = installManualTimers(t).timers;
|
||||
|
||||
const manager = new ShareLinkManager(fakeViewer());
|
||||
manager.completeInitialRestore(manager.parseInitialHash().restoreToken);
|
||||
@@ -237,7 +221,7 @@ function deferred() {
|
||||
/** A ShareRestoration over a real ShareLinkManager with a stub restore sink. */
|
||||
function restorationHarness(t, initialHash, navigationOverrides = {}) {
|
||||
const tab = installTab(t, initialHash);
|
||||
const timers = installManualTimers(t);
|
||||
const timers = installManualTimers(t).timers;
|
||||
|
||||
const restores = [];
|
||||
let navigationGeneration = 0;
|
||||
@@ -427,7 +411,7 @@ test('the layer coordinator replaces in-flight share layers with the navigated s
|
||||
|
||||
test('a manager held for an engine-changing visit neither stamps nor restores that entry', (t) => {
|
||||
const tab = installTab(t, SHARE_A);
|
||||
const timers = installManualTimers(t);
|
||||
const timers = installManualTimers(t).timers;
|
||||
const manager = new ShareLinkManager(fakeViewer());
|
||||
manager.completeInitialRestore(manager.parseInitialHash().restoreToken);
|
||||
// The page's own popstate listener runs first (src/main.js): the visit
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export function withEnv(t, env) {
|
||||
for (const [name, value] of Object.entries(env)) {
|
||||
const previous = process.env[name];
|
||||
t.after(() => {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
});
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
export function element(localName, children = [], value = '') {
|
||||
const node = {
|
||||
localName,
|
||||
children,
|
||||
get textContent() {
|
||||
return value + children.map((child) => child.textContent).join('');
|
||||
},
|
||||
getAttribute: () => null,
|
||||
getElementsByTagName(name) {
|
||||
return children.flatMap((child) => [
|
||||
...(name === '*' || name === child.localName ? [child] : []),
|
||||
...child.getElementsByTagName(name),
|
||||
]);
|
||||
},
|
||||
};
|
||||
for (const child of children) child.parentElement = node;
|
||||
return node;
|
||||
}
|
||||
|
||||
/** A stored (uncompressed) KMZ holding doc.kml only. */
|
||||
export function kmz(name) {
|
||||
const key = Buffer.from('doc.kml');
|
||||
const bytes = Buffer.from(`<kml>${name}</kml>`);
|
||||
const header = Buffer.alloc(30);
|
||||
header.writeUInt32LE(0x04034b50);
|
||||
header.writeUInt32LE(bytes.length, 18);
|
||||
header.writeUInt32LE(bytes.length, 22);
|
||||
header.writeUInt16LE(key.length, 26);
|
||||
const directory = Buffer.alloc(46);
|
||||
directory.writeUInt32LE(0x02014b50);
|
||||
directory.writeUInt32LE(bytes.length, 20);
|
||||
directory.writeUInt32LE(bytes.length, 24);
|
||||
directory.writeUInt16LE(key.length, 28);
|
||||
directory.writeUInt32LE(0, 42);
|
||||
const central = Buffer.concat([directory, key]);
|
||||
const end = Buffer.alloc(22);
|
||||
end.writeUInt32LE(0x06054b50);
|
||||
end.writeUInt16LE(1, 8);
|
||||
end.writeUInt16LE(1, 10);
|
||||
end.writeUInt32LE(central.length, 12);
|
||||
end.writeUInt32LE(header.length + key.length + bytes.length, 16);
|
||||
return new Uint8Array(Buffer.concat([header, key, bytes, central, end]));
|
||||
}
|
||||
|
||||
export function deferred() {
|
||||
let resolve;
|
||||
const promise = new Promise((done) => (resolve = done));
|
||||
return { promise, resolve };
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/** Queue timers instead of running them, so delayed work runs only on demand. */
|
||||
export function installManualTimers(t) {
|
||||
const prior = {
|
||||
setTimeout: globalThis.setTimeout,
|
||||
clearTimeout: globalThis.clearTimeout,
|
||||
};
|
||||
const timers = new Map();
|
||||
let timerId = 0;
|
||||
globalThis.setTimeout = (fn) => {
|
||||
timers.set(++timerId, fn);
|
||||
return timerId;
|
||||
};
|
||||
globalThis.clearTimeout = (id) => timers.delete(id);
|
||||
t.after(() => Object.assign(globalThis, prior));
|
||||
return {
|
||||
timers,
|
||||
runAll() {
|
||||
for (const [id, fn] of [...timers]) {
|
||||
timers.delete(id);
|
||||
fn();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { createSatellitesDisplay } from '../layers/satellites/render/display.js';
|
||||
|
||||
export function satelliteServices() {
|
||||
const noop = () => {};
|
||||
return {
|
||||
renderer: {
|
||||
create: ({ engine }) => createSatellitesDisplay(engine),
|
||||
},
|
||||
overlays: {
|
||||
setEntries: noop,
|
||||
setVisible: noop,
|
||||
clearSource: noop,
|
||||
},
|
||||
layerState: { isExplicitLayerStateOrigin: () => false },
|
||||
focus: {
|
||||
clearFocusTarget: noop,
|
||||
publishFocusTargetFromCachedPosition: noop,
|
||||
getFocusTarget: () => null,
|
||||
},
|
||||
context: {
|
||||
clearTrackedSubjectContext: noop,
|
||||
getContextStore: () => ({ entities: new Map() }),
|
||||
refreshTrackedSubjectContext: noop,
|
||||
selectTrackedSubjectContext: noop,
|
||||
},
|
||||
readout: { refreshTrackedReadout: noop },
|
||||
render: { holdContinuousRender: noop, releaseContinuousRender: noop },
|
||||
picking: { registerPickOwner: noop, unregisterPickOwner: noop },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* A response whose body is a live stream, plus a flag that flips when the
|
||||
* stream is cancelled. A rejection path that returns without cancelling holds
|
||||
* the transport open, so the flag is what the refusal tests actually assert.
|
||||
*/
|
||||
export const streamingResponse = (init = {}) => {
|
||||
const state = { cancelled: false };
|
||||
const body = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode('['));
|
||||
},
|
||||
cancel() {
|
||||
state.cancelled = true;
|
||||
},
|
||||
});
|
||||
return { response: new Response(body, init), state };
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { installManualTimers } from '../testSupport/manualTimers.mjs';
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { createCesiumMoversEngine } from '../testSupport/cesiumMoversEngine.mjs';
|
||||
import { enuOffset } from '../globe/follow.js';
|
||||
@@ -77,30 +78,6 @@ function installPage(t, href) {
|
||||
return { location, writes, target };
|
||||
}
|
||||
|
||||
function installManualTimers(t) {
|
||||
const prior = {
|
||||
setTimeout: globalThis.setTimeout,
|
||||
clearTimeout: globalThis.clearTimeout,
|
||||
};
|
||||
const timers = new Map();
|
||||
let timerId = 0;
|
||||
globalThis.setTimeout = (fn) => {
|
||||
timers.set(++timerId, fn);
|
||||
return timerId;
|
||||
};
|
||||
globalThis.clearTimeout = (id) => timers.delete(id);
|
||||
t.after(() => Object.assign(globalThis, prior));
|
||||
return {
|
||||
timers,
|
||||
runAll() {
|
||||
for (const [id, fn] of [...timers]) {
|
||||
timers.delete(id);
|
||||
fn();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** A viewer whose camera flights land at once. */
|
||||
function fakeViewer() {
|
||||
const flights = [];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import '../../testSupport/stubMgrs.mjs';
|
||||
import { viewerEngine } from '../../testSupport/viewerEngine.mjs';
|
||||
// src/ui/views/intelHudView.test.mjs — the Intel HUD view renders what
|
||||
// IntelHUD publishes: its readouts, visibility, layout variant and colors.
|
||||
@@ -10,30 +11,10 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createApp, nextTick } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { registerHooks } from 'node:module';
|
||||
import { presentSelectedVessel } from '../../hudReadouts.js';
|
||||
import { useHudStore } from '../stores/hud.js';
|
||||
import IntelHudView from './IntelHudView.vue';
|
||||
|
||||
const MGRS_STUB_URL = 'gev-test-stub:mgrs';
|
||||
registerHooks({
|
||||
resolve(specifier, context, next) {
|
||||
if (specifier === 'mgrs') return { url: MGRS_STUB_URL, shortCircuit: true };
|
||||
return next(specifier, context);
|
||||
},
|
||||
load(url, context, next) {
|
||||
if (url === MGRS_STUB_URL) {
|
||||
return {
|
||||
format: 'module',
|
||||
shortCircuit: true,
|
||||
source:
|
||||
'export function forward() { return "10SEG55776339"; }\nexport default { forward };\n',
|
||||
};
|
||||
}
|
||||
return next(url, context);
|
||||
},
|
||||
});
|
||||
|
||||
const { IntelHUD } = await import('../../hud.js');
|
||||
|
||||
const listeners = () => ({ addEventListener() {}, removeEventListener() {} });
|
||||
|
||||
Reference in new issue
Block a user