mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
refactor(street-level): drop hover, legend and byte-budget cache; flatten the provider directory
- No pointer cursor over coverage lines (no other layer has one); a click test replaces the hover tests. - No legend: one source, one colour. - The on-globe credit lives in index.js; credits.js is gone. - Billboard glyphs are SVG data URLs with the same geometry, so tests need no canvas stub. - The tile cache keeps the 120 most recently used tiles, same 24 h TTL, instead of a byte budget; listTileLayers was test-only. - providers/mapillary/ is flattened into the layer and its policy merged into one policy.js; the source's method list is a literal in the catalog. - One fakeCesiumViewer for the layer tests. - The fixture gate keeps the steps the unit tests do not cover: coverage and credit, a rejected key, line -> cone -> photo, close, a keyless page. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
34d43c7e3f
commit
e214129e45
41 files changed
+335
-909
No files matched your search
@@ -3,17 +3,9 @@ import test from 'node:test';
|
||||
import * as Cesium from 'cesium';
|
||||
import { createSelection } from './selection.js';
|
||||
|
||||
/**
|
||||
* The selection installed on a stand-in viewer. Animation frames queue until
|
||||
* `frame()` runs them.
|
||||
*/
|
||||
/** The selection installed on a stand-in viewer, with what its clicks reach. */
|
||||
function harness({ selected = false } = {}) {
|
||||
const saved = {
|
||||
document: globalThis.document,
|
||||
requestAnimationFrame: globalThis.requestAnimationFrame,
|
||||
};
|
||||
const frames = [];
|
||||
globalThis.requestAnimationFrame = (task) => frames.push(task);
|
||||
const saved = globalThis.document;
|
||||
globalThis.document = new EventTarget();
|
||||
// The document's keydown listeners, so a test can hand them any target.
|
||||
const keyListeners = new Set();
|
||||
@@ -31,26 +23,17 @@ function harness({ selected = false } = {}) {
|
||||
if (type === 'keydown') keyListeners.delete(listener);
|
||||
removeListener(type, listener, options);
|
||||
};
|
||||
const sequences = { selected, cleared: 0 };
|
||||
const picks = [];
|
||||
/** What the pointer is over: a line this layer owns, or nothing. */
|
||||
const sequences = { selected, cleared: 0, selects: [] };
|
||||
const opened = [];
|
||||
/** What the pointer is over: a pick id, or nothing. */
|
||||
const scene = { under: null };
|
||||
const canvas = Object.assign(new EventTarget(), {
|
||||
style: {},
|
||||
// Keep Cesium's handler on the canvas; there is no real document here.
|
||||
disableRootEvents: true,
|
||||
onwheel: null,
|
||||
});
|
||||
const camera = { moveEnd: new Cesium.Event() };
|
||||
const viewer = {
|
||||
camera,
|
||||
scene: {
|
||||
canvas,
|
||||
pick(position) {
|
||||
picks.push(position);
|
||||
return scene.under && { id: scene.under };
|
||||
},
|
||||
},
|
||||
scene: { canvas, pick: () => scene.under && { id: scene.under } },
|
||||
};
|
||||
const state = {
|
||||
viewer,
|
||||
@@ -68,20 +51,24 @@ function harness({ selected = false } = {}) {
|
||||
state,
|
||||
parts: {
|
||||
sequences: {
|
||||
select: (id) => sequences.selects.push(id),
|
||||
clearSelection() {
|
||||
sequences.cleared++;
|
||||
sequences.selected = false;
|
||||
},
|
||||
},
|
||||
openImage: (id) => opened.push(id),
|
||||
},
|
||||
});
|
||||
selection.install(viewer);
|
||||
const action = (type) =>
|
||||
state.clickHandler.getInputAction(Cesium.ScreenSpaceEventType[type]);
|
||||
const onMove = action('MOUSE_MOVE');
|
||||
return {
|
||||
state,
|
||||
sequences,
|
||||
opened,
|
||||
scene,
|
||||
click: () => action('LEFT_CLICK')({ position: { x: 10, y: 10 } }),
|
||||
/** A keydown reaching the document from `target`; the event as handled. */
|
||||
key(key, target, { defaultPrevented = false } = {}) {
|
||||
const event = {
|
||||
@@ -95,94 +82,32 @@ function harness({ selected = false } = {}) {
|
||||
for (const listener of [...keyListeners]) listener(event);
|
||||
return event;
|
||||
},
|
||||
scene,
|
||||
canvas,
|
||||
picks,
|
||||
move: (x) => onMove({ endPosition: { x, y: 10 } }),
|
||||
camera,
|
||||
/** A pointer event on the canvas with `buttons` held. */
|
||||
buttons(type, buttons) {
|
||||
const event = new Event(type);
|
||||
Object.defineProperty(event, 'buttons', { value: buttons });
|
||||
canvas.dispatchEvent(event);
|
||||
},
|
||||
frame() {
|
||||
for (const task of frames.splice(0)) task();
|
||||
},
|
||||
restore() {
|
||||
selection.uninstall();
|
||||
Object.assign(globalThis, saved);
|
||||
globalThis.document = saved;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('hover picks once a frame, at the latest position, and shows a pointer over a line', () => {
|
||||
test('a click on a line selects its sequence, on a cone opens its image, elsewhere does nothing', () => {
|
||||
const h = harness();
|
||||
try {
|
||||
h.scene.under = 'mly:seq:1';
|
||||
for (let x = 0; x < 5; x++) h.move(x);
|
||||
h.frame();
|
||||
assert.deepEqual(
|
||||
h.picks.map(({ x }) => x),
|
||||
[4],
|
||||
'one pick for the burst',
|
||||
);
|
||||
assert.equal(h.canvas.style.cursor, 'pointer');
|
||||
h.scene.under = null;
|
||||
h.move(6);
|
||||
h.frame();
|
||||
assert.equal(h.canvas.style.cursor, '', 'off the line');
|
||||
h.frame();
|
||||
assert.equal(h.picks.length, 2, 'a still pointer costs nothing');
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a drag with any button picks nothing, and a release with a modifier held does not stick', () => {
|
||||
const h = harness();
|
||||
try {
|
||||
for (const held of [1, 2, 4]) {
|
||||
h.buttons('pointerdown', held);
|
||||
h.move(held);
|
||||
h.frame();
|
||||
h.scene.under = 'mly:seq:abc';
|
||||
h.click();
|
||||
assert.deepEqual(h.sequences.selects, ['abc']);
|
||||
h.scene.under = 'mly:img:42';
|
||||
h.click();
|
||||
assert.deepEqual(h.opened, ['42']);
|
||||
for (const under of [null, 'cctv:7', 'sl:pos']) {
|
||||
h.scene.under = under;
|
||||
h.click();
|
||||
}
|
||||
assert.equal(h.picks.length, 0, 'left, right and middle drags');
|
||||
// Released with Shift held: Cesium's plain LEFT_UP never fires.
|
||||
h.buttons('pointerup', 0);
|
||||
h.move(5);
|
||||
h.frame();
|
||||
assert.equal(h.picks.length, 1, 'hover picks again');
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('when the camera rests, a still pointer is picked again so the cursor cannot stick', () => {
|
||||
const h = harness();
|
||||
try {
|
||||
h.scene.under = 'mly:seq:1';
|
||||
h.move(5);
|
||||
h.frame();
|
||||
assert.equal(h.canvas.style.cursor, 'pointer');
|
||||
// The camera moves the line out from under a still pointer.
|
||||
h.scene.under = null;
|
||||
h.camera.moveEnd.raiseEvent();
|
||||
h.frame();
|
||||
assert.equal(h.picks.length, 2);
|
||||
assert.equal(h.canvas.style.cursor, '');
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('a hover frame queued before the layer went off does not pick', () => {
|
||||
const h = harness();
|
||||
try {
|
||||
h.move(2);
|
||||
h.state.enabled = false;
|
||||
h.frame();
|
||||
assert.equal(h.picks.length, 0, 'the layer went off');
|
||||
assert.deepEqual([h.sequences.selects, h.opened], [['abc'], ['42']]);
|
||||
// Mapillary switched off under a layer that is on: lines are not drawn.
|
||||
h.state.providerOn = false;
|
||||
h.scene.under = 'mly:seq:abc';
|
||||
h.click();
|
||||
assert.deepEqual(h.sequences.selects, ['abc']);
|
||||
} finally {
|
||||
h.restore();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user