test(street-level): close the mutation survivors; Esc in rich text

A mutation review left 18 earlier survivors; each now has a test that
fails with the mutant applied:

- the expanded viewer returns into the panel on shrink, close and destroy
  (the fake DOM had no parentNode, so reinsertion was never exercised); the
  fake DOM gains offsetParent and a MutationObserver that honours its
  options, and a <body> child added while expanded goes inert;
- each pose moves the marker, drives FOLLOW and selects a new sequence;
  close() retires an open still mounting or opening; a provider toggled
  while the layer is off activates nothing; the adapter drops late and
  superseded poses;
- share-link defaults: "any date" (0 days), the codec max pinned to the
  filter's MAX_SINCE_DAYS (layerState cannot import it across package
  boundaries, so a test ties the two), Mapillary on when a link omits it;
- Esc that something else handled, or typed in a field, keeps the
  selection; a key problem outranks LOADING on the pill;
- the marker's terrain height (mesh, DEM, clamp then re-place);
- proxy: LRU memory cache and its half-budget rule, Retry-After capped,
  a request after a cancelled fetch starts a fresh one;
- date-line tile ranking with and without focus; the horizon cull idles
  while the camera stands still; the old zoom goes as soon as the new one
  lands; a rate limit holds every refresh inside its wait.

One real bug found and fixed: Esc inside a contenteditable cleared the
selected sequence (selection.js now ignores isContentEditable targets).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Said TurkseverandClaude Opus 5.5 committed 2026-10-06 16:54:06 +01:00
1 parent 9dde7b4a62
commit 3af0f80cdb
15 files changed
+1198 -30

No files matched your search

@@ -839,3 +839,57 @@ test('only lines within the mesh sampler range of the camera are asked for (revi
assert.ok(farthest < 200, `nothing from the corner (${farthest} m)`);
coverage.clear();
});
test('the old zoom goes as soon as the new zoom has loaded, not after the stale wait', async () => {
const { viewer, source, state, coverage, bytes } = setup();
coverage.refresh();
source.calls[0].resolve(bytes);
await settle();
const [old] = viewer.scene.groundPrimitives.items;
assert.ok(old, 'the z14 lines are drawn');
viewer.view.height = 3000; // zoom out to z13
coverage.refresh();
assert.equal(state.coverage.zoom, 13);
const fresh = source.calls.slice(1);
assert.ok(fresh.length > 0, 'the new zoom is loading');
assert.equal(state.coverage.stale.size, 1, 'the old zoom stays meanwhile');
assert.ok(viewer.scene.groundPrimitives.items.has(old));
for (const call of fresh) {
assert.ok(
viewer.scene.groundPrimitives.items.has(old),
'kept while a new tile is still loading',
);
call.resolve(new Uint8Array());
await settle();
}
// The last new tile is in: the old lines go now, long before the wait.
assert.equal(state.coverage.pending.size, 0);
assert.equal(state.coverage.stale.size, 0);
assert.equal(viewer.scene.groundPrimitives.items.has(old), false);
assert.equal(state.coverage.staleTimer, null, 'the stale wait is cancelled');
coverage.clear();
});
test('a rate limit holds every refresh inside its wait, not only the first (review IC8 P2)', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout', 'Date'] });
const { viewer, source, coverage } = setup();
coverage.refresh();
source.calls[0].reject(
Object.assign(new Error('rate limited'), { retryAfterSec: 30 }),
);
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
t.mock.timers.tick(1000);
viewer.view.lon += 0.05;
coverage.refresh();
assert.equal(source.calls.length, 1, 'held 1 s into a 30 s wait');
t.mock.timers.tick(28_000);
viewer.view.lon += 0.05;
coverage.refresh();
assert.equal(source.calls.length, 1, 'held 29 s into a 30 s wait');
t.mock.timers.tick(1000);
assert.ok(source.calls.length > 1, 'asked again once the wait is over');
coverage.clear();
coverage.resetErrors();
});