mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
Merge branch 'main' into upstream/traffic-layer-stale
This commit is contained in:
145 files changed
+17257
-457
No files matched your search
@@ -448,6 +448,14 @@ export const DATA_CREDITS = [
|
||||
'(<a href="https://earthdata.nasa.gov/firms" target="_blank" rel="noopener">earthdata.nasa.gov/firms</a>), ' +
|
||||
'part of NASA’s Earth Observing System Data and Information System (EOSDIS)',
|
||||
},
|
||||
{
|
||||
key: 's2-ghostmaps',
|
||||
html:
|
||||
'S2 GhostMaps intel products: S2 Underground © by S2A1, ' +
|
||||
'<a href="https://github.com/s2underground/GhostMaps" target="_blank" rel="noopener">github.com/s2underground/GhostMaps</a>, ' +
|
||||
'licensed under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" rel="noopener">CC BY-NC-SA 4.0</a>. ' +
|
||||
'Shown as published; reports are S2 assessments, not verified fact.',
|
||||
},
|
||||
{
|
||||
key: 'drivebc-cctv',
|
||||
html:
|
||||
|
||||
@@ -1134,7 +1134,8 @@ export function repairKmlRootNamespaces(value, what = 'KML') {
|
||||
return text.slice(0, start) + root + text.slice(end);
|
||||
}
|
||||
|
||||
function parseXml(text, what) {
|
||||
/** Parse KML or GPX with the shared namespace repairs and XML validation. */
|
||||
export function parseXml(text, what) {
|
||||
// Match the namespace repairs Cesium applies to ordinary exported KML.
|
||||
if (what !== 'GPX') text = repairKmlRootNamespaces(text, what);
|
||||
const doc = new DOMParser().parseFromString(
|
||||
@@ -1150,7 +1151,8 @@ function parseXml(text, what) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
function decodeXml(bytes) {
|
||||
/** Decode XML bytes, honouring a UTF-16 byte-order mark. */
|
||||
export function decodeXml(bytes) {
|
||||
const encoding =
|
||||
bytes[0] === 0xff && bytes[1] === 0xfe
|
||||
? 'utf-16le'
|
||||
|
||||
@@ -23,7 +23,7 @@ const NEW_SINGLE_CHARACTER_TOKEN_PATTERN = /^[0-9]$/;
|
||||
* out-of-grammar ID is rejected outright, because half an address is a
|
||||
* DIFFERENT aircraft, not a shorter name for the same one.
|
||||
*/
|
||||
const TRACKING_ID_GRAMMAR = /^[0-9a-z~_-]{1,16}$/;
|
||||
export const TRACKING_ID_GRAMMAR = /^[0-9a-z~_-]{1,16}$/;
|
||||
/**
|
||||
* Ceilings for the untrusted v2 layer fields. The layer ceiling covers the
|
||||
* complete reserved one-character space plus every two-character base-36
|
||||
@@ -655,6 +655,11 @@ export const LAYER_STATE_REGISTRY = Object.freeze([
|
||||
token: 'x',
|
||||
disposition: 'enabled-only',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 's2-ghostmaps',
|
||||
token: '5',
|
||||
disposition: 'enabled-only',
|
||||
}),
|
||||
Object.freeze({
|
||||
id: 'satellites',
|
||||
token: 's',
|
||||
|
||||
+145
-13
@@ -202,8 +202,8 @@ function encode(state) {
|
||||
|
||||
test('production registry is exact, canonical, and rejects incomplete contracts', async () => {
|
||||
assert.equal(validateLayerStateRegistry(), true);
|
||||
assert.equal(REGISTERED_LAYER_IDS.length, 31);
|
||||
assert.equal(new Set(REGISTERED_LAYER_IDS).size, 31);
|
||||
assert.equal(REGISTERED_LAYER_IDS.length, 32);
|
||||
assert.equal(new Set(REGISTERED_LAYER_IDS).size, 32);
|
||||
assert.ok(REGISTERED_LAYER_IDS.includes('transit'));
|
||||
assert.deepEqual(REGISTERED_LAYER_IDS, [...REGISTERED_LAYER_IDS].sort());
|
||||
assert.deepEqual(LEGACY_LAYER_STATE_TOKENS, {
|
||||
@@ -242,19 +242,19 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
for (const [id, token] of Object.entries(LEGACY_LAYER_STATE_TOKENS)) {
|
||||
assert.equal(LAYER_STATE_TOKEN_RESERVATIONS[id], token);
|
||||
}
|
||||
assert.equal(nextLayerStateToken(), '5');
|
||||
assert.equal(nextLayerStateToken(), '6');
|
||||
assert.equal(
|
||||
nextLayerStateToken({
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
alpha: '5',
|
||||
bravo: '6',
|
||||
alpha: '6',
|
||||
bravo: '7',
|
||||
}),
|
||||
'7',
|
||||
'8',
|
||||
);
|
||||
const digitsExhausted = {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
...Object.fromEntries(
|
||||
[...'56789'].map((digit) => [`prior-${digit}`, digit]),
|
||||
[...'6789'].map((digit) => [`prior-${digit}`, digit]),
|
||||
),
|
||||
};
|
||||
assert.equal(nextLayerStateToken(digitsExhausted), '00');
|
||||
@@ -307,8 +307,8 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(LAYER_STATE_TOKEN_RESERVATIONS, {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
future: '5',
|
||||
next: '6',
|
||||
future: '6',
|
||||
next: '7',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@@ -318,7 +318,7 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
future: '00',
|
||||
}),
|
||||
/next free token 5/,
|
||||
/next free token 6/,
|
||||
);
|
||||
const beforeLastDigit = { ...digitsExhausted };
|
||||
delete beforeLastDigit['prior-9'];
|
||||
@@ -341,10 +341,10 @@ test('production registry is exact, canonical, and rejects incomplete contracts'
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateAllocations(
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, merged: '5' },
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, merged: '5', competing: '5' },
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, merged: '6' },
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS, merged: '6', competing: '6' },
|
||||
),
|
||||
/next free token 6/,
|
||||
/next free token 7/,
|
||||
);
|
||||
assert.throws(
|
||||
() => validateLayerStateAllocations({ future: '00' }, { future: '01' }),
|
||||
@@ -2858,3 +2858,135 @@ test('explicit provider switches reach durable state without adopting unrelated
|
||||
coordinator.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
function enabledLayerParam(sink) {
|
||||
return new URLSearchParams(encode(sink.provider())).get('l');
|
||||
}
|
||||
|
||||
test('Clear All drops a saved layer a context mode had already switched off', async () => {
|
||||
const manager = productionManager();
|
||||
const storage = memoryStorage();
|
||||
const sink = shareSink();
|
||||
const coordinator = new LayerStateCoordinator(manager, sink, { storage });
|
||||
await coordinator.start();
|
||||
await manager.setEnabled('earthquakes', true, { origin: 'user' });
|
||||
await manager.setEnabled('flights', true, { origin: 'user' });
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, [
|
||||
'earthquakes',
|
||||
'flights',
|
||||
]);
|
||||
// Context isolation is a temporary, passive takeover: the saved choice stays.
|
||||
await manager.setEnabled('earthquakes', false, { origin: 'programmatic' });
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, [
|
||||
'earthquakes',
|
||||
'flights',
|
||||
]);
|
||||
|
||||
const updatesBefore = sink.updates;
|
||||
const result = await manager.clearSelectedLayers({ origin: 'user' });
|
||||
assert.deepEqual(result.targetIds, ['flights']);
|
||||
assert.ok(sink.updates > updatesBefore, 'the URL is told to refresh');
|
||||
assert.deepEqual([...manager.getEnabledLayerIds()], []);
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, []);
|
||||
assert.equal(enabledLayerParam(sink), '', 'the URL carries no layers');
|
||||
assert.deepEqual(
|
||||
parseStoredLayerState(storage.getItem(LAYER_STATE_STORAGE_KEY))
|
||||
.enabledLayerIds,
|
||||
[],
|
||||
'a reload restores nothing',
|
||||
);
|
||||
coordinator.destroy();
|
||||
});
|
||||
|
||||
test('Clear All drops a saved layer a dependency release superseded mid-batch', async () => {
|
||||
let manager;
|
||||
manager = productionManager({
|
||||
// Contacts-style owner: its own teardown releases a borrowed dependency
|
||||
// without awaiting it, which supersedes Clear All's turn for that layer.
|
||||
'military-awareness': {
|
||||
disable() {
|
||||
void manager.setEnabled('flights', false, { origin: 'programmatic' });
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
const storage = memoryStorage();
|
||||
const sink = shareSink();
|
||||
const coordinator = new LayerStateCoordinator(manager, sink, { storage });
|
||||
await coordinator.start();
|
||||
await manager.setEnabled('earthquakes', true, { origin: 'user' });
|
||||
await manager.setEnabled('flights', true, { origin: 'user' });
|
||||
await manager.setEnabled('military-awareness', true, { origin: 'user' });
|
||||
|
||||
const result = await manager.clearSelectedLayers({ origin: 'user' });
|
||||
assert.equal(
|
||||
result.items.find((item) => item.id === 'flights')?.superseded,
|
||||
true,
|
||||
'the fixture reproduces the superseded flights turn',
|
||||
);
|
||||
assert.deepEqual([...manager.getEnabledLayerIds()], []);
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, []);
|
||||
assert.equal(enabledLayerParam(sink), '');
|
||||
coordinator.destroy();
|
||||
});
|
||||
|
||||
test('Clear All keeps a layer a newer explicit request turned back on', async () => {
|
||||
const manager = productionManager();
|
||||
const sink = shareSink();
|
||||
const coordinator = new LayerStateCoordinator(manager, sink, {
|
||||
storage: memoryStorage(),
|
||||
});
|
||||
await coordinator.start();
|
||||
await manager.setEnabled('earthquakes', true, { origin: 'user' });
|
||||
await manager.setEnabled('flights', true, { origin: 'user' });
|
||||
const clearing = manager.clearSelectedLayers({ origin: 'user' });
|
||||
// A direct request issued during the batch owns its layer.
|
||||
const reenable = manager.setEnabled('earthquakes', true, { origin: 'user' });
|
||||
await Promise.all([clearing, reenable]);
|
||||
assert.deepEqual([...manager.getEnabledLayerIds()], ['earthquakes']);
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, [
|
||||
'earthquakes',
|
||||
]);
|
||||
assert.equal(enabledLayerParam(sink), 'e');
|
||||
coordinator.destroy();
|
||||
});
|
||||
|
||||
test('a passive clear never rewrites saved layers', async () => {
|
||||
const manager = productionManager();
|
||||
const storage = memoryStorage();
|
||||
const coordinator = new LayerStateCoordinator(manager, shareSink(), {
|
||||
storage,
|
||||
});
|
||||
await coordinator.start();
|
||||
await manager.setEnabled('flights', true, { origin: 'user' });
|
||||
await manager.setEnabled('flights', false, { origin: 'programmatic' });
|
||||
const writes = storage.writes.length;
|
||||
await manager.clearSelectedLayers({ origin: 'programmatic' });
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, ['flights']);
|
||||
assert.equal(storage.writes.length, writes);
|
||||
coordinator.destroy();
|
||||
});
|
||||
|
||||
test('Clear All cancels a saved restore still waiting to start', async () => {
|
||||
const gate = deferred();
|
||||
const manager = productionManager();
|
||||
const sink = shareSink();
|
||||
const coordinator = new LayerStateCoordinator(manager, sink, {
|
||||
storage: memoryStorage(),
|
||||
restoreGate: gate.promise,
|
||||
});
|
||||
const params = new URLSearchParams('v=2&l=e.f');
|
||||
const restore = coordinator.start({
|
||||
shareLayerState: decodeLayerStateParams(params),
|
||||
});
|
||||
await manager.clearSelectedLayers({ origin: 'user' });
|
||||
assert.deepEqual(coordinator.getDurableState().enabledLayerIds, []);
|
||||
gate.resolve();
|
||||
const results = await restore;
|
||||
for (const id of ['earthquakes', 'flights']) {
|
||||
assert.equal(results.find((result) => result.layerId === id).succeeded, false);
|
||||
}
|
||||
assert.deepEqual([...manager.getEnabledLayerIds()], []);
|
||||
assert.equal(enabledLayerParam(sink), '');
|
||||
coordinator.destroy();
|
||||
});
|
||||
@@ -148,6 +148,36 @@ export class LayerStateCoordinator {
|
||||
return this.restorePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the running layer state with a share opened later in this tab
|
||||
* (hash navigation). Every in-flight restore and pending shared Follow is
|
||||
* superseded; the new state restores through the same passive share origin
|
||||
* as start-up, so recipient-local preferences are never written. A share
|
||||
* without a layer payload restores the defaults, as a reload of it would.
|
||||
*/
|
||||
applyShareLayerState({
|
||||
shareLayerState = null,
|
||||
shareCreatedAtMs = null,
|
||||
} = {}) {
|
||||
if (this._destroyed) return Promise.resolve([]);
|
||||
for (const controller of this._restoreControllers.values())
|
||||
controller.abort('superseded-by-share-navigation');
|
||||
this._revokePendingTrackingWatch('superseded-by-share-navigation');
|
||||
const selected = shareLayerState
|
||||
? normalizeLayerState(shareLayerState)
|
||||
: null;
|
||||
this._source = selected ? 'share' : 'legacy-share';
|
||||
this._shareCreatedAtMs =
|
||||
selected && Number.isFinite(shareCreatedAtMs) ? shareCreatedAtMs : null;
|
||||
this._durableState = selected || createDefaultLayerState();
|
||||
this.shareLinkManager?.onLayerStateChange?.();
|
||||
this._notifyDurableState();
|
||||
this.restorePromise = this._restoreSelectedState(
|
||||
LAYER_RESTORE_ORIGINS.share,
|
||||
);
|
||||
return this.restorePromise;
|
||||
}
|
||||
|
||||
get source() {
|
||||
return this._source;
|
||||
}
|
||||
@@ -220,6 +250,30 @@ export class LayerStateCoordinator {
|
||||
this._revokePendingTrackingWatch('owner-layer-disabled');
|
||||
}
|
||||
if (!isExplicitLayerStateOrigin(change.origin)) return;
|
||||
if (change.type === 'selection-cleared') {
|
||||
// Only layers a newer request turned back on survive Clear All. Any
|
||||
// other saved layer would reappear from the share link or a reload.
|
||||
const stillEnabled = new Set(change.enabledLayerIds || []);
|
||||
const dropped = this._durableState.enabledLayerIds.filter(
|
||||
(id) => !stillEnabled.has(id),
|
||||
);
|
||||
if (dropped.length === 0) return;
|
||||
for (const layerId of dropped) {
|
||||
this._restoreControllers
|
||||
.get(layerId)
|
||||
?.abort('superseded-by-explicit-visibility');
|
||||
}
|
||||
if (dropped.some((id) => SHARE_TRACKING_RESTORE_POLICIES[id])) {
|
||||
this._revokePendingTrackingWatch('superseded-by-explicit-visibility');
|
||||
}
|
||||
this._commitExplicit({
|
||||
...this._durableState,
|
||||
enabledLayerIds: this._durableState.enabledLayerIds.filter((id) =>
|
||||
stillEnabled.has(id),
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (change.type === 'visibility') {
|
||||
this._restoreControllers
|
||||
.get(change.layerId)
|
||||
@@ -312,15 +366,19 @@ export class LayerStateCoordinator {
|
||||
}
|
||||
|
||||
async _restoreSelectedState(origin) {
|
||||
// One controller set per run: a superseded run must not clear the newer
|
||||
// run's controllers when it settles.
|
||||
const controllers = new Map();
|
||||
for (const entry of LAYER_STATE_REGISTRY) {
|
||||
this._restoreControllers.set(entry.id, new AbortController());
|
||||
controllers.set(entry.id, new AbortController());
|
||||
}
|
||||
this._restoreControllers = controllers;
|
||||
try {
|
||||
await this._waitForRestoreGate();
|
||||
const enabled = new Set(this._durableState.enabledLayerIds);
|
||||
const settled = await Promise.allSettled(
|
||||
LAYER_STATE_REGISTRY.map(async (entry) => {
|
||||
const controller = this._restoreControllers.get(entry.id);
|
||||
const controller = controllers.get(entry.id);
|
||||
const targetEnabled = enabled.has(entry.id);
|
||||
const options = layerOptionsForRestore(this._durableState, entry.id);
|
||||
if (origin === LAYER_RESTORE_ORIGINS.share && options) {
|
||||
@@ -370,7 +428,7 @@ export class LayerStateCoordinator {
|
||||
}));
|
||||
}),
|
||||
);
|
||||
this.lastRestoreResults = settled.map((result, index) => {
|
||||
const results = settled.map((result, index) => {
|
||||
if (result.status === 'fulfilled') return result.value;
|
||||
const entry = LAYER_STATE_REGISTRY[index];
|
||||
return {
|
||||
@@ -387,9 +445,13 @@ export class LayerStateCoordinator {
|
||||
succeeded: false,
|
||||
};
|
||||
});
|
||||
return this.lastRestoreResults.map((result) => ({ ...result }));
|
||||
// A run superseded by a newer share keeps the newer run's results.
|
||||
if (this._restoreControllers === controllers)
|
||||
this.lastRestoreResults = results;
|
||||
return results.map((result) => ({ ...result }));
|
||||
} finally {
|
||||
this._restoreControllers.clear();
|
||||
if (this._restoreControllers === controllers)
|
||||
this._restoreControllers.clear();
|
||||
this._notifyDurableState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
{ ...parseLayerStateTokenReservations(reservationRows) },
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS },
|
||||
);
|
||||
assert.equal(Object.keys(LAYER_STATE_TOKEN_RESERVATIONS).length, 31);
|
||||
assert.equal(Object.keys(LAYER_STATE_TOKEN_RESERVATIONS).length, 32);
|
||||
assert.deepEqual(
|
||||
{ ...LAYER_STATE_TOKEN_RESERVATIONS },
|
||||
{
|
||||
@@ -136,6 +136,7 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
'street-level': '0',
|
||||
'gnss-integrity': '3',
|
||||
'air-quality': '4',
|
||||
's2-ghostmaps': '5',
|
||||
},
|
||||
);
|
||||
assert.throws(
|
||||
@@ -161,9 +162,9 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
/Duplicate layer-state token reservation/,
|
||||
);
|
||||
assert.equal(
|
||||
parseLayerStateTokenReservations([...reservationRows, ['future', '5']])
|
||||
parseLayerStateTokenReservations([...reservationRows, ['future', '6']])
|
||||
.future,
|
||||
'5',
|
||||
'6',
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
@@ -181,14 +182,14 @@ test('reservation ledger is complete, pinned, and rejects duplicate or malformed
|
||||
|
||||
test('published retired digits and competing PRs advance the merge-time allocation', () => {
|
||||
withPublishedBase(
|
||||
{ rows: [...reservationRows, ['retired-layer', '5']] },
|
||||
{ rows: [...reservationRows, ['retired-layer', '6']] },
|
||||
(cwd) => {
|
||||
const published = readPublishedLayerStateReservations('HEAD', cwd);
|
||||
assert.equal(nextLayerStateToken(published), '6');
|
||||
assert.equal(nextLayerStateToken(published), '7');
|
||||
assert.equal(
|
||||
validateLayerStateAllocations(published, {
|
||||
...published,
|
||||
newcomer: '6',
|
||||
newcomer: '7',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
@@ -196,15 +197,15 @@ test('published retired digits and competing PRs advance the merge-time allocati
|
||||
() =>
|
||||
validateLayerStateAllocations(published, {
|
||||
...published,
|
||||
competing: '5',
|
||||
competing: '6',
|
||||
}),
|
||||
/next free token 6/,
|
||||
/next free token 7/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
validateLayerStateAllocations(published, {
|
||||
...LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
newcomer: '5',
|
||||
newcomer: '6',
|
||||
}),
|
||||
/changed or removed: retired-layer/,
|
||||
);
|
||||
@@ -212,8 +213,8 @@ test('published retired digits and competing PRs advance the merge-time allocati
|
||||
);
|
||||
});
|
||||
|
||||
test('PR B manually replaces provisional 5 with 6 after PR A publishes 5', () => {
|
||||
withPublishedBase({ rows: [...reservationRows, ['pr-a', '5']] }, (cwd) => {
|
||||
test('PR B manually replaces provisional 6 with 7 after PR A publishes 6', () => {
|
||||
withPublishedBase({ rows: [...reservationRows, ['pr-a', '6']] }, (cwd) => {
|
||||
mkdirSync(path.join(cwd, 'scripts'));
|
||||
const fixtureChecker = path.join(
|
||||
cwd,
|
||||
@@ -234,8 +235,8 @@ test('PR B manually replaces provisional 5 with 6 after PR A publishes 5', () =>
|
||||
|
||||
writeCodecFixture(
|
||||
cwd,
|
||||
[...reservationRows, ['pr-b', '5']],
|
||||
[{ id: 'pr-b', token: '5' }],
|
||||
[...reservationRows, ['pr-b', '6']],
|
||||
[{ id: 'pr-b', token: '6' }],
|
||||
);
|
||||
const beforeRebase = runCheck();
|
||||
assert.equal(beforeRebase.status, 1);
|
||||
@@ -243,10 +244,10 @@ test('PR B manually replaces provisional 5 with 6 after PR A publishes 5', () =>
|
||||
|
||||
writeCodecFixture(
|
||||
cwd,
|
||||
[...reservationRows, ['pr-a', '5'], ['pr-b', '5']],
|
||||
[...reservationRows, ['pr-a', '6'], ['pr-b', '6']],
|
||||
[
|
||||
{ id: 'pr-a', token: '5' },
|
||||
{ id: 'pr-b', token: '5' },
|
||||
{ id: 'pr-a', token: '6' },
|
||||
{ id: 'pr-b', token: '6' },
|
||||
],
|
||||
);
|
||||
const staleRebase = runCheck();
|
||||
@@ -254,16 +255,16 @@ test('PR B manually replaces provisional 5 with 6 after PR A publishes 5', () =>
|
||||
assert.match(staleRebase.stderr, /Duplicate layer-state token reservation/);
|
||||
|
||||
const published = readPublishedLayerStateReservations('HEAD', cwd);
|
||||
assert.equal(nextLayerStateToken(published), '6');
|
||||
const validBaselineRows = [...reservationRows, ['pr-a', '5']];
|
||||
writeCodecFixture(cwd, validBaselineRows, [{ id: 'pr-a', token: '5' }]);
|
||||
assert.equal(nextLayerStateToken(published), '7');
|
||||
const validBaselineRows = [...reservationRows, ['pr-a', '6']];
|
||||
writeCodecFixture(cwd, validBaselineRows, [{ id: 'pr-a', token: '6' }]);
|
||||
const helper = spawnSync(
|
||||
process.execPath,
|
||||
[realpathSync(fixtureNext), 'pr-b'],
|
||||
{ cwd, encoding: 'utf8' },
|
||||
);
|
||||
assert.equal(helper.status, 0, helper.stderr);
|
||||
assert.match(helper.stdout, /pr-b: 6/);
|
||||
assert.match(helper.stdout, /pr-b: 7/);
|
||||
assert.deepEqual(
|
||||
JSON.parse(readFileSync(path.join(cwd, LEDGER_PATH), 'utf8')),
|
||||
validBaselineRows,
|
||||
@@ -271,20 +272,20 @@ test('PR B manually replaces provisional 5 with 6 after PR A publishes 5', () =>
|
||||
);
|
||||
writeCodecFixture(
|
||||
cwd,
|
||||
[...reservationRows, ['pr-a', '5'], ['pr-b', '6']],
|
||||
[...reservationRows, ['pr-a', '6'], ['pr-b', '7']],
|
||||
[
|
||||
{ id: 'pr-a', token: '5' },
|
||||
{ id: 'pr-b', token: '6' },
|
||||
{ id: 'pr-a', token: '6' },
|
||||
{ id: 'pr-b', token: '7' },
|
||||
],
|
||||
);
|
||||
const corrected = runCheck();
|
||||
assert.equal(corrected.status, 0, corrected.stderr);
|
||||
assert.match(corrected.stdout, /32 published, 1 new/);
|
||||
assert.match(corrected.stdout, /33 published, 1 new/);
|
||||
});
|
||||
});
|
||||
|
||||
test('allocation batches cross the last digit and base-36 pair boundaries in order', () => {
|
||||
const digitRows = [...'56789'].map((digit) => [`retired-${digit}`, digit]);
|
||||
const digitRows = [...'6789'].map((digit) => [`retired-${digit}`, digit]);
|
||||
const allDigits = parseLayerStateTokenReservations([
|
||||
...reservationRows,
|
||||
...digitRows,
|
||||
@@ -336,7 +337,7 @@ test('allocation batches cross the last digit and base-36 pair boundaries in ord
|
||||
test('an isolated valid two-character fixture round-trips an l field beyond the old 64-character cap', async () => {
|
||||
const cwd = mkdtempSync(path.join(tmpdir(), 'gev-layer-token-width-'));
|
||||
try {
|
||||
const priorDigits = [...'56789'].map((digit) => [
|
||||
const priorDigits = [...'6789'].map((digit) => [
|
||||
`qa-prior-digit-${digit}`,
|
||||
digit,
|
||||
]);
|
||||
@@ -373,7 +374,7 @@ test('an isolated valid two-character fixture round-trips an l field beyond the
|
||||
const params = new URLSearchParams([['v', '2']]);
|
||||
codec.encodeLayerStateParams(params, state);
|
||||
assert.ok(params.get('l').length > 64, 'fixture must cross the old cap');
|
||||
assert.equal(params.get('l').length, 73);
|
||||
assert.equal(params.get('l').length, 75);
|
||||
const restored = codec.decodeLayerStateParams(params);
|
||||
assert.deepEqual(restored?.enabledLayerIds, expectedLayerIds);
|
||||
assert.deepEqual(restored?.options, state.options);
|
||||
@@ -391,7 +392,7 @@ test('checker reads a complete future base ledger and rejects retired-token reus
|
||||
},
|
||||
(cwd) => {
|
||||
const published = readPublishedLayerStateReservations('HEAD', cwd);
|
||||
assert.equal(Object.keys(published).length, 32);
|
||||
assert.equal(Object.keys(published).length, 33);
|
||||
assert.equal(published['retired-layer'], '00');
|
||||
assert.throws(
|
||||
() =>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
["radio", "r"],
|
||||
["recent-imagery", "1"],
|
||||
["rocket-launches", "x"],
|
||||
["s2-ghostmaps", "5"],
|
||||
["satellites", "s"],
|
||||
["street-level", "0"],
|
||||
["telegeography-submarine-cables", "u"],
|
||||
|
||||
@@ -1650,6 +1650,15 @@ export class LayerLifecycle {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clear All owns the whole selection, not only the layers it switched off
|
||||
// itself. A layer already off for a passive reason (isolated by a context
|
||||
// mode, released by a dependency owner, or superseded mid-batch) gets no
|
||||
// OFF of its own here, so publish the set that is still on afterwards.
|
||||
this._notifyListeners({
|
||||
type: 'selection-cleared',
|
||||
origin,
|
||||
enabledLayerIds: [...this.getEnabledLayerIds()],
|
||||
});
|
||||
const items = targetIds.map((id) => {
|
||||
const state = this.getLayerLifecycleState(id) || {
|
||||
enabled: false,
|
||||
|
||||
Reference in new issue
Block a user