gods-eye-view/src/data/manager.test.mjs

3101 lines
119 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Re-entrancy contract for DataLayerManager.toggle() (audit M1 ⊗).
//
// The bug: toggle() awaits the layer's init() + first update() before arming the
// polling interval. A second toggle during that window used to interleave — the
// disable branch ran mid-enable, the interval was armed AFTER the user turned the
// layer off, and a subsequent enable armed a SECOND interval → 2× poll → OpenSky
// 429. The fix serializes toggles per-entry and re-checks enabled after the awaits.
//
// Pure test: the manager only calls the layer module's lifecycle methods and (when
// a toggle container is present) DOM refresh. We pass no container, so it stays
// headless. Run with: npm test
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { DataLayerManager, layerFeedState } from './manager.js';
import {
contextSnapshotLayerIds,
shouldCaptureContextSession,
} from '../contextModePolicy.js';
/** Build a mock layer whose init/update resolve on the next microtask, so a
* second toggle can land while the first is awaiting. */
function makeSlowLayer(id, { updateInterval = 1000 } = {}) {
const calls = { enable: 0, disable: 0, update: 0, init: 0, presentation: [] };
return {
calls,
module: {
id,
name: id,
icon: '',
source: 'test',
updateInterval,
async init() { calls.init++; await Promise.resolve(); },
enable() { calls.enable++; },
disable() { calls.disable++; },
async update() { calls.update++; await Promise.resolve(); },
setLifecyclePresentation(state) { calls.presentation.push({ ...state }); },
getStats() { return { count: 0, lastUpdate: null }; },
},
};
}
test('keeps panel-hidden coordinator layers registered and addressable', () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('military-awareness', { updateInterval: -1 });
layer.module.showInTogglePanel = false;
mgr.register(layer.module);
assert.deepEqual(mgr.getAll().map(({ id, showInTogglePanel }) => ({ id, showInTogglePanel })), [
{ id: 'military-awareness', showInTogglePanel: false },
]);
assert.equal(mgr.isEnabled('military-awareness'), false);
});
test('adopts direct layer params without re-entering the layer setter', () => {
let params = { selectedFlightsTrackingId: 'flight-a' };
let setterCalls = 0;
const manager = new DataLayerManager({});
manager.register({
id: 'flights', name: 'Flights', icon: '', source: 'test',
setParams() { setterCalls += 1; return true; },
getParams() { return { ...params }; },
});
const events = [];
manager.subscribe((event) => events.push(event));
assert.equal(manager.adoptLayerParams('flights', {
selectedFlightsTrackingId: 'flight-a',
}, { origin: 'user' }), true);
assert.equal(setterCalls, 0);
assert.equal(events.at(-1)?.type, 'params');
assert.equal(events.at(-1)?.params.selectedFlightsTrackingId, 'flight-a');
params = { selectedFlightsTrackingId: 'flight-b' };
assert.equal(manager.adoptLayerParams('flights', {
selectedFlightsTrackingId: 'flight-a',
}, { origin: 'user' }), false, 'changed live params reject stale adoption');
});
test('adopts settled visibility without re-running lifecycle work', async () => {
const manager = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
manager.register(layer.module);
await manager.setEnabled('flights', true, { origin: 'programmatic' });
const events = [];
manager.subscribe((event) => events.push(event));
assert.equal(manager.adoptLayerVisibility('flights', true, {
origin: 'user',
adoptedFromSelection: true,
}), true);
assert.equal(layer.calls.enable, 1);
assert.equal(events.at(-1)?.type, 'visibility');
assert.equal(events.at(-1)?.adoptedFromSelection, true);
assert.equal(manager.adoptLayerVisibility('flights', false, { origin: 'user' }), false);
});
test('renders ordinary layer rows without recreating a panel-hidden coordinator', async () => {
const originalDocument = globalThis.document;
const makeElement = () => {
const element = {
children: [],
className: '',
dataset: {},
textContent: '',
disabled: false,
attributes: {},
classList: {
toggle() {},
},
appendChild(child) { this.children.push(child); return child; },
addEventListener() {},
setAttribute(name, value) { this.attributes[name] = String(value); },
querySelector(selector) {
if (selector.startsWith('[data-layer-id="')) {
const id = selector.slice(16, -2);
return this.children.find((child) => child.dataset.layerId === id) || null;
}
const className = selector.startsWith('.') ? selector.slice(1) : '';
const visit = (node) => {
if (String(node.className).split(/\s+/).includes(className)) return node;
for (const child of node.children || []) {
const found = visit(child);
if (found) return found;
}
return null;
};
return visit(this);
},
set innerHTML(value) { if (value === '') this.children = []; },
get innerHTML() { return ''; },
};
return element;
};
globalThis.document = { createElement: makeElement };
const mgr = new DataLayerManager({});
const ordinary = makeSlowLayer('flights', { updateInterval: -1 });
const coordinator = makeSlowLayer('military-awareness', { updateInterval: -1 });
coordinator.module.showInTogglePanel = false;
mgr.register(ordinary.module);
mgr.register(coordinator.module);
const container = makeElement();
try {
mgr.buildTogglePanel(container);
assert.ok(container.querySelector('[data-layer-id="flights"]'));
assert.equal(container.querySelector('[data-layer-id="military-awareness"]'), null);
assert.equal(await mgr.setEnabled('military-awareness', true), true);
mgr._refreshTogglePanel();
assert.equal(container.querySelector('[data-layer-id="military-awareness"]'), null);
assert.equal(mgr.getAll().find(({ id }) => id === 'military-awareness').enabled, true);
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
test('clearSelectedLayers includes hidden coordinators and preserves newer dependency restoration', async () => {
const mgr = new DataLayerManager({});
const order = [];
const satellites = makeSlowLayer('satellites', { updateInterval: -1 });
const missions = makeSlowLayer('rocket-launches', { updateInterval: -1 });
const context = makeSlowLayer('military-awareness', { updateInterval: -1 });
context.module.showInTogglePanel = false;
satellites.module.disable = () => { order.push('satellites'); };
missions.module.disable = async () => {
order.push('rocket-launches');
await mgr.setEnabled('satellites', true, { origin: 'dependency-restore' });
};
context.module.disable = () => { order.push('military-awareness'); };
for (const layer of [satellites, missions, context]) mgr.register(layer.module);
await mgr.setEnabled('satellites', true);
await mgr.setEnabled('rocket-launches', true);
await mgr.setEnabled('military-awareness', true);
const result = await mgr.clearSelectedLayers({ origin: 'user' });
assert.deepEqual(result.targetIds, [
'military-awareness',
'rocket-launches',
'satellites',
]);
assert.deepEqual(order, ['military-awareness', 'rocket-launches']);
assert.deepEqual(result.clearedIds, ['military-awareness', 'rocket-launches']);
assert.deepEqual(result.notClearedIds, ['satellites']);
assert.equal(result.items.find(({ id }) => id === 'satellites')?.superseded, true);
assert.deepEqual([...mgr.getEnabledLayerIds()], ['satellites']);
await mgr.destroyAll();
});
test('clearSelectedLayers absorbs a hidden coordinator fire-and-forget dependency release', async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: -1 });
const context = makeSlowLayer('military-awareness', { updateInterval: -1 });
context.module.showInTogglePanel = false;
let releaseFinished;
const finished = new Promise((resolve) => { releaseFinished = resolve; });
context.module.disable = () => {
void mgr.setEnabled('flights', false, { origin: 'dependency-release' })
.finally(releaseFinished);
};
mgr.register(flights.module);
mgr.register(context.module);
await mgr.setEnabled('flights', true);
await mgr.setEnabled('military-awareness', true);
const result = await mgr.clearSelectedLayers({ origin: 'user' });
await finished;
assert.deepEqual(result.targetIds, ['military-awareness', 'flights']);
assert.deepEqual(result.notClearedIds, []);
assert.deepEqual([...mgr.getEnabledLayerIds()], []);
});
test('clearSelectedLayers continues after failures and reports final lifecycle truth', async () => {
const mgr = new DataLayerManager({});
const ordinary = makeSlowLayer('flights', { updateInterval: -1 });
const failing = makeSlowLayer('traffic', { updateInterval: -1 });
failing.module.disable = () => false;
mgr.register(ordinary.module);
mgr.register(failing.module);
await mgr.setEnabled('flights', true);
await mgr.setEnabled('traffic', true);
const result = await mgr.clearSelectedLayers();
assert.deepEqual(result.clearedIds, ['flights']);
assert.deepEqual(result.notClearedIds, ['traffic']);
const failure = result.items.find(({ id }) => id === 'traffic');
assert.equal(failure.enabled, true);
assert.equal(failure.lifecycleState, 'enabled');
assert.equal(failure.uncertain, true);
assert.equal(await mgr.setEnabled('traffic', false), false);
});
test('newer direct layer intent supersedes clearSelectedLayers without a blind retry', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseDisable;
let announceDisable;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
layer.module.disable = async () => {
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
mgr.register(layer.module);
await mgr.setEnabled('flights', true);
const clearing = mgr.clearSelectedLayers({ origin: 'user' });
await disableStarted;
const newerEnable = mgr.setEnabled('flights', true, { origin: 'voice' });
releaseDisable();
const result = await clearing;
await newerEnable;
assert.deepEqual(result.notClearedIds, ['flights']);
assert.equal(mgr.isEnabled('flights'), true);
assert.deepEqual(mgr.getLayerLifecycleState('flights'), {
enabled: true,
lifecycleState: 'enabled',
uncertain: false,
});
});
test('clearSelectedLayers does not issue a delayed OFF after a newer explicit ON', async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: -1 });
const blocker = makeSlowLayer('traffic', { updateInterval: -1 });
let releaseBlocker;
let announceBlocker;
const blockerStarted = new Promise((resolve) => { announceBlocker = resolve; });
blocker.module.disable = async () => {
announceBlocker();
await new Promise((resolve) => { releaseBlocker = resolve; });
};
mgr.register(flights.module);
mgr.register(blocker.module);
await mgr.setEnabled('flights', true);
await mgr.setEnabled('traffic', true);
const clearing = mgr.clearSelectedLayers({ origin: 'user' });
await blockerStarted;
await mgr.setEnabled('flights', true, { origin: 'voice' });
releaseBlocker();
const result = await clearing;
const flightsResult = result.items.find(({ id }) => id === 'flights');
assert.equal(flightsResult.superseded, true);
assert.equal(flightsResult.enabled, true);
assert.deepEqual(result.notClearedIds, ['flights']);
});
test('clearSelectedLayers skips delayed OFF for every newer absolute-intent origin', async (t) => {
for (const origin of ['user', 'voice', 'programmatic', 'dependency-restore', 'context-restore']) {
await t.test(origin, async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: -1 });
const blocker = makeSlowLayer('traffic', { updateInterval: -1 });
let releaseBlocker;
let announceBlocker;
const blockerStarted = new Promise((resolve) => { announceBlocker = resolve; });
blocker.module.disable = async () => {
announceBlocker();
await new Promise((resolve) => { releaseBlocker = resolve; });
};
mgr.register(flights.module);
mgr.register(blocker.module);
await mgr.setEnabled('flights', true);
await mgr.setEnabled('traffic', true);
const clearing = mgr.clearSelectedLayers({ origin: 'user' });
await blockerStarted;
await mgr.setEnabled('flights', true, { origin });
releaseBlocker();
const result = await clearing;
const flightsResult = result.items.find(({ id }) => id === 'flights');
assert.equal(flightsResult.superseded, true);
assert.equal(flightsResult.enabled, true);
assert.deepEqual(result.notClearedIds, ['flights']);
await mgr.destroyAll();
});
}
});
test('Clear All reserves its complete OFF baseline before sequential teardown', async () => {
const mgr = new DataLayerManager({});
const first = makeSlowLayer('flights', { updateInterval: -1 });
const blocker = makeSlowLayer('traffic', { updateInterval: -1 });
let releaseBlocker;
let markBlockerStarted;
const blockerStarted = new Promise((resolve) => { markBlockerStarted = resolve; });
blocker.module.disable = async () => {
markBlockerStarted();
await new Promise((resolve) => { releaseBlocker = resolve; });
};
mgr.register(first.module);
mgr.register(blocker.module);
await mgr.setEnabled('flights', true);
await mgr.setEnabled('traffic', true);
const clearing = mgr.clearSelectedLayers({ origin: 'user' });
await blockerStarted;
assert.deepEqual(
[...mgr.getEnabledLayerIds()],
[],
'all captured targets are effectively OFF before the first awaited teardown settles',
);
await mgr.setEnabled('flights', true, { origin: 'voice' });
assert.deepEqual(
[...mgr.getEnabledLayerIds()],
['flights'],
'a later layer intent supersedes only its own Clear reservation',
);
releaseBlocker();
const result = await clearing;
assert.equal(result.items.find(({ id }) => id === 'flights')?.superseded, true);
assert.equal(mgr.isEnabled('flights'), true);
await mgr.destroyAll();
});
test('a current queued request aborted before its turn publishes one exact cancellation', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 });
let releaseToggle;
let markToggleStarted;
const toggleStarted = new Promise((resolve) => { markToggleStarted = resolve; });
layer.module.enable = async () => {
markToggleStarted();
await new Promise((resolve) => { releaseToggle = resolve; });
};
mgr.register(layer.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const occupyingTurn = mgr.toggle('rocket-launches', { origin: 'programmatic' });
await toggleStarted;
const controller = new AbortController();
const queued = mgr._setEnabledWithIntent('rocket-launches', false, {
origin: 'voice',
signal: controller.signal,
});
controller.abort();
releaseToggle();
await occupyingTurn;
assert.equal(await queued.promise, false);
const outcome = await mgr._waitForVisibilityIntent('rocket-launches', queued.intentEpoch);
assert.equal(outcome.succeeded, false);
assert.equal(outcome.cancellationReason, 'caller-abort');
assert.equal(outcome.phase, 'queued');
const terminal = changes.filter(({ intentEpoch }) => intentEpoch === queued.intentEpoch);
assert.deepEqual(terminal.map(({ type }) => type), ['visibility-cancelled']);
await mgr.destroyAll();
});
test('visibility lifecycle events retain the exact absolute-intent epoch', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 });
const changes = [];
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
const request = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'voice' });
assert.equal(await request.promise, true);
const ownedChanges = changes.filter(({ layerId }) => layerId === 'rocket-launches');
assert.ok(ownedChanges.length >= 3);
assert.ok(
ownedChanges.every(({ intentEpoch }) => intentEpoch === request.intentEpoch),
'pre-transition, lifecycle, and settled events remain correlated to one accepted intent',
);
await mgr.destroyAll();
});
test('programmatic ON during Clear All active OFF owns final visibility and reporting', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseDisable;
let announceDisable;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
layer.module.disable = async () => {
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
mgr.register(layer.module);
await mgr.setEnabled('flights', true);
const clearing = mgr.clearSelectedLayers({ origin: 'user' });
await disableStarted;
const newerEnable = mgr.setEnabled('flights', true, { origin: 'programmatic' });
releaseDisable();
const result = await clearing;
await newerEnable;
const item = result.items.find(({ id }) => id === 'flights');
assert.equal(item.superseded, true);
assert.equal(item.cleared, false);
assert.equal(item.lifecycleState, 'enabling');
assert.deepEqual(result.notClearedIds, ['flights']);
assert.deepEqual(mgr.getLayerLifecycleState('flights'), {
enabled: true,
lifecycleState: 'enabled',
uncertain: false,
});
// The test-owned slow disable is intentionally left installed; a destroy
// would start a second unrelated gated disable and obscure this race.
});
test('manager exposes enabling and disabling without changing settled visibility early', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('radio', { updateInterval: -1 });
let releaseInit;
let announceInit;
let releaseDisable;
let announceDisable;
const initStarted = new Promise((resolve) => { announceInit = resolve; });
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
layer.module.init = async () => {
announceInit();
await new Promise((resolve) => { releaseInit = resolve; });
};
layer.module.disable = async () => {
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
mgr.register(layer.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const enabling = mgr.setEnabled('radio', true, { origin: 'user' });
await initStarted;
assert.deepEqual(mgr.getLayerLifecycleState('radio'), {
enabled: false,
lifecycleState: 'enabling',
uncertain: false,
});
assert.equal(mgr.getAll()[0].lifecycleState, 'enabling');
assert.equal(changes.at(-1)?.type, 'visibility-transition');
assert.equal(changes.at(-1)?.settledEnabled, false);
assert.deepEqual(layer.calls.presentation.at(-1), {
lifecycleState: 'enabling', enabled: false, uncertain: false,
});
releaseInit();
assert.equal(await enabling, true);
assert.deepEqual(mgr.getLayerLifecycleState('radio'), {
enabled: true,
lifecycleState: 'enabled',
uncertain: false,
});
assert.deepEqual(layer.calls.presentation.at(-1), {
lifecycleState: 'enabled', enabled: true, uncertain: false,
});
const disabling = mgr.setEnabled('radio', false, { origin: 'user' });
await disableStarted;
assert.deepEqual(mgr.getLayerLifecycleState('radio'), {
enabled: true,
lifecycleState: 'disabling',
uncertain: false,
});
assert.equal(changes.at(-1)?.type, 'visibility-transition');
assert.equal(changes.at(-1)?.settledEnabled, true);
assert.deepEqual(layer.calls.presentation.at(-1), {
lifecycleState: 'disabling', enabled: true, uncertain: false,
});
releaseDisable();
assert.equal(await disabling, true);
assert.deepEqual(mgr.getLayerLifecycleState('radio'), {
enabled: false,
lifecycleState: 'disabled',
uncertain: false,
});
assert.deepEqual(layer.calls.presentation.at(-1), {
lifecycleState: 'disabled', enabled: false, uncertain: false,
});
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility').map(({ enabled }) => enabled),
[true, false],
);
});
test('double-toggle during the awaited enable leaves the layer OFF with no leaked interval', async () => {
const mgr = new DataLayerManager(/* viewer */ {});
const layer = makeSlowLayer('flights');
mgr.register(layer.module);
// Fire two toggles back-to-back WITHOUT awaiting the first — the classic
// voice+click / double-click race.
const p1 = mgr.toggle('flights'); // enable
const p2 = mgr.toggle('flights'); // should be treated as the disable
await Promise.all([p1, p2]);
// Net effect of enable-then-disable: layer is OFF...
assert.equal(mgr.isEnabled('flights'), false, 'layer should end disabled');
// ...and NO interval is left running (the leak this fix prevents).
const entry = mgr.layers.get('flights');
assert.equal(entry.intervalId, null, 'no polling interval should be armed');
});
test('serialized toggles never arm two intervals (2× poll → 429 guard)', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('military');
mgr.register(layer.module);
// enable, disable, enable — rapid-fire, unawaited.
const ps = [mgr.toggle('military'), mgr.toggle('military'), mgr.toggle('military')];
await Promise.all(ps);
assert.equal(mgr.isEnabled('military'), true, 'odd number of toggles ends enabled');
const entry = mgr.layers.get('military');
assert.notEqual(entry.intervalId, null, 'exactly one interval should be armed');
// enable ran twice, disable once — and only one interval survives.
assert.ok(layer.calls.enable >= 1, 'enable was called');
clearInterval(entry.intervalId); // don't leak the timer out of the test
});
test('setEnabled is idempotent and serializes with toggle', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('satellites', { updateInterval: 0 });
mgr.register(layer.module);
await mgr.setEnabled('satellites', true);
assert.equal(mgr.isEnabled('satellites'), true);
await mgr.setEnabled('satellites', true); // no-op, already enabled
assert.equal(layer.calls.enable, 1, 'no redundant enable');
await mgr.setEnabled('satellites', false);
assert.equal(mgr.isEnabled('satellites'), false);
const entry = mgr.layers.get('satellites');
assert.equal(entry.intervalId, null, 'stats interval cleared on disable');
});
test('idempotent absolute intent publishes its newer origin without rerunning lifecycle', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 });
const changes = [];
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
await mgr.setEnabled('rocket-launches', true, { origin: 'programmatic' });
changes.length = 0;
assert.equal(await mgr.setEnabled('rocket-launches', true, { origin: 'voice' }), true);
assert.equal(layer.calls.enable, 1, 'the module is not redundantly enabled');
assert.deepEqual(changes, [{
type: 'visibility',
layerId: 'rocket-launches',
enabled: true,
origin: 'voice',
intentEpoch: 2,
}]);
await mgr.destroyAll();
});
test('an aborted enable is transactionally cancelled without a settled visibility intent', async () => {
const mgr = new DataLayerManager({});
const changes = [];
let releaseUpdate;
let announceUpdate;
let updateShouldWait = true;
let cleanupFails = true;
let moduleActive = false;
const updateStarted = new Promise((resolve) => { announceUpdate = resolve; });
const layer = makeSlowLayer('radio', { updateInterval: 0 });
layer.module.enable = () => {
layer.calls.enable++;
moduleActive = true;
};
layer.module.update = async () => {
layer.calls.update++;
if (!updateShouldWait) return;
updateShouldWait = false;
announceUpdate();
await new Promise((resolve) => { releaseUpdate = resolve; });
};
// Cleanup failure means the partially enabled module cannot be proven OFF;
// retain the conservative ON state without emitting settled visibility.
layer.module.disable = () => {
layer.calls.disable++;
moduleActive = false;
if (cleanupFails) throw new Error('cleanup fixture');
};
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
const controller = new AbortController();
const enabling = mgr.setEnabled('radio', true, {
origin: 'voice',
signal: controller.signal,
});
await updateStarted;
controller.abort();
releaseUpdate();
const changed = await enabling;
assert.equal(changed, false);
assert.equal(mgr.isEnabled('radio'), true);
assert.equal(moduleActive, false, 'module cleanup completed before reporting failure');
assert.equal(mgr.layers.get('radio').lifecycleUncertain, true);
assert.equal(mgr.layers.get('radio').intervalId, null);
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
assert.equal(changes.at(-1)?.type, 'visibility-failed');
assert.equal(changes.at(-1)?.phase, 'cancel-enable-cleanup');
cleanupFails = false;
const visibilityBeforeRetry = changes.filter(({ type }) => type === 'visibility').length;
assert.equal(await mgr.setEnabled('radio', true), true);
assert.equal(moduleActive, true, 'same-state retry performs real enable work');
assert.equal(mgr.isEnabled('radio'), true);
assert.equal(mgr.layers.get('radio').lifecycleUncertain, false);
assert.notEqual(mgr.layers.get('radio').intervalId, null);
assert.equal(
changes.filter(({ type }) => type === 'visibility').length,
visibilityBeforeRetry + 1,
'only the reconciled retry emits settled visibility',
);
clearInterval(mgr.layers.get('radio').intervalId);
});
test('failed enable cleanup leaves reconciliation debt instead of skipping a same-state retry', async (t) => {
for (const phase of ['init', 'enable', 'update']) {
for (const cleanupFailure of ['throw', 'false']) {
await t.test(`${phase} / cleanup ${cleanupFailure}`, async () => {
const mgr = new DataLayerManager({});
const changes = [];
let failPhase = true;
let failCleanup = true;
let moduleActive = false;
const layer = makeSlowLayer(`reconcile-${phase}-${cleanupFailure}`, { updateInterval: 1000 });
layer.module.init = async () => {
layer.calls.init++;
if (failPhase && phase === 'init') throw new Error('init fixture');
};
layer.module.enable = async () => {
layer.calls.enable++;
moduleActive = true;
if (failPhase && phase === 'enable') throw new Error('enable fixture');
};
layer.module.update = async () => {
layer.calls.update++;
if (failPhase && phase === 'update') throw new Error('update fixture');
};
layer.module.disable = async () => {
layer.calls.disable++;
moduleActive = false;
if (!failCleanup) return;
if (cleanupFailure === 'false') return false;
throw new Error('cleanup fixture');
};
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
assert.equal(await mgr.setEnabled(layer.module.id, true), false);
assert.equal(moduleActive, false, 'cleanup made the module inactive');
assert.equal(mgr.isEnabled(layer.module.id), true, 'manager remains conservatively ON');
assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, true);
assert.equal(mgr.layers.get(layer.module.id).intervalId, null);
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
assert.equal(changes.at(-1)?.type, 'visibility-failed');
assert.equal(changes.at(-1)?.phase, phase);
failPhase = false;
failCleanup = false;
const enablesBeforeRetry = layer.calls.enable;
assert.equal(await mgr.setEnabled(layer.module.id, true), true);
assert.equal(layer.calls.enable, enablesBeforeRetry + 1, 'retry does not take the same-state no-op');
assert.equal(moduleActive, true);
assert.equal(mgr.isEnabled(layer.module.id), true);
assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, false);
assert.notEqual(mgr.layers.get(layer.module.id).intervalId, null);
assert.equal(changes.filter(({ type }) => type === 'visibility').length, 1);
clearInterval(mgr.layers.get(layer.module.id).intervalId);
});
}
}
});
test('failed disable is uncertain and same-state enable reconciles module authority', async () => {
const mgr = new DataLayerManager({});
const changes = [];
let rejectDisable = false;
let moduleActive = false;
const layer = makeSlowLayer('disable-reconcile', { updateInterval: 1000 });
layer.module.enable = async () => {
layer.calls.enable++;
moduleActive = true;
};
layer.module.disable = async () => {
layer.calls.disable++;
moduleActive = false;
if (rejectDisable) throw new Error('disable fixture');
};
mgr.register(layer.module);
await mgr.setEnabled(layer.module.id, true);
mgr.subscribe((change) => changes.push(change));
rejectDisable = true;
assert.equal(await mgr.setEnabled(layer.module.id, false), false);
assert.equal(moduleActive, false);
assert.equal(mgr.isEnabled(layer.module.id), true);
assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, true);
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
rejectDisable = false;
const enablesBeforeRetry = layer.calls.enable;
assert.equal(await mgr.setEnabled(layer.module.id, true), true);
assert.equal(layer.calls.enable, enablesBeforeRetry + 1);
assert.equal(moduleActive, true);
assert.equal(mgr.layers.get(layer.module.id).lifecycleUncertain, false);
assert.equal(changes.filter(({ type }) => type === 'visibility').length, 1);
clearInterval(mgr.layers.get(layer.module.id).intervalId);
});
test('abort rejections from init and enable are cancellations, not lifecycle failures', async (t) => {
for (const phase of ['init', 'enable']) {
await t.test(phase, async () => {
const mgr = new DataLayerManager({});
const changes = [];
let announcePhase;
const phaseStarted = new Promise((resolve) => { announcePhase = resolve; });
const layer = makeSlowLayer(`radio-${phase}`, { updateInterval: -1 });
const rejectOnAbort = (_viewer, { signal } = {}) => new Promise((_resolve, reject) => {
announcePhase();
signal.addEventListener('abort', () => {
const error = new Error(`${phase} aborted`);
error.name = 'AbortError';
reject(error);
}, { once: true });
});
if (phase === 'init') layer.module.init = rejectOnAbort;
else layer.module.enable = rejectOnAbort;
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
const controller = new AbortController();
const enabling = mgr.setEnabled(layer.module.id, true, {
origin: 'voice',
signal: controller.signal,
});
await phaseStarted;
controller.abort();
const changed = await enabling;
assert.equal(changed, false);
assert.equal(mgr.isEnabled(layer.module.id), false);
assert.equal(changes.some(({ type }) => type === 'visibility-failed'), false);
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
assert.equal(changes.at(-1)?.type, 'visibility-cancelled');
assert.equal(changes.at(-1)?.intentEpoch, 1);
assert.equal(changes.at(-1)?.cancellationReason, 'caller-abort');
});
}
});
test('failed cancelled-disable compensation preserves manager/module coherence and permits retry', async () => {
const mgr = new DataLayerManager({});
const changes = [];
let releaseDisable;
let announceDisable;
let failEnable = false;
let disableAttempts = 0;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
const layer = makeSlowLayer('radio', { updateInterval: -1 });
layer.module.enable = () => {
layer.calls.enable++;
if (failEnable) throw new Error('cleanup enable fixture');
};
layer.module.disable = async () => {
layer.calls.disable++;
disableAttempts++;
if (disableAttempts > 1) return;
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
mgr.register(layer.module);
await mgr.setEnabled('radio', true);
failEnable = true;
mgr.subscribe((change) => changes.push(change));
const controller = new AbortController();
const disabling = mgr.setEnabled('radio', false, {
origin: 'voice',
signal: controller.signal,
});
await disableStarted;
controller.abort();
releaseDisable();
const changed = await disabling;
assert.equal(changed, false);
assert.equal(mgr.isEnabled('radio'), false);
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
assert.equal(changes.at(-1)?.type, 'visibility-failed');
assert.equal(changes.at(-1)?.phase, 'cancel-disable-compensation');
failEnable = false;
assert.equal(await mgr.setEnabled('radio', true), true);
assert.equal(mgr.isEnabled('radio'), true, 'same-state retry performs real lifecycle work');
});
test('cancelled Space Missions entry exposes the owning manager intent epoch', async () => {
const mgr = new DataLayerManager({});
const changes = [];
let releaseEnable;
let announceEnable;
const enableStarted = new Promise((resolve) => { announceEnable = resolve; });
const missions = makeSlowLayer('rocket-launches', { updateInterval: -1 });
missions.module.enable = async () => {
announceEnable();
await new Promise((resolve) => { releaseEnable = resolve; });
};
mgr.register(missions.module);
mgr.subscribe((change) => changes.push(change));
const controller = new AbortController();
const enabling = mgr.setEnabled('rocket-launches', true, {
origin: 'user',
signal: controller.signal,
});
await enableStarted;
controller.abort();
releaseEnable();
assert.equal(await enabling, false);
const cancelled = changes.find(({ type }) => type === 'visibility-cancelled');
assert.equal(cancelled?.layerId, 'rocket-launches');
assert.equal(cancelled?.enabled, true);
assert.equal(cancelled?.intentEpoch, 1);
});
test('lifecycle methods returning false reject their transaction without settled visibility', async (t) => {
for (const phase of ['init', 'enable', 'update', 'disable']) {
await t.test(phase, async () => {
const mgr = new DataLayerManager({});
const changes = [];
const layer = makeSlowLayer(`semantic-${phase}`, { updateInterval: -1 });
layer.module[phase] = async () => false;
mgr.register(layer.module);
if (phase === 'disable') {
layer.module.disable = async () => undefined;
await mgr.setEnabled(layer.module.id, true);
layer.module.disable = async () => false;
}
mgr.subscribe((change) => changes.push(change));
const changed = await mgr.setEnabled(layer.module.id, phase !== 'disable');
assert.equal(changed, false);
assert.equal(mgr.isEnabled(layer.module.id), phase === 'disable');
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
assert.equal(changes.at(-1)?.type, 'visibility-failed');
assert.equal(changes.at(-1)?.phase, phase);
assert.equal(
mgr.getLayerLifecycleState(layer.module.id).lifecycleState,
phase === 'disable' ? 'enabled' : 'disabled',
);
});
}
});
test('module-local AbortError is a cancellation while the caller signal remains live', async (t) => {
for (const phase of ['init', 'enable', 'update', 'disable']) {
await t.test(phase, async () => {
const mgr = new DataLayerManager({});
const changes = [];
const layer = makeSlowLayer(`resource-abort-${phase}`, { updateInterval: -1 });
const abortLocally = async () => {
const error = new Error(`${phase} resource cancelled`);
error.name = 'AbortError';
throw error;
};
if (phase === 'disable') {
mgr.register(layer.module);
await mgr.setEnabled(layer.module.id, true);
layer.module.disable = abortLocally;
} else {
layer.module[phase] = abortLocally;
mgr.register(layer.module);
}
mgr.subscribe((change) => changes.push(change));
const changed = await mgr.setEnabled(layer.module.id, phase !== 'disable');
assert.equal(changed, false);
assert.equal(mgr.isEnabled(layer.module.id), phase === 'disable');
assert.equal(changes.some(({ type }) => type === 'visibility-failed'), false);
assert.equal(changes.some(({ type }) => type === 'visibility'), false);
assert.equal(changes.at(-1)?.type, 'visibility-cancelled');
assert.equal(changes.at(-1)?.cancellationReason, 'resource-abort');
assert.equal(changes.at(-1)?.phase, phase);
assert.equal(
mgr.getLayerLifecycleState(layer.module.id).lifecycleState,
phase === 'disable' ? 'enabled' : 'disabled',
);
});
}
});
test('a settled resource cancellation cannot disable a later successful retry', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('resource-abort-retry', { updateInterval: -1 });
const caller = new AbortController();
layer.module.enable = async () => {
const error = new Error('resource request cancelled');
error.name = 'AbortError';
throw error;
};
mgr.register(layer.module);
assert.equal(await mgr.setEnabled(layer.module.id, true, { signal: caller.signal }), false);
assert.equal(caller.signal.aborted, false);
assert.equal(mgr.isEnabled(layer.module.id), false);
layer.module.enable = async () => undefined;
assert.equal(await mgr.setEnabled(layer.module.id, true), true);
assert.equal(mgr.isEnabled(layer.module.id), true);
const disablesAfterRetry = layer.calls.disable;
caller.abort();
await Promise.resolve();
assert.equal(mgr.isEnabled(layer.module.id), true);
assert.equal(layer.calls.disable, disablesAfterRetry);
});
test('simultaneous absolute enable requests stay idempotent inside the toggle queue', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('vessels', { updateInterval: 0 });
mgr.register(layer.module);
await Promise.all([
mgr.setEnabled('vessels', true),
mgr.setEnabled('vessels', true),
mgr.setEnabled('vessels', true),
]);
assert.equal(mgr.isEnabled('vessels'), true, 'repeated absolute enables end enabled');
assert.equal(layer.calls.enable, 1, 'the queued desired-state check prevents a second enable');
assert.equal(layer.calls.disable, 0, 'an absolute enable never turns the layer off');
await Promise.all([
mgr.setEnabled('vessels', false),
mgr.setEnabled('vessels', false),
]);
assert.equal(mgr.isEnabled('vessels'), false, 'repeated absolute disables end disabled');
assert.equal(layer.calls.disable, 1, 'the queued desired-state check prevents a second disable');
});
test('enabled-layer snapshots restore the exact set through normal visibility events', async () => {
const mgr = new DataLayerManager({});
const layers = ['flights', 'satellites', 'earthquakes'].map((id) => makeSlowLayer(id, { updateInterval: 0 }));
const changes = [];
for (const layer of layers) mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
await Promise.all([
mgr.setEnabled('flights', true),
mgr.setEnabled('earthquakes', true),
]);
const snapshot = mgr.getEnabledLayerIds();
assert.deepEqual([...snapshot], ['flights', 'earthquakes']);
snapshot.add('unknown-layer');
await Promise.all([
mgr.setEnabled('flights', false),
mgr.setEnabled('satellites', true),
]);
const restoreStart = changes.length;
await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore' });
assert.deepEqual([...mgr.getEnabledLayerIds()], ['flights', 'earthquakes']);
assert.deepEqual(
changes.slice(restoreStart)
.filter(({ type }) => type === 'visibility')
.map(({ layerId, enabled, origin }) => ({ layerId, enabled, origin }))
.sort((a, b) => a.layerId.localeCompare(b.layerId)),
[
{ layerId: 'earthquakes', enabled: true, origin: 'context-restore' },
{ layerId: 'flights', enabled: true, origin: 'context-restore' },
{ layerId: 'satellites', enabled: false, origin: 'context-restore' },
],
'every accepted registered absolute restore intent emits through the normal manager path',
);
await mgr.destroyAll();
});
test('restore waits for every queued layer transition before rethrowing a failure', async () => {
const mgr = new DataLayerManager({});
const failing = makeSlowLayer('failing', { updateInterval: 0 });
const radio = makeSlowLayer('radio', { updateInterval: 0 });
let releaseRadioDisable;
const radioDisableGate = new Promise((resolve) => { releaseRadioDisable = resolve; });
radio.module.disable = async () => radioDisableGate;
failing.module.enable = async () => {
throw new Error('real lifecycle enable failure');
};
mgr.register(failing.module);
mgr.register(radio.module);
await mgr.setEnabled('radio', true);
let settled = false;
const restoring = mgr.restoreEnabledLayerIds(new Set(['failing']), { origin: 'context-restore' })
.finally(() => { settled = true; });
await Promise.resolve();
await Promise.resolve();
assert.equal(settled, false, 'a sibling semantic failure must not release the restore barrier early');
releaseRadioDisable();
await assert.rejects(restoring, (error) => {
assert.match(error.message, /Failed to restore layer "failing" visibility/);
assert.deepEqual(error.failedLayerIds, ['failing']);
return true;
});
assert.equal(settled, true);
assert.equal(mgr.isEnabled('failing'), false, 'fulfilled false remains an honest failed state');
assert.equal(mgr.isEnabled('radio'), false);
await mgr.destroyAll();
});
test('restore forwards caller cancellation to every visibility intent', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: 0 });
const controller = new AbortController();
const receivedSignals = [];
const originalSetEnabledWithIntent = mgr._setEnabledWithIntent.bind(mgr);
mgr._setEnabledWithIntent = (layerId, enabled, options) => {
receivedSignals.push(options?.signal || null);
return originalSetEnabledWithIntent(layerId, enabled, options);
};
mgr.register(layer.module);
controller.abort();
await assert.rejects(
mgr.restoreEnabledLayerIds(new Set(['flights']), {
origin: 'context-restore',
signal: controller.signal,
}),
/Failed to restore layer "flights" visibility/,
);
assert.deepEqual(receivedSignals, [controller.signal]);
assert.equal(mgr.isEnabled('flights'), false);
await mgr.destroyAll();
});
test('a partial caller-aborted restore can be compensated to its exact target', async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: 0 });
const military = makeSlowLayer('military', { updateInterval: 0 });
const radio = makeSlowLayer('radio', { updateInterval: 0 });
mgr.register(flights.module);
mgr.register(military.module);
mgr.register(radio.module);
await mgr.setEnabled('flights', true);
await mgr.setEnabled('military', true);
const controller = new AbortController();
const originalDisable = flights.module.disable;
flights.module.disable = async (...args) => {
const result = await originalDisable(...args);
controller.abort();
return result;
};
await assert.rejects(
mgr.restoreEnabledLayerIds(new Set(['radio']), {
origin: 'context-restore',
signal: controller.signal,
}),
/Failed to restore layer/,
);
await mgr.restoreEnabledLayerIds(new Set(['radio']), { origin: 'context-restore' });
assert.deepEqual(new Set(mgr.getEnabledLayerIds()), new Set(['radio']));
await mgr.destroyAll();
});
test('restore follows superseding intents and requires their authoritative settled target', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('satellites', { updateInterval: 0 });
mgr.register(layer.module);
await mgr.setEnabled('satellites', true);
const originalSetEnabledWithIntent = mgr._setEnabledWithIntent.bind(mgr);
let injected = false;
mgr._setEnabledWithIntent = (layerId, enabled, options) => {
const handle = originalSetEnabledWithIntent(layerId, enabled, options);
if (!injected && options?.origin === 'context-restore') {
injected = true;
originalSetEnabledWithIntent(layerId, false, { origin: 'voice' });
}
return handle;
};
await assert.rejects(
mgr.restoreEnabledLayerIds(new Set(['satellites']), { origin: 'context-restore' }),
/Failed to restore layer "satellites" visibility/,
);
assert.equal(mgr.isEnabled('satellites'), false);
injected = false;
mgr._setEnabledWithIntent = (layerId, enabled, options) => {
const handle = originalSetEnabledWithIntent(layerId, enabled, options);
if (!injected && options?.origin === 'context-restore') {
injected = true;
originalSetEnabledWithIntent(layerId, true, { origin: 'voice' });
}
return handle;
};
await mgr.restoreEnabledLayerIds(new Set(['satellites']), { origin: 'context-restore' });
assert.equal(mgr.isEnabled('satellites'), true);
await mgr.destroyAll();
});
test('visibility notifications distinguish user toggles from dependencies', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('earthquakes', { updateInterval: 0 });
const changes = [];
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
await mgr.toggle('earthquakes', { origin: 'user' });
await mgr.setEnabled('earthquakes', false);
const settled = changes.filter(({ type }) => type === 'visibility');
assert.equal(settled[0].origin, 'user');
assert.equal(settled[1].origin, 'programmatic');
});
test('newer absolute OFF supersedes a slow ON before settled publication', async () => {
const mgr = new DataLayerManager({});
let releaseUpdate;
let markUpdateStarted;
let moduleActive = false;
const updateGate = new Promise((resolve) => { releaseUpdate = resolve; });
const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; });
const radio = makeSlowLayer('radio', { updateInterval: 0 });
radio.module.enable = () => {
radio.calls.enable++;
moduleActive = true;
};
radio.module.update = async () => {
radio.calls.update++;
markUpdateStarted();
await updateGate;
};
radio.module.disable = () => {
radio.calls.disable++;
moduleActive = false;
};
mgr.register(radio.module);
const requests = [];
const changes = [];
const latestToken = Symbol('latest-off');
mgr.subscribeVisibilityRequests((change) => requests.push(change));
mgr.subscribe((change) => changes.push(change));
const enabling = mgr.setEnabled('radio', true, { origin: 'voice' });
await updateStarted;
const disabling = mgr.setEnabled('radio', false, {
origin: 'user',
notificationToken: latestToken,
});
assert.deepEqual(requests.map(({ enabled, origin }) => ({ enabled, origin })), [
{ enabled: true, origin: 'voice' },
{ enabled: false, origin: 'user' },
]);
releaseUpdate();
assert.deepEqual(await Promise.all([enabling, disabling]), [false, true]);
assert.equal(mgr.isEnabled('radio'), false);
assert.equal(moduleActive, false);
assert.equal(mgr.layers.get('radio').intervalId, null);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility')
.map(({ enabled, origin, notificationToken }) => ({ enabled, origin, notificationToken })),
[{ enabled: false, origin: 'user', notificationToken: latestToken }],
'only the latest absolute request may publish settled visibility',
);
assert.equal(
radio.calls.presentation.some(({ lifecycleState, enabled, uncertain }) => (
lifecycleState === 'enabled' && enabled && !uncertain
)),
false,
'the superseded ON never exposes certain settled presentation',
);
await mgr.destroyAll();
});
test('newer OFF cancels init and enable phases without obsolete settlement', async (t) => {
for (const phase of ['init', 'enable']) {
await t.test(phase, async () => {
const mgr = new DataLayerManager({});
let releasePhase;
let markPhaseStarted;
const phaseGate = new Promise((resolve) => { releasePhase = resolve; });
const phaseStarted = new Promise((resolve) => { markPhaseStarted = resolve; });
const radio = makeSlowLayer(`radio-${phase}`, { updateInterval: -1 });
radio.module[phase] = async () => {
radio.calls[phase]++;
markPhaseStarted();
await phaseGate;
};
mgr.register(radio.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const enabling = mgr.setEnabled(radio.module.id, true, { origin: 'voice' });
await phaseStarted;
const disabling = mgr.setEnabled(radio.module.id, false, { origin: 'user' });
releasePhase();
assert.deepEqual(await Promise.all([enabling, disabling]), [false, true]);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility')
.map(({ enabled, origin }) => ({ enabled, origin })),
[{ enabled: false, origin: 'user' }],
);
assert.equal(mgr.isEnabled(radio.module.id), false);
assert.equal(
radio.calls.presentation.some(({ lifecycleState, enabled, uncertain }) => (
lifecycleState === 'enabled' && enabled && !uncertain
)),
false,
);
await mgr.destroyAll();
});
}
});
test('supersession during a visibility guard cancels the stale request before block publication', async () => {
const mgr = new DataLayerManager({});
let releaseGuard;
let markGuardStarted;
const guardGate = new Promise((resolve) => { releaseGuard = resolve; });
const guardStarted = new Promise((resolve) => { markGuardStarted = resolve; });
const radio = makeSlowLayer('radio', { updateInterval: -1 });
mgr.register(radio.module);
mgr.addVisibilityGuard(async (change) => {
if (change.layerId !== 'radio' || !change.enabled) return null;
markGuardStarted();
await guardGate;
return 'stale guard result';
});
const changes = [];
mgr.subscribe((change) => changes.push(change));
const enabling = mgr.setEnabled('radio', true, { origin: 'voice' });
await guardStarted;
const disabling = mgr.setEnabled('radio', false, { origin: 'user' });
releaseGuard();
assert.deepEqual(await Promise.all([enabling, disabling]), [false, true]);
assert.equal(changes.some(({ type }) => type === 'visibility-blocked'), false);
assert.deepEqual(
changes.filter(({ type }) => ['visibility-cancelled', 'visibility'].includes(type))
.map(({ type, enabled, origin }) => ({ type, enabled, origin })),
[
{ type: 'visibility-cancelled', enabled: true, origin: 'voice' },
{ type: 'visibility', enabled: false, origin: 'user' },
],
);
assert.equal(mgr.isEnabled('radio'), false);
await mgr.destroyAll();
});
test('newer same-target absolute intent owns the only settled publication', async () => {
const mgr = new DataLayerManager({});
let releaseUpdate;
let markUpdateStarted;
let firstUpdate = true;
const updateGate = new Promise((resolve) => { releaseUpdate = resolve; });
const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; });
const radio = makeSlowLayer('radio', { updateInterval: -1 });
radio.module.update = async () => {
radio.calls.update++;
if (!firstUpdate) return;
firstUpdate = false;
markUpdateStarted();
await updateGate;
};
mgr.register(radio.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const voiceEnable = mgr.setEnabled('radio', true, { origin: 'voice' });
await updateStarted;
const userEnable = mgr.setEnabled('radio', true, { origin: 'user' });
releaseUpdate();
assert.deepEqual(await Promise.all([voiceEnable, userEnable]), [false, true]);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility')
.map(({ enabled, origin }) => ({ enabled, origin })),
[{ enabled: true, origin: 'user' }],
);
assert.equal(mgr.isEnabled('radio'), true);
await mgr.destroyAll();
});
test('cancelled visibility publishes an atomic successor handoff and exact intent outcomes', async () => {
const mgr = new DataLayerManager({});
let releaseUpdate;
let markUpdateStarted;
let firstUpdate = true;
const updateGate = new Promise((resolve) => { releaseUpdate = resolve; });
const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; });
const missions = makeSlowLayer('rocket-launches', { updateInterval: -1 });
missions.module.update = async () => {
missions.calls.update++;
if (!firstUpdate) return;
firstUpdate = false;
markUpdateStarted();
await updateGate;
};
mgr.register(missions.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const first = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'user' });
await updateStarted;
const successor = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'programmatic' });
releaseUpdate();
assert.equal(await first.promise, false);
assert.equal(await successor.promise, true);
const cancelled = changes.find(({ type, intentEpoch }) => (
type === 'visibility-cancelled' && intentEpoch === first.intentEpoch
));
assert.deepEqual({
reason: cancelled?.cancellationReason,
phase: cancelled?.phase,
successorIntentEpoch: cancelled?.successorIntentEpoch,
successorEnabled: cancelled?.successorEnabled,
successorOrigin: cancelled?.successorOrigin,
}, {
reason: 'superseded',
phase: 'update',
successorIntentEpoch: successor.intentEpoch,
successorEnabled: true,
successorOrigin: 'programmatic',
});
assert.equal((await mgr._waitForVisibilityIntent('rocket-launches', successor.intentEpoch))?.succeeded, true);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility').map(({ enabled, origin }) => ({ enabled, origin })),
[{ enabled: true, origin: 'programmatic' }],
);
await mgr.destroyAll();
});
test('same-target uncertain retry retains authoritative visibility until abort cleanup settles', async () => {
const mgr = new DataLayerManager({});
const radio = makeSlowLayer('radio', { updateInterval: -1 });
let enableAttempt = 0;
let markRetryStarted;
let releaseCleanup;
const retryStarted = new Promise((resolve) => { markRetryStarted = resolve; });
const cleanupGate = new Promise((resolve) => { releaseCleanup = resolve; });
radio.module.enable = (_viewer, { signal } = {}) => {
radio.calls.enable++;
enableAttempt += 1;
if (enableAttempt !== 2) return Promise.resolve();
markRetryStarted();
return new Promise((_resolve, reject) => {
signal.addEventListener('abort', () => {
const error = new Error('superseded retry');
error.name = 'AbortError';
reject(error);
}, { once: true });
});
};
radio.module.disable = async () => {
radio.calls.disable++;
await cleanupGate;
};
mgr.register(radio.module);
assert.equal(await mgr.setEnabled('radio', true), true);
const entry = mgr.layers.get('radio');
entry.lifecycleUncertain = true;
const firstRetry = mgr.setEnabled('radio', true, { origin: 'voice' });
await retryStarted;
const latestRetry = mgr.setEnabled('radio', true, { origin: 'user' });
assert.equal(
mgr.isEnabled('radio'),
true,
'supersession does not rewrite the last authoritative boolean before cleanup',
);
releaseCleanup();
assert.deepEqual(await Promise.all([firstRetry, latestRetry]), [false, true]);
assert.equal(mgr.isEnabled('radio'), true);
assert.equal(entry.lifecycleUncertain, false);
await mgr.destroyAll();
});
test('re-entrant absolute request during lifecycle presentation owns settlement and publication', async () => {
const mgr = new DataLayerManager({});
const radio = makeSlowLayer('radio', { updateInterval: 1000 });
let moduleActive = false;
let reentrantDisable = null;
let triggered = false;
radio.module.enable = () => {
radio.calls.enable++;
moduleActive = true;
};
radio.module.disable = () => {
radio.calls.disable++;
moduleActive = false;
};
radio.module.setLifecyclePresentation = (state) => {
radio.calls.presentation.push({ ...state });
if (!triggered && state.lifecycleState === 'enabled' && state.enabled && !state.uncertain) {
triggered = true;
reentrantDisable = mgr.setEnabled('radio', false, { origin: 'user' });
}
};
mgr.register(radio.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const staleEnable = mgr.setEnabled('radio', true, { origin: 'voice' });
assert.equal(await staleEnable, false);
assert.ok(reentrantDisable);
assert.equal(await reentrantDisable, true);
assert.equal(moduleActive, false);
assert.equal(mgr.isEnabled('radio'), false);
assert.equal(mgr.layers.get('radio').intervalId, null);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility')
.map(({ enabled, origin }) => ({ enabled, origin })),
[{ enabled: false, origin: 'user' }],
);
await mgr.destroyAll();
});
test('newer absolute ON supersedes a slow OFF without publishing stale OFF', async () => {
const mgr = new DataLayerManager({});
let releaseDisable;
let markDisableStarted;
let firstDisable = true;
const disableGate = new Promise((resolve) => { releaseDisable = resolve; });
const disableStarted = new Promise((resolve) => { markDisableStarted = resolve; });
const radio = makeSlowLayer('radio', { updateInterval: -1 });
radio.module.disable = async () => {
radio.calls.disable++;
if (!firstDisable) return;
firstDisable = false;
markDisableStarted();
await disableGate;
};
mgr.register(radio.module);
await mgr.setEnabled('radio', true);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const voiceDisable = mgr.setEnabled('radio', false, { origin: 'voice' });
await disableStarted;
const userEnable = mgr.setEnabled('radio', true, { origin: 'user' });
releaseDisable();
assert.deepEqual(await Promise.all([voiceDisable, userEnable]), [false, true]);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility')
.map(({ enabled, origin }) => ({ enabled, origin })),
[{ enabled: true, origin: 'user' }],
);
assert.equal(mgr.isEnabled('radio'), true);
await mgr.destroyAll();
});
test('rapid ON then OFF then ON publishes only the final absolute intent', async () => {
const mgr = new DataLayerManager({});
let releaseUpdate;
let markUpdateStarted;
let firstUpdate = true;
const updateGate = new Promise((resolve) => { releaseUpdate = resolve; });
const updateStarted = new Promise((resolve) => { markUpdateStarted = resolve; });
const radio = makeSlowLayer('radio', { updateInterval: -1 });
radio.module.update = async () => {
radio.calls.update++;
if (!firstUpdate) return;
firstUpdate = false;
markUpdateStarted();
await updateGate;
};
mgr.register(radio.module);
const changes = [];
mgr.subscribe((change) => changes.push(change));
const firstOn = mgr.setEnabled('radio', true, { origin: 'voice' });
await updateStarted;
const middleOff = mgr.setEnabled('radio', false, { origin: 'programmatic' });
const finalOn = mgr.setEnabled('radio', true, { origin: 'user' });
releaseUpdate();
assert.deepEqual(await Promise.all([firstOn, middleOff, finalOn]), [false, false, true]);
assert.deepEqual(
changes.filter(({ type }) => type === 'visibility')
.map(({ enabled, origin }) => ({ enabled, origin })),
[{ enabled: true, origin: 'user' }],
);
assert.equal(mgr.isEnabled('radio'), true);
await mgr.destroyAll();
});
test('waitForLayerSettled defers reconciliation until the captured queue completes', async () => {
const mgr = new DataLayerManager({});
let releaseEnable;
let markEnableStarted;
const enableGate = new Promise((resolve) => { releaseEnable = resolve; });
const enableStarted = new Promise((resolve) => { markEnableStarted = resolve; });
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
missions.module.enable = async () => {
markEnableStarted();
await enableGate;
};
mgr.register(missions.module);
const enabling = mgr.setEnabled('rocket-launches', true);
await enableStarted;
let settled = false;
const waiting = mgr.waitForLayerSettled('rocket-launches').then(() => { settled = true; });
await Promise.resolve();
assert.equal(settled, false);
releaseEnable();
await Promise.all([enabling, waiting]);
assert.equal(settled, true);
await mgr.destroyAll();
});
test('programmatic Context enables neither create nor replace a restoration snapshot', async () => {
const mgr = new DataLayerManager({});
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
mgr.register(missions.module);
let snapshot = {
enabledLayerIds: new Set(['flights', 'traffic']),
marker: 'existing-session',
};
const existingSnapshot = snapshot;
mgr.subscribe((change) => {
if (!shouldCaptureContextSession(change)) return;
snapshot = {
enabledLayerIds: contextSnapshotLayerIds(mgr.getEnabledLayerIds()),
marker: 'captured-by-user',
};
});
await mgr.setEnabled('rocket-launches', true, { origin: 'programmatic' });
assert.equal(snapshot, existingSnapshot, 'programmatic enable preserves an existing session snapshot');
await mgr.setEnabled('rocket-launches', false, { origin: 'programmatic' });
snapshot = null;
await mgr.setEnabled('rocket-launches', true, { origin: 'programmatic' });
assert.equal(snapshot, null, 'programmatic enable does not create a new session snapshot');
await mgr.destroyAll();
});
test('visibility guards refuse incompatible enables before lifecycle work', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: 0 });
const changes = [];
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
const removeGuard = mgr.addVisibilityGuard((change) => (
change.layerId === 'flights' && change.enabled
? 'Replay isolation keeps Live Flights off'
: null
));
const changed = await mgr.setEnabled('flights', true, { origin: 'user' });
assert.equal(changed, false);
assert.equal(mgr.isEnabled('flights'), false);
assert.deepEqual(layer.calls, {
enable: 0, disable: 0, update: 0, init: 0, presentation: [],
});
assert.deepEqual(changes, [
{
type: 'visibility-will-change',
layerId: 'flights',
enabled: true,
origin: 'user',
intentEpoch: 1,
},
{
type: 'visibility-blocked',
layerId: 'flights',
enabled: true,
origin: 'user',
intentEpoch: 1,
reason: 'Replay isolation keeps Live Flights off',
},
]);
removeGuard();
await mgr.setEnabled('flights', true, { origin: 'programmatic' });
assert.equal(mgr.isEnabled('flights'), true);
await mgr.destroyAll();
});
test('failed asynchronous disable stays enabled and reports an explicit lifecycle failure', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: 0 });
const failure = new Error('poller refused to stop');
const changes = [];
layer.module.disable = async () => { throw failure; };
mgr.register(layer.module);
mgr.subscribe((change) => changes.push(change));
await mgr.setEnabled('flights', true);
const changed = await mgr.setEnabled('flights', false);
assert.equal(changed, false);
assert.equal(mgr.isEnabled('flights'), true, 'manager must not publish a false disabled state');
assert.notEqual(mgr.layers.get('flights').intervalId, null, 'the live refresh interval remains owned');
const failed = changes.find(({ type }) => type === 'visibility-failed');
assert.deepEqual({
layerId: failed?.layerId,
enabled: failed?.enabled,
phase: failed?.phase,
error: failed?.error,
}, {
layerId: 'flights',
enabled: false,
phase: 'disable',
error: failure,
});
clearInterval(mgr.layers.get('flights').intervalId);
});
test('captured enabled set repairs siblings stopped before an isolation failure', async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: 0 });
const traffic = makeSlowLayer('traffic', { updateInterval: 0 });
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
traffic.module.disable = async () => { throw new Error('traffic teardown failed'); };
mgr.register(flights.module);
mgr.register(traffic.module);
mgr.register(missions.module);
await Promise.all([
mgr.setEnabled('flights', true),
mgr.setEnabled('traffic', true),
]);
const snapshot = mgr.getEnabledLayerIds();
const results = await Promise.all([
mgr.setEnabled('flights', false),
mgr.setEnabled('traffic', false),
]);
assert.deepEqual(results, [true, false]);
assert.deepEqual([...mgr.getEnabledLayerIds()], ['traffic'], 'one sibling stopped before failure surfaced');
// Model rollback running from inside Rocket Launches' own visibility guard:
// that entry's queue cannot settle until the guard returns. Excluding the
// trigger must let every sibling restore without enqueueing behind itself.
mgr.layers.get('rocket-launches').toggleChain = new Promise(() => {});
await mgr.restoreEnabledLayerIds(snapshot, {
origin: 'context-restore',
excludeLayerIds: ['rocket-launches'],
});
assert.deepEqual([...mgr.getEnabledLayerIds()], ['flights', 'traffic']);
clearInterval(mgr.layers.get('flights').intervalId);
clearInterval(mgr.layers.get('traffic').intervalId);
});
test('deferred full restore reconciles an uncertain failed Context shell', async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: 0 });
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
let cleanupCanConfirm = false;
missions.module.enable = async () => { throw new Error('mission activation fixture'); };
missions.module.disable = async () => cleanupCanConfirm;
mgr.register(flights.module);
mgr.register(missions.module);
await mgr.setEnabled('flights', true);
const snapshot = mgr.getEnabledLayerIds();
await mgr.setEnabled('flights', false);
assert.equal(await mgr.setEnabled('rocket-launches', true, { origin: 'user' }), false);
assert.equal(mgr.isEnabled('rocket-launches'), true, 'uncertain cleanup stays conservatively ON');
assert.equal(mgr.layers.get('rocket-launches').lifecycleUncertain, true);
await mgr.waitForLayerSettled('rocket-launches');
cleanupCanConfirm = true;
await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore' });
assert.equal(mgr.isEnabled('flights'), true);
assert.equal(mgr.isEnabled('rocket-launches'), false);
assert.equal(mgr.layers.get('rocket-launches').lifecycleUncertain, false);
await mgr.destroyAll();
});
test('mission entry guard remains active until the slow Rocket Launches enable settles', async () => {
const mgr = new DataLayerManager({});
const flights = makeSlowLayer('flights', { updateInterval: 0 });
let releaseMissionEnable;
let markMissionEnableStarted;
const missionEnableGate = new Promise((resolve) => { releaseMissionEnable = resolve; });
const missionEnableStarted = new Promise((resolve) => { markMissionEnableStarted = resolve; });
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
missions.module.enable = async () => {
markMissionEnableStarted();
await missionEnableGate;
};
mgr.register(flights.module);
mgr.register(missions.module);
let enteringMode = null;
mgr.addVisibilityGuard(async (change) => {
if (enteringMode === 'space-missions' && change.enabled && change.layerId !== 'rocket-launches') {
return 'Replay isolation keeps other layers off';
}
if (change.enabled && change.layerId === 'rocket-launches') {
enteringMode = 'space-missions';
}
return null;
});
mgr.subscribe((change) => {
if (
change.layerId === 'rocket-launches'
&& ['visibility', 'visibility-blocked', 'visibility-failed'].includes(change.type)
) {
enteringMode = null;
}
});
const enablingMissions = mgr.setEnabled('rocket-launches', true, { origin: 'user' });
await missionEnableStarted;
const flightsChanged = await mgr.setEnabled('flights', true, { origin: 'user' });
assert.equal(flightsChanged, false);
assert.equal(mgr.isEnabled('flights'), false, 'incompatible layer stays off throughout mission startup');
assert.equal(enteringMode, 'space-missions', 'entry gate remains owned while enable is pending');
releaseMissionEnable();
await enablingMissions;
assert.equal(mgr.isEnabled('rocket-launches'), true);
assert.equal(enteringMode, null, 'settled visibility releases the entry gate');
await mgr.destroyAll();
});
test('manager awaits asynchronous dependency teardown before a rapid re-enable', async () => {
const mgr = new DataLayerManager({});
const order = [];
let releaseDisable;
let markDisableStarted;
const disableGate = new Promise((resolve) => { releaseDisable = resolve; });
const disableStarted = new Promise((resolve) => { markDisableStarted = resolve; });
mgr.register({
id: 'rocket-launches',
name: 'missions',
icon: '',
source: 'test',
updateInterval: 0,
async init() {},
async enable() { order.push('enable'); },
async disable() {
order.push('disable-start');
markDisableStarted();
await disableGate;
order.push('disable-finish');
},
async update() {},
getStats() { return { count: 0, lastUpdate: null }; },
});
await mgr.setEnabled('rocket-launches', true);
const disabling = mgr.setEnabled('rocket-launches', false);
await disableStarted;
const reenabling = mgr.setEnabled('rocket-launches', true);
assert.deepEqual(order, ['enable', 'disable-start']);
releaseDisable();
assert.deepEqual(await Promise.all([disabling, reenabling]), [false, true]);
assert.deepEqual(order, ['enable', 'disable-start', 'disable-finish', 'enable']);
assert.equal(mgr.isEnabled('rocket-launches'), true);
await mgr.destroyAll();
});
test('layer feed states distinguish unavailable, fallback, stale, and degraded controls', () => {
assert.equal(layerFeedState({ error: 'feed down', count: 0, lastUpdate: null }), 'unavailable');
assert.equal(layerFeedState({
status: 'unavailable',
error: 'feed down',
count: 50,
lastUpdate: 1,
}), 'unavailable', 'an explicit total outage stays unavailable while last-good data is preserved');
assert.equal(layerFeedState({ mode: 'sim', count: 100, lastUpdate: 1 }), 'fallback');
assert.equal(layerFeedState({ source: 'adsb.lol', count: 10, lastUpdate: 1 }), 'fallback');
assert.equal(layerFeedState({
source: 'adsb.lol',
fallback: false,
count: 10,
lastUpdate: 1,
}), 'nominal', 'an explicitly primary adsb.lol feed is not a fallback');
assert.equal(layerFeedState({ stale: true, count: 0, lastUpdate: 1 }), 'stale');
assert.equal(layerFeedState({ error: 'partial group failure', count: 50, lastUpdate: 1 }), 'degraded');
assert.equal(layerFeedState({ loading: true }), 'loading');
assert.equal(layerFeedState({ count: 5, lastUpdate: 1 }), 'nominal');
});
test('layer metadata names degraded state instead of presenting an ordinary age', () => {
const mgr = new DataLayerManager({});
assert.match(mgr._buildMetaText({
source: 'AISStream',
stats: { stale: true, count: 20, lastUpdate: Date.now() - 10_000 },
}), /^STALE · AISStream · /);
assert.equal(mgr._buildMetaText({
source: 'TomTom',
stats: { mode: 'sim', count: 120, lastUpdate: 1, loadingLabel: 'simulated traffic' },
}), 'FALLBACK · TomTom · simulated traffic');
assert.equal(mgr._buildMetaText({
source: 'CelesTrak',
stats: { error: 'CelesTrak unreachable', count: 0, lastUpdate: null },
}), 'UNAVAILABLE · CelesTrak · CelesTrak unreachable');
assert.equal(mgr._buildMetaText({
source: 'CelesTrak',
stats: {
status: 'unavailable',
error: 'CelesTrak unreachable',
count: 50,
lastUpdate: 1,
},
}), 'UNAVAILABLE · CelesTrak · CelesTrak unreachable');
});
test('uncertain lifecycle state overrides ordinary feed status without disabling reconciliation', () => {
const mgr = new DataLayerManager({});
const classes = new Map();
const attributes = new Map();
const button = {
classList: {
toggle(name, active) { classes.set(name, Boolean(active)); },
},
dataset: {},
disabled: false,
textContent: '',
setAttribute(name, value) { attributes.set(name, String(value)); },
};
const layer = {
name: 'Radio',
source: 'Radio Browser',
enabled: true,
lifecycleState: 'enabled',
lifecycleUncertain: true,
stats: { count: 750, lastUpdate: Date.now() },
};
mgr._syncToggleButton(button, layer);
assert.equal(button.textContent, 'UNCERTAIN');
assert.equal(button.dataset.feedState, 'uncertain');
assert.equal(button.disabled, false, 'the lifecycle toggle remains available to reconcile authority');
assert.equal(attributes.get('aria-label'), 'Radio: UNCERTAIN');
assert.equal(classes.get('lifecycle-uncertain'), true);
assert.equal(classes.get('feed-nominal'), false);
assert.equal(
mgr._buildMetaText(layer),
'UNCERTAIN · Radio Browser · lifecycle state requires reconciliation',
);
});
test('pre-transition subscribers capture the exact enabled set before user changes', async () => {
const mgr = new DataLayerManager({});
const context = makeSlowLayer('military-awareness', { updateInterval: 0 });
const satellites = makeSlowLayer('satellites', { updateInterval: 0 });
const snapshots = [];
mgr.register(context.module);
mgr.register(satellites.module);
await mgr.setEnabled('satellites', true);
mgr.subscribe((change) => {
if (
change.type === 'visibility-will-change'
&& change.layerId === 'military-awareness'
&& change.origin === 'user'
) {
snapshots.push({ enabled: change.enabled, ids: [...mgr.getEnabledLayerIds()] });
}
});
await mgr.toggle('military-awareness', { origin: 'user' });
await mgr.toggle('military-awareness', { origin: 'user' });
assert.deepEqual(snapshots, [
{ enabled: true, ids: ['satellites'] },
{ enabled: false, ids: ['military-awareness', 'satellites'] },
]);
await mgr.destroyAll();
});
test('absolute Context entry intent is excluded from its own pre-entry restore snapshot', async () => {
const mgr = new DataLayerManager({});
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
const satellites = makeSlowLayer('satellites', { updateInterval: 0 });
mgr.register(missions.module);
mgr.register(satellites.module);
let snapshot = null;
mgr.subscribe((change) => {
if (shouldCaptureContextSession(change)) {
snapshot = contextSnapshotLayerIds(
mgr.getEnabledLayerIds(),
null,
[change.layerId],
);
}
});
await mgr.setEnabled('rocket-launches', true, { origin: 'user' });
assert.deepEqual([...snapshot], []);
await mgr.setEnabled('rocket-launches', false, { origin: 'user' });
await mgr.restoreEnabledLayerIds(snapshot, { origin: 'context-restore' });
assert.equal(mgr.isEnabled('rocket-launches'), false);
assert.equal(mgr.isEnabled('satellites'), false);
await mgr.destroyAll();
});
test('destroy waits for pre-destroy restoration before removing a layer', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('satellites', { updateInterval: 0 });
const order = [];
layer.module.destroy = () => order.push('destroy');
mgr.register(layer.module);
await mgr.setEnabled('satellites', true);
mgr.subscribeBeforeDestroy(async ({ layerId }) => {
order.push(`restore-start:${layerId}`);
await Promise.resolve();
order.push('restore-finish');
});
await mgr.destroyLayer('satellites');
assert.deepEqual(order, ['restore-start:satellites', 'restore-finish', 'destroy']);
assert.equal(mgr.layers.has('satellites'), false);
});
test('destroy revokes an in-flight enable before it can publish settled visibility', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 });
let releaseUpdate;
let announceUpdate;
const updateStarted = new Promise((resolve) => { announceUpdate = resolve; });
layer.module.update = async () => {
announceUpdate();
await new Promise((resolve) => { releaseUpdate = resolve; });
};
mgr.register(layer.module);
const changes = [];
mgr.subscribe((change) => changes.push({ ...change }));
const enable = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'user' });
await updateStarted;
const destroying = mgr.destroyLayer('rocket-launches');
assert.equal(mgr.isEffectivelyEnabled('rocket-launches'), false);
releaseUpdate();
assert.equal(await enable.promise, false);
assert.equal(await destroying, true);
assert.equal(mgr.layers.has('rocket-launches'), false);
assert.equal(
changes.some((change) => change.type === 'visibility' && change.enabled === true),
false,
'revoked enable never publishes settled ON',
);
const cancelled = changes.find((change) => (
change.type === 'visibility-cancelled' && change.intentEpoch === enable.intentEpoch
));
assert.equal(cancelled?.cancellationReason, 'superseded');
assert.equal(cancelled?.successorOrigin, 'teardown');
assert.equal(cancelled?.successorEnabled, false);
});
test('destroy revokes an in-flight public toggle before it can publish settled visibility', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 });
let releaseUpdate;
let announceUpdate;
const updateStarted = new Promise((resolve) => { announceUpdate = resolve; });
layer.module.update = async () => {
announceUpdate();
await new Promise((resolve) => { releaseUpdate = resolve; });
};
mgr.register(layer.module);
const changes = [];
mgr.subscribe((change) => changes.push({ ...change }));
const enabling = mgr.toggle('rocket-launches', { origin: 'user' });
await updateStarted;
const destroying = mgr.destroyLayer('rocket-launches');
releaseUpdate();
assert.equal(await enabling, false);
assert.equal(await destroying, true);
assert.equal(mgr.layers.has('rocket-launches'), false);
assert.equal(
changes.some((change) => change.type === 'visibility' && change.enabled === true),
false,
'revoked public toggle never publishes settled ON',
);
const cancelled = changes.find((change) => change.type === 'visibility-cancelled');
assert.equal(cancelled?.phase, 'update');
assert.equal(cancelled?.cancellationReason, 'superseded');
assert.equal(cancelled?.successorOrigin, 'teardown');
});
test('destroyLayer retains an enabled entry when semantic disable fails and permits retry', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('radio', { updateInterval: 1000 });
let moduleActive = false;
let rejectDisable = true;
let destroyCalls = 0;
layer.module.enable = () => { moduleActive = true; };
layer.module.disable = () => {
if (rejectDisable) return false;
moduleActive = false;
return true;
};
layer.module.destroy = () => { destroyCalls += 1; };
mgr.register(layer.module);
await mgr.setEnabled('radio', true);
assert.equal(await mgr.destroyLayer('radio'), false);
assert.equal(mgr.layers.has('radio'), true);
assert.equal(mgr.isEnabled('radio'), true);
assert.equal(moduleActive, true);
assert.equal(destroyCalls, 0);
assert.notEqual(mgr.layers.get('radio').intervalId, null);
rejectDisable = false;
assert.equal(await mgr.destroyLayer('radio'), true);
assert.equal(mgr.layers.has('radio'), false);
assert.equal(moduleActive, false);
assert.equal(destroyCalls, 1);
});
test('pre-destroy hook can restore the exact focused-session state and params', async () => {
const mgr = new DataLayerManager({});
const local = makeSlowLayer('cctv', { updateInterval: 0 });
const satellites = makeSlowLayer('satellites', { updateInterval: 0 });
const missions = makeSlowLayer('rocket-launches', { updateInterval: 0 });
const satelliteParams = { catalog: 'default', showDots: true };
satellites.module.getParams = () => satelliteParams;
satellites.module.setParams = (params) => Object.assign(satelliteParams, params);
for (const layer of [local, satellites, missions]) mgr.register(layer.module);
await Promise.all([
mgr.setEnabled('cctv', true),
mgr.setEnabled('satellites', true),
]);
const snapshot = {
enabled: mgr.getEnabledLayerIds(),
params: mgr.getLayerParams('satellites'),
};
await Promise.all([
mgr.setEnabled('cctv', false),
mgr.setEnabled('rocket-launches', true),
]);
mgr.setLayerParams('satellites', { catalog: 'dense', showDots: false });
let stateAtDestroy = null;
mgr.subscribeBeforeDestroy(async () => {
mgr.setLayerParams('satellites', snapshot.params);
await mgr.restoreEnabledLayerIds(snapshot.enabled, { origin: 'context-restore' });
stateAtDestroy = {
enabled: [...mgr.getEnabledLayerIds()],
params: mgr.getLayerParams('satellites'),
};
});
await mgr.destroyLayer('rocket-launches');
assert.deepEqual(stateAtDestroy, {
enabled: ['cctv', 'satellites'],
params: { catalog: 'default', showDots: true },
});
assert.deepEqual([...mgr.getEnabledLayerIds()], ['cctv', 'satellites']);
await mgr.destroyAll();
});
test('layer parameter snapshots are detached from module-owned nested state', () => {
const mgr = new DataLayerManager({});
const params = { catalog: 'dense', filters: { altitude: [100, 200] } };
mgr.register({
id: 'satellites',
name: 'satellites',
icon: '',
source: 'test',
getParams() { return params; },
});
const snapshot = mgr.getLayerParams('satellites');
params.catalog = 'default';
params.filters.altitude[0] = 999;
assert.deepEqual(snapshot, { catalog: 'dense', filters: { altitude: [100, 200] } });
});
test('feed state: guidance statuses are normal operation, not faults', () => {
// The Military Installations wide-view prompt: zoom/search guidance must
// never read DEGRADED — waiting for user action is instruction, not fault.
assert.equal(layerFeedState({ status: 'zoom-in', error: 'zoom in to search', count: 12 }), 'nominal');
assert.equal(layerFeedState({ status: 'idle' }), 'nominal');
assert.equal(layerFeedState({ status: 'empty', error: 'no records in view' }), 'nominal');
// Honesty carve-out: rendered records from a genuinely stale cache still
// read STALE through the guidance state.
assert.equal(layerFeedState({ status: 'zoom-in', stale: true, count: 12 }), 'stale');
// Loading still wins over guidance, and a real declared outage still wins over everything.
assert.equal(layerFeedState({ status: 'zoom-in', loading: true }), 'loading');
assert.equal(layerFeedState({ status: 'unavailable', error: 'down' }), 'unavailable');
// A bare error with no prior data and no guidance status remains unavailable.
assert.equal(layerFeedState({ error: 'boom' }), 'unavailable');
});
test('effective visibility counts in-flight transitions as their target state', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseInit;
let announceInit;
const initStarted = new Promise((resolve) => { announceInit = resolve; });
layer.module.init = async () => {
announceInit();
await new Promise((resolve) => { releaseInit = resolve; });
};
mgr.register(layer.module);
// Mid-ENABLING: settled false, effectively true, snapshot includes it.
const pendingEnable = mgr.setEnabled('flights', true, { origin: 'user' });
await initStarted;
assert.equal(mgr.isEnabled('flights'), false, 'settled state stays false during activation');
assert.equal(mgr.isEffectivelyEnabled('flights'), true, 'in-flight enable is effectively ON');
assert.ok(mgr.getEnabledLayerIds().has('flights'), 'snapshot captures the in-flight enable');
releaseInit();
assert.equal(await pendingEnable, true);
// Mid-DISABLING: settled true, effectively false, snapshot excludes it.
let releaseDisable;
let announceDisable;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
layer.module.disable = async () => {
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
const pendingDisable = mgr.setEnabled('flights', false, { origin: 'user' });
await disableStarted;
assert.equal(mgr.isEnabled('flights'), true, 'settled state stays true during teardown');
assert.equal(mgr.isEffectivelyEnabled('flights'), false, 'in-flight disable is effectively OFF');
assert.equal(mgr.getEnabledLayerIds().has('flights'), false, 'snapshot honors the in-flight disable');
releaseDisable();
await pendingDisable;
assert.equal(mgr.isEnabled('flights'), false);
});
test('superseded-intent adoption re-runs visibility guards before publishing success', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseDisable;
let announceDisable;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
mgr.register(layer.module);
assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true);
// Slow user OFF whose cleanup will be superseded mid-flight. Only the FIRST
// disable is gated; the guarded adoption's compensating disable must run
// through unimpeded.
let disableCalls = 0;
layer.module.disable = async () => {
disableCalls += 1;
if (disableCalls > 1) return;
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' });
await disableStarted;
// An exclusive mode installs its guard while the OFF is still in flight.
const guardChanges = [];
mgr.addVisibilityGuard((change) => {
guardChanges.push({ ...change });
return change.layerId === 'flights' && change.enabled
? 'Flights are unavailable in this Context mode'
: null;
});
const changes = [];
mgr.subscribe((change) => changes.push({ ...change }));
// Newer absolute ON supersedes the OFF; its adoption path must consult the
// guard instead of announcing the compensated ON state as a success.
const pendingOn = mgr.setEnabled('flights', true, { origin: 'user' });
releaseDisable();
const onResult = await pendingOn;
await pendingOff;
await mgr.waitForLayerSettled('flights');
assert.equal(onResult, false, 'guarded adoption reports the blocked request as unfulfilled');
assert.ok(
guardChanges.some((change) => change.layerId === 'flights' && change.enabled),
'the guard was actually consulted for the adopted ON',
);
assert.ok(
changes.some((change) => change.type === 'visibility-blocked' && change.layerId === 'flights'),
'the blocked adoption is published as blocked',
);
assert.equal(
changes.some((change) => change.type === 'visibility' && change.layerId === 'flights' && change.enabled === true),
false,
'no successful ON visibility event is published past the guard',
);
assert.equal(mgr.isEnabled('flights'), false, 'the guard-forbidden adopted state is reconciled OFF');
assert.equal(mgr.isEffectivelyEnabled('flights'), false);
});
test('superseding an intent inside the adoption guard publishes the exact successor envelope', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('rocket-launches', { updateInterval: -1 });
mgr.register(layer.module);
assert.equal(await mgr.setEnabled('rocket-launches', true, { origin: 'user' }), true);
let releaseDisable;
let announceDisable;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
layer.module.disable = async () => {
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
let releaseAdoptionGuard;
let announceAdoptionGuard;
const adoptionGuardStarted = new Promise((resolve) => { announceAdoptionGuard = resolve; });
mgr.addVisibilityGuard(async (change) => {
if (change.layerId !== 'rocket-launches' || !change.enabled || change.origin !== 'replacement-b') return null;
announceAdoptionGuard();
await new Promise((resolve) => { releaseAdoptionGuard = resolve; });
return null;
});
const changes = [];
mgr.subscribe((change) => changes.push({ ...change }));
const off = mgr._setEnabledWithIntent('rocket-launches', false, { origin: 'user' });
await disableStarted;
const replacementB = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'replacement-b' });
releaseDisable();
await adoptionGuardStarted;
const replacementC = mgr._setEnabledWithIntent('rocket-launches', true, { origin: 'replacement-c' });
releaseAdoptionGuard();
assert.equal(await off.promise, false);
assert.equal(await replacementB.promise, false);
assert.equal(await replacementC.promise, true);
const outcomeB = await mgr._waitForVisibilityIntent('rocket-launches', replacementB.intentEpoch);
assert.equal(outcomeB.cancellationReason, 'superseded');
assert.equal(outcomeB.successorIntentEpoch, replacementC.intentEpoch);
assert.equal(outcomeB.successorEnabled, true);
assert.equal(outcomeB.successorOrigin, 'replacement-c');
assert.ok(changes.some((change) => (
change.type === 'visibility-cancelled'
&& change.intentEpoch === replacementB.intentEpoch
&& change.successorIntentEpoch === replacementC.intentEpoch
)));
assert.equal(mgr.isEnabled('rocket-launches'), true);
});
test('effective visibility follows the newest absolute intent in both supersede directions', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseInit;
let announceInit;
const initStarted = new Promise((resolve) => { announceInit = resolve; });
layer.module.init = async () => {
announceInit();
await new Promise((resolve) => { releaseInit = resolve; });
};
mgr.register(layer.module);
// OFF supersedes an in-flight enable: from the synchronous moment of the
// OFF request, effective visibility must read false even though the
// superseded transaction's lifecycleState still says 'enabling'.
const pendingOn = mgr.setEnabled('flights', true, { origin: 'user' });
await initStarted;
assert.equal(mgr.isEffectivelyEnabled('flights'), true);
const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' });
assert.equal(
mgr.isEffectivelyEnabled('flights'),
false,
'synchronously after OFF supersedes, effective visibility is OFF',
);
assert.equal(mgr.getEnabledLayerIds().has('flights'), false);
releaseInit();
await pendingOn;
await pendingOff;
await mgr.waitForLayerSettled('flights');
assert.equal(mgr.isEnabled('flights'), false);
// ON supersedes an in-flight disable: the inverse direction.
assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true);
let releaseDisable;
let announceDisable;
const disableStarted = new Promise((resolve) => { announceDisable = resolve; });
let disableCalls = 0;
layer.module.disable = async () => {
disableCalls += 1;
if (disableCalls > 1) return;
announceDisable();
await new Promise((resolve) => { releaseDisable = resolve; });
};
const pendingOff2 = mgr.setEnabled('flights', false, { origin: 'user' });
await disableStarted;
assert.equal(mgr.isEffectivelyEnabled('flights'), false);
const pendingOn2 = mgr.setEnabled('flights', true, { origin: 'user' });
assert.equal(
mgr.isEffectivelyEnabled('flights'),
true,
'synchronously after ON supersedes, effective visibility is ON',
);
assert.ok(mgr.getEnabledLayerIds().has('flights'));
releaseDisable();
await pendingOff2;
await pendingOn2;
await mgr.waitForLayerSettled('flights');
assert.equal(mgr.isEnabled('flights'), true);
assert.equal(mgr.isEffectivelyEnabled('flights'), true, 'settled fallback after all intents release');
});
test('a newer absolute intent aborts a hung guard-compensation instead of starving', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseFirstDisable;
let announceFirstDisable;
const firstDisableStarted = new Promise((resolve) => { announceFirstDisable = resolve; });
let hangCompensation = false;
let compensationAborted = false;
mgr.register(layer.module);
assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true);
let disableCalls = 0;
layer.module.disable = async (_viewer, { signal } = {}) => {
disableCalls += 1;
if (disableCalls === 1) {
announceFirstDisable();
await new Promise((resolve) => { releaseFirstDisable = resolve; });
return;
}
if (hangCompensation) {
// Hang until the manager aborts this transition; resolve on abort so the
// lifecycle can run its cancellation path.
await new Promise((resolve) => {
if (signal?.aborted) { compensationAborted = true; resolve(); return; }
signal?.addEventListener('abort', () => { compensationAborted = true; resolve(); }, { once: true });
});
return false;
}
};
const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' });
await firstDisableStarted;
const removeGuard = mgr.addVisibilityGuard((change) => (
change.layerId === 'flights' && change.enabled ? 'blocked by mode' : null
));
hangCompensation = true;
const pendingBlockedOn = mgr.setEnabled('flights', true, { origin: 'user' });
releaseFirstDisable();
// Give the blocked adoption time to enter its hung compensation.
await new Promise((resolve) => setTimeout(resolve, 10));
removeGuard();
// The newest intent must be able to abort the hung compensation and run.
const pendingFinalOn = mgr.setEnabled('flights', true, { origin: 'user' });
const finalOn = await Promise.race([
pendingFinalOn,
new Promise((resolve) => setTimeout(() => resolve('starved'), 2000)),
]);
assert.notEqual(finalOn, 'starved', 'newest intent must not starve behind the compensation');
assert.equal(compensationAborted, true, 'the hung compensation was aborted');
assert.equal(await pendingBlockedOn, false, 'the guard-blocked request stays unfulfilled');
assert.equal(finalOn, true, 'the newest ON wins once the guard is gone');
await pendingOff;
await mgr.waitForLayerSettled('flights');
assert.equal(mgr.isEnabled('flights'), true);
});
test('re-entrant setEnabled from a blocked-adoption listener supersedes the compensation cleanly', async () => {
const mgr = new DataLayerManager({});
const layer = makeSlowLayer('flights', { updateInterval: -1 });
let releaseFirstDisable;
let announceFirstDisable;
const firstDisableStarted = new Promise((resolve) => { announceFirstDisable = resolve; });
mgr.register(layer.module);
assert.equal(await mgr.setEnabled('flights', true, { origin: 'user' }), true);
let disableCalls = 0;
layer.module.disable = async () => {
disableCalls += 1;
if (disableCalls === 1) {
announceFirstDisable();
await new Promise((resolve) => { releaseFirstDisable = resolve; });
}
};
const pendingOff = mgr.setEnabled('flights', false, { origin: 'user' });
await firstDisableStarted;
let removeGuard = mgr.addVisibilityGuard((change) => (
change.layerId === 'flights' && change.enabled ? 'blocked by mode' : null
));
// The moment the blocked event is announced, a listener re-enters with a
// newer absolute intent — this used to land in the unregistered-compensation
// window and starve, or orphan the transitional presentation.
let reentrantResult = null;
const effectiveDuringBlocked = [];
const unsubscribe = mgr.subscribe((change) => {
if (change.type !== 'visibility-blocked' || change.layerId !== 'flights') return;
effectiveDuringBlocked.push(mgr.isEffectivelyEnabled('flights'));
removeGuard();
reentrantResult = mgr.setEnabled('flights', true, { origin: 'user' });
});
const pendingBlockedOn = mgr.setEnabled('flights', true, { origin: 'user' });
releaseFirstDisable();
const blockedOn = await pendingBlockedOn;
const reentrant = await Promise.race([
(async () => reentrantResult === null ? 'never-fired' : await reentrantResult)(),
new Promise((resolve) => setTimeout(() => resolve('starved'), 2000)),
]);
await pendingOff;
await mgr.waitForLayerSettled('flights');
unsubscribe();
assert.equal(blockedOn, false, 'the guard-blocked request stays unfulfilled');
assert.notEqual(reentrant, 'starved', 'the re-entrant newest intent must not starve');
assert.notEqual(reentrant, 'never-fired', 'the blocked event fired and re-entered');
assert.deepEqual(
effectiveDuringBlocked,
[false],
'during the blocked callback, effective visibility reads the reconciliation target (OFF)',
);
assert.equal(mgr.isEnabled('flights'), true, 'the re-entrant ON wins after the guard is removed');
const lifecycle = mgr.getLayerLifecycleState('flights');
assert.equal(lifecycle.lifecycleState, 'enabled', 'no orphaned transitional presentation');
assert.equal(lifecycle.uncertain, false);
});
test('every manager registration exposes the normalized loading and refresh contract', () => {
const mgr = new DataLayerManager({});
mgr.register({
id: 'minimal',
name: 'Minimal',
icon: '',
source: 'test',
updateInterval: -1,
init() {},
enable() {},
disable() {},
update() {},
});
mgr.register({
id: 'specific',
name: 'Specific',
icon: '',
source: 'test',
updateInterval: -1,
init() {},
enable() {},
disable() {},
update() {},
getStats() {
return {
count: 4,
lastUpdate: 123,
error: 'module-owned error',
available: false,
customHealth: 'preserved',
};
},
});
mgr.layers.get('specific').initialized = true;
for (const layer of mgr.getAll()) {
assert.equal(typeof layer.stats.loading, 'boolean', `${layer.id} loading must be normalized`);
assert.equal(typeof layer.stats.refreshing, 'boolean', `${layer.id} refreshing must be normalized`);
assert.ok(Object.hasOwn(layer.stats, 'managerRefreshError'));
}
const specific = mgr.getAll().find(({ id }) => id === 'specific').stats;
assert.equal(specific.error, 'module-owned error');
assert.equal(specific.available, false);
assert.equal(specific.customHealth, 'preserved');
});
test('periodic refresh publishes work, failure, and later manager-owned recovery', async () => {
const mgr = new DataLayerManager({});
let updateResult = true;
let moduleError = null;
let releaseUpdate;
let updateStarted;
const started = new Promise((resolve) => { updateStarted = resolve; });
const events = [];
mgr.register({
id: 'flights',
name: 'Live Flights',
icon: '',
source: 'test',
updateInterval: 30000,
init() {},
enable() {},
disable() {},
async update() {
updateStarted();
await new Promise((resolve) => { releaseUpdate = resolve; });
return updateResult;
},
getStats() {
return { count: 8, lastUpdate: 123, error: moduleError, available: true };
},
});
const entry = mgr.layers.get('flights');
entry.initialized = true;
entry.enabled = true;
entry.lifecycleState = 'enabled';
mgr.subscribe((event) => events.push(event));
updateResult = false;
const failedRefresh = mgr._runPeriodicUpdate('flights', entry);
await started;
assert.equal(mgr.getAll()[0].stats.refreshing, true);
assert.equal(mgr.getAll()[0].lifecycleState, 'enabled');
releaseUpdate();
assert.equal(await failedRefresh, false);
assert.match(mgr.getAll()[0].stats.managerRefreshError, /refresh rejected/);
assert.deepEqual(events.map(({ type }) => type), ['refresh-transition', 'refresh-failed']);
updateStarted = () => {};
updateResult = true;
entry.module.update = async () => true;
assert.equal(await mgr._runPeriodicUpdate('flights', entry), true);
assert.equal(mgr.getAll()[0].stats.managerRefreshError, null);
assert.deepEqual(events.map(({ type }) => type), [
'refresh-transition',
'refresh-failed',
'refresh-transition',
'refresh',
]);
assert.equal(mgr.isEnabled('flights'), true, 'refresh state never owns visibility');
let explicitRefreshCalls = 0;
entry.module.update = async (_viewer, { signal } = {}) => {
assert.equal(signal, null);
explicitRefreshCalls += 1;
return true;
};
assert.equal(await mgr.refreshLayer('flights'), true);
assert.equal(explicitRefreshCalls, 1, 'an enabled layer can be refreshed on demand');
});
test('periodic rejection preserves a module-specific error and recovers independently', async () => {
const mgr = new DataLayerManager({});
let moduleError = 'upstream-specific outage';
let shouldReject = true;
mgr.register({
id: 'satellites',
name: 'Satellites',
icon: '',
source: 'test',
updateInterval: 0,
refreshInterval: 300000,
init() {},
enable() {},
disable() {},
async update() {
if (shouldReject) throw new Error('network rejected');
return true;
},
getStats() {
return { count: 12, lastUpdate: 456, error: moduleError, available: false };
},
});
const entry = mgr.layers.get('satellites');
entry.initialized = true;
entry.enabled = true;
entry.lifecycleState = 'enabled';
assert.equal(await mgr._runPeriodicUpdate('satellites', entry), false);
let stats = mgr.getAll()[0].stats;
assert.equal(stats.error, 'upstream-specific outage');
assert.equal(stats.available, false);
assert.equal(stats.managerRefreshError, 'network rejected');
shouldReject = false;
moduleError = null;
entry.module.getStats = () => ({ count: 13, lastUpdate: 789, error: null, available: true });
assert.equal(await mgr._runPeriodicUpdate('satellites', entry), true);
stats = mgr.getAll()[0].stats;
assert.equal(stats.error, null);
assert.equal(stats.available, true);
assert.equal(stats.managerRefreshError, null);
});
test('disable invalidates an active periodic refresh without publishing stale settlement', async () => {
const mgr = new DataLayerManager({});
let releaseRefresh;
let announceRefresh;
const refreshStarted = new Promise((resolve) => { announceRefresh = resolve; });
const events = [];
mgr.register({
id: 'flights',
name: 'Live Flights',
icon: '',
source: 'test',
updateInterval: 30000,
init() {},
enable() {},
disable() {},
async update() {
announceRefresh();
await new Promise((resolve) => { releaseRefresh = resolve; });
throw new Error('late refresh failure');
},
getStats() {
return { count: 4, lastUpdate: 123, error: null, available: true };
},
});
const entry = mgr.layers.get('flights');
entry.initialized = true;
entry.enabled = true;
entry.lifecycleState = 'enabled';
mgr.subscribe((event) => events.push(event));
const pendingRefresh = mgr._runPeriodicUpdate('flights', entry);
await refreshStarted;
const waitingRefresh = mgr.refreshLayer('flights');
await Promise.resolve();
const pendingDisable = mgr.setEnabled('flights', false, { origin: 'user' });
assert.equal(await waitingRefresh, false);
await new Promise((resolve) => setTimeout(resolve, 0));
releaseRefresh();
assert.equal(await pendingRefresh, false);
assert.equal(await pendingDisable, true);
assert.equal(mgr.isEnabled('flights'), false);
assert.equal(entry.refreshing, false);
assert.equal(entry.managerRefreshError, null);
assert.ok(events.some(({ type }) => type === 'refresh-transition'));
assert.ok(events.some(({ type, reason }) => (
type === 'refresh-cancelled' && reason === 'layer-disabled'
)));
assert.ok(!events.some(({ type }) => type === 'refresh-failed' || type === 'refresh'));
});
test('destroy settles an explicit refresh waiting behind invalidated periodic work', async () => {
const mgr = new DataLayerManager({});
let releaseRefresh;
let announceRefresh;
const refreshStarted = new Promise((resolve) => { announceRefresh = resolve; });
const events = [];
mgr.register({
id: 'flights',
name: 'Live Flights',
icon: '',
source: 'test',
updateInterval: 30000,
init() {},
enable() {},
disable() {},
async update() {
announceRefresh();
await new Promise((resolve) => { releaseRefresh = resolve; });
return true;
},
getStats() {
return { count: 4, lastUpdate: 123, error: null, available: true };
},
});
const entry = mgr.layers.get('flights');
entry.initialized = true;
entry.enabled = true;
entry.lifecycleState = 'enabled';
mgr.subscribe((event) => events.push(event));
const periodicRefresh = mgr._runPeriodicUpdate('flights', entry);
await refreshStarted;
const requestedRefresh = mgr.refreshLayer('flights');
await Promise.resolve();
const destroy = mgr.destroyLayer('flights');
assert.equal(await requestedRefresh, false);
assert.ok(events.some(({ type, reason }) => (
type === 'refresh-cancelled' && reason === 'layer-destroyed'
)));
releaseRefresh();
assert.equal(await periodicRefresh, false);
assert.equal(await destroy, true);
});
// ── Per-layer row controls (chips + color legend) ───────────────────────────
// The satellites layer is the first consumer: a DENSE catalog chip and a class
// legend rendered under its row. The manager owns the DOM and the param write;
// the layer only declares what it wants, so the chip can never disagree with
// the layer's real state.
/** DOM double rich enough for the row-controls render path. */
function makeControlElement() {
const element = {
children: [],
className: '',
dataset: {},
style: {},
attributes: {},
listeners: {},
textContent: '',
hidden: false,
disabled: false,
title: '',
type: '',
classList: { toggle() {} },
appendChild(child) { child.parent = this; this.children.push(child); return child; },
append(...nodes) { for (const n of nodes) n.parent = this; this.children.push(...nodes); },
replaceChildren(...nodes) { this.children = [...nodes]; },
remove() {
const siblings = this.parent?.children;
if (siblings) this.parent.children = siblings.filter((n) => n !== this);
// Browsers blur a node the moment it leaves the document. Modelling that
// is the whole point: it is exactly what in-place chip reconciliation
// exists to avoid, so a regression to rebuild-everything must fail here.
if (globalThis.document?.activeElement === this) globalThis.document.activeElement = null;
},
focus() { if (globalThis.document) globalThis.document.activeElement = this; },
addEventListener(name, handler) { this.listeners[name] = handler; },
setAttribute(name, value) { this.attributes[name] = String(value); },
closest(selector) {
const className = selector.slice(1);
return String(this.className).split(/\s+/).includes(className) ? this : null;
},
querySelector(selector) {
if (selector.startsWith('[data-layer-id="')) {
const id = selector.slice(16, -2);
return this.children.find((child) => child.dataset.layerId === id) || null;
}
const className = selector.startsWith('.') ? selector.slice(1) : '';
const visit = (node) => {
if (String(node.className).split(/\s+/).includes(className)) return node;
for (const child of node.children || []) {
const found = visit(child);
if (found) return found;
}
return null;
};
return visit(this);
},
set innerHTML(value) { if (value === '') this.children = []; },
get innerHTML() { return ''; },
};
return element;
}
/** Collect every node in a rendered subtree carrying `className`. */
function collectByClass(node, className) {
const found = [];
const visit = (current) => {
if (String(current.className).split(/\s+/).includes(className)) found.push(current);
for (const child of current.children || []) visit(child);
};
visit(node);
return found;
}
/** A layer that declares a two-state chip plus a legend, like satellites. */
function makeRowControlLayer() {
let mode = 'core';
return {
get mode() { return mode; },
module: {
id: 'satellites',
name: 'Satellites',
icon: '',
source: 'CelesTrak',
updateInterval: -1,
async init() {},
enable() {},
disable() {},
async update() {},
getStats() { return { count: 3, lastUpdate: Date.now() }; },
setParams(params) { if (params.catalog) mode = params.catalog; },
getParams() { return { catalog: mode }; },
getRowControls() {
const dense = mode === 'dense';
return {
chips: [{
id: 'catalog',
label: 'DENSE',
active: dense,
title: 'toggle the dense catalog',
params: { catalog: dense ? 'core' : 'dense' },
}],
legend: [
{ klass: 'nav', label: 'NAV', color: '#4fd8ff', blurb: 'GNSS', count: 2 },
{ klass: 'geo', label: 'GEO', color: '#c89bff', blurb: 'belt', count: 5 },
],
};
},
},
};
}
test('a layer that declares row controls renders its chips and color legend', async () => {
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement };
const mgr = new DataLayerManager({});
const layer = makeRowControlLayer();
mgr.register(layer.module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
const row = container.querySelector('[data-layer-id="satellites"]');
const controls = row.querySelector('.data-toggle-controls');
assert.ok(controls, 'the row gained a controls block');
// Disabled layers stay quiet — no chip, no legend.
assert.equal(controls.hidden, true);
assert.equal(await mgr.setEnabled('satellites', true), true);
mgr._refreshTogglePanel();
assert.equal(controls.hidden, false);
const chips = collectByClass(controls, 'data-toggle-chip');
assert.equal(chips.length, 1);
assert.equal(chips[0].textContent, 'DENSE');
assert.equal(chips[0].dataset.chipId, 'catalog');
assert.equal(chips[0].attributes['aria-pressed'], 'false');
assert.equal(chips[0].title, 'toggle the dense catalog');
const swatches = collectByClass(controls, 'data-toggle-legend-swatch');
assert.deepEqual(swatches.map((s) => s.style.background), ['#4fd8ff', '#c89bff'],
'each legend swatch is painted the exact class color');
const items = collectByClass(controls, 'data-toggle-legend-item');
assert.deepEqual(items.map((i) => i.title), ['GNSS', 'belt']);
assert.equal(items.length, 2);
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
test('clicking a row chip applies the params it declared and re-renders', async () => {
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement };
const mgr = new DataLayerManager({});
const layer = makeRowControlLayer();
mgr.register(layer.module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
assert.equal(await mgr.setEnabled('satellites', true), true);
const row = container.querySelector('[data-layer-id="satellites"]');
const controls = row.querySelector('.data-toggle-controls');
const chip = collectByClass(controls, 'data-toggle-chip')[0];
controls.listeners.click({ target: chip });
assert.equal(layer.mode, 'dense', 'the chip wrote the params it declared');
// setLayerParams refreshes the panel, so the chip already reflects the flip.
const afterOn = collectByClass(controls, 'data-toggle-chip')[0];
assert.equal(afterOn.attributes['aria-pressed'], 'true');
assert.equal(afterOn.className.includes('active'), true);
controls.listeners.click({ target: afterOn });
assert.equal(layer.mode, 'core', 'the chip toggles back rather than latching');
assert.equal(collectByClass(controls, 'data-toggle-chip')[0].attributes['aria-pressed'], 'false');
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
test('a click outside a chip is inert, and a throwing layer cannot blank the panel', async () => {
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement };
const warn = console.warn;
console.warn = () => {};
const mgr = new DataLayerManager({});
const layer = makeRowControlLayer();
mgr.register(layer.module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
assert.equal(await mgr.setEnabled('satellites', true), true);
const row = container.querySelector('[data-layer-id="satellites"]');
const controls = row.querySelector('.data-toggle-controls');
const legendItem = collectByClass(controls, 'data-toggle-legend-item')[0];
controls.listeners.click({ target: legendItem });
assert.equal(layer.mode, 'core', 'the legend is not a control');
controls.listeners.click({ target: { closest: () => null } });
assert.equal(layer.mode, 'core');
layer.module.getRowControls = () => { throw new Error('boom'); };
mgr._refreshTogglePanel();
assert.equal(controls.hidden, true, 'a throwing layer collapses to an empty block');
assert.ok(container.querySelector('[data-layer-id="satellites"]'), 'the row itself survives');
} finally {
console.warn = warn;
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
test('keyboard focus on a chip survives the refresh its own click triggers', async () => {
// _syncRowControls runs on EVERY panel refresh, including the one the chip's
// own click triggers. Rebuilding the button would blur it every time, so a
// keyboard user loses their place on activation.
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement, activeElement: null };
const mgr = new DataLayerManager({});
const layer = makeRowControlLayer();
mgr.register(layer.module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
assert.equal(await mgr.setEnabled('satellites', true), true);
const controls = container
.querySelector('[data-layer-id="satellites"]')
.querySelector('.data-toggle-controls');
const chip = collectByClass(controls, 'data-toggle-chip')[0];
chip.focus();
assert.equal(globalThis.document.activeElement, chip, 'the chip starts focused');
controls.listeners.click({ target: chip });
assert.equal(globalThis.document.activeElement, chip,
'activating the chip does not blur it');
assert.equal(collectByClass(controls, 'data-toggle-chip')[0], chip,
'the SAME button node is reused across the click-driven refresh');
mgr._refreshTogglePanel();
mgr._refreshTogglePanel();
assert.equal(globalThis.document.activeElement, chip,
'repeated refreshes never steal focus');
// Legend entries hold no focus, so they may be replaced — never duplicated.
assert.equal(collectByClass(controls, 'data-toggle-legend-item').length, 2);
// ...and a chip that genuinely goes away still releases focus.
layer.module.getRowControls = () => ({ chips: [], legend: [] });
mgr._refreshTogglePanel();
assert.equal(globalThis.document.activeElement, null);
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
test('an async layer pushes its own row refresh, and a busy chip refuses clicks', async () => {
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement };
const mgr = new DataLayerManager({});
let settled = false;
let writes = 0;
const module = {
id: 'satellites',
name: 'Satellites',
icon: '',
source: 'CelesTrak',
updateInterval: -1,
async init() {},
enable() {},
disable() {},
async update() {},
getStats() { return { count: 1, lastUpdate: Date.now() }; },
setParams() { writes += 1; },
getRowControls() {
return {
chips: [{
id: 'catalog',
label: settled ? 'DENSE' : 'DENSE ···',
active: settled,
busy: !settled,
disabled: !settled,
state: settled ? 'active' : 'loading',
title: 'x',
params: { catalog: 'core' },
}],
legend: [],
};
},
setRowControlsListener(fn) { module._listener = fn; },
};
mgr.register(module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
assert.equal(await mgr.setEnabled('satellites', true), true);
assert.equal(typeof module._listener, 'function', 'the manager installed its listener');
const controls = container
.querySelector('[data-layer-id="satellites"]')
.querySelector('.data-toggle-controls');
const chip = collectByClass(controls, 'data-toggle-chip')[0];
assert.equal(chip.textContent, 'DENSE ···');
assert.equal(chip.disabled, true);
assert.equal(chip.attributes['aria-busy'], 'true');
assert.equal(chip.attributes['aria-pressed'], 'false', 'busy is never reported as active');
assert.equal(chip.className.includes('chip-loading'), true);
controls.listeners.click({ target: chip });
assert.equal(writes, 0, 'a disabled chip is inert');
// The layer settles and pushes its own refresh — no panel poll involved.
settled = true;
module._listener();
assert.equal(chip.textContent, 'DENSE');
assert.equal(chip.disabled, false);
assert.equal(chip.attributes['aria-pressed'], 'true');
assert.equal(chip.attributes['aria-busy'], 'false');
controls.listeners.click({ target: chip });
assert.equal(writes, 1, 'the settled chip writes again');
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});
test('a layer that surrenders its row controls hides the block entirely', async () => {
// Space Missions borrows the satellite layer with showPoints:false; the row
// must not advertise a legend for an empty sky or offer a chip whose write
// the dependency owner will silently revert.
const originalDocument = globalThis.document;
globalThis.document = { createElement: makeControlElement };
const mgr = new DataLayerManager({});
let surrendered = false;
const layer = makeRowControlLayer();
const inner = layer.module.getRowControls;
layer.module.getRowControls = () => (surrendered ? { chips: [], legend: [] } : inner());
mgr.register(layer.module);
const container = makeControlElement();
try {
mgr.buildTogglePanel(container);
assert.equal(await mgr.setEnabled('satellites', true), true);
const controls = container
.querySelector('[data-layer-id="satellites"]')
.querySelector('.data-toggle-controls');
assert.equal(controls.hidden, false);
assert.equal(collectByClass(controls, 'data-toggle-chip').length, 1);
surrendered = true;
mgr._refreshTogglePanel();
assert.equal(controls.hidden, true);
assert.equal(collectByClass(controls, 'data-toggle-chip').length, 0,
'the chip is removed, not merely hidden behind a style');
assert.equal(collectByClass(controls, 'data-toggle-legend-item').length, 0);
surrendered = false;
mgr._refreshTogglePanel();
assert.equal(controls.hidden, false, 'the row returns when the owner releases it');
assert.equal(collectByClass(controls, 'data-toggle-chip').length, 1);
} finally {
await mgr.destroyAll();
if (originalDocument === undefined) delete globalThis.document;
else globalThis.document = originalDocument;
}
});