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:
German Khokhlov committed 2026-11-10 16:43:58 +04:00
1 parent 6c416f3fe6
commit df02033465
26 files changed
+167 -418

No files matched your search

+1 -18
View File
@@ -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 -11
View File
@@ -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 -11
View File
@@ -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 -11
View File
@@ -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 -11
View File
@@ -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 -11
View File
@@ -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 -18
View File
@@ -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 -11
View File
@@ -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 -11
View File
@@ -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 -11
View File
@@ -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 -19
View File
@@ -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
View File
@@ -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 -18
View File
@@ -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 -20
View File
@@ -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');
+5 -50
View File
@@ -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 }],
});
+5 -50
View File
@@ -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 }],
});
+4 -26
View File
@@ -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 });
+2 -27
View File
@@ -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 });
+4 -20
View File
@@ -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
+11
View File
@@ -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;
}
}
+49
View File
@@ -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 };
}
+24
View File
@@ -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();
}
},
};
}
+30
View File
@@ -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 },
};
}
+17
View File
@@ -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 -24
View File
@@ -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 -20
View File
@@ -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() {} });