mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(street-level): review round — safer docking, mount retry, provider fallthrough
- Only a user's own collapse docks a floating window; cockpit entry, restores and the Cyber accordion no longer dock it or delete its saved place, and the accordion leaves floating windows alone. - The viewer host records an adapter as active only after its mount succeeds, so a library that failed to load once is retried; concurrent opens share one mount, and unmounting mid-mount leaves nothing active. - openNearest tries every active provider; one provider's error (no key, offline) no longer hides the others' imagery. - The SINCE readout shows its cut-off date as soon as the committed value matches the thumb, even while the slider keeps focus. - A floating window that was only dragged is bounded to the viewport and scrolls instead of running off-screen. - getStats summarises coverage (summarizeCoverage) instead of building a full UI snapshot every second; disable() uses credits.hideAll(); smaller cleanups in the viewer host and sequence snapshot. - Unit tests: panel docking and header double press, viewer-host mount retry/sharing/cancel, coverage summary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
5877f24f0b
commit
6207e4144e
12 files changed
+425
-55
No files matched your search
@@ -0,0 +1,89 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createViewerHost } from './viewerHost.js';
|
||||
|
||||
function harness(adapter) {
|
||||
const state = {
|
||||
enabled: true,
|
||||
notify() {},
|
||||
providers: new Map(),
|
||||
street: { host: {}, renderMode: 'letterbox', follow: false },
|
||||
};
|
||||
const def = { id: 'mapillary', name: 'Mapillary', label: 'MAPILLARY' };
|
||||
state.providers.set('mapillary', { def, instance: { viewer: adapter } });
|
||||
const parts = {
|
||||
marker: { set() {}, clear() {} },
|
||||
follow: { followCamera() {}, lookAtPosition() {} },
|
||||
};
|
||||
return { state, host: createViewerHost({ state, parts }) };
|
||||
}
|
||||
|
||||
function fakeAdapter({ failMounts = 0 } = {}) {
|
||||
const calls = { mount: 0, open: [], unmount: 0, listeners: 0 };
|
||||
let emit = null;
|
||||
return {
|
||||
calls,
|
||||
async mount() {
|
||||
calls.mount++;
|
||||
if (calls.mount <= failMounts) throw new Error('library failed to load');
|
||||
},
|
||||
async open(id) {
|
||||
calls.open.push(id);
|
||||
emit?.({
|
||||
providerId: 'mapillary',
|
||||
imageId: id,
|
||||
position: { lon: 1, lat: 2 },
|
||||
bearing: 90,
|
||||
isPano: false,
|
||||
externalUrl: `https://example.test/${id}`,
|
||||
});
|
||||
},
|
||||
close() {},
|
||||
unmount() {
|
||||
calls.unmount++;
|
||||
},
|
||||
resize() {},
|
||||
onPose(listener) {
|
||||
calls.listeners++;
|
||||
emit = listener;
|
||||
return () => {
|
||||
calls.listeners--;
|
||||
emit = null;
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('a failed mount is retried on the next open instead of being cached', async () => {
|
||||
const adapter = fakeAdapter({ failMounts: 1 });
|
||||
const { state, host } = harness(adapter);
|
||||
await host.open('mapillary', 'a');
|
||||
assert.equal(state.street.error, 'library failed to load');
|
||||
assert.equal(adapter.calls.listeners, 0, 'the pose listener was released');
|
||||
await host.open('mapillary', 'b');
|
||||
assert.equal(state.street.error, null);
|
||||
assert.equal(adapter.calls.mount, 2);
|
||||
assert.deepEqual(adapter.calls.open, ['b']);
|
||||
assert.equal(state.street.imageId, 'b');
|
||||
assert.equal(state.street.externalUrl, 'https://example.test/b');
|
||||
});
|
||||
|
||||
test('concurrent opens share one mount and one pose listener', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { host } = harness(adapter);
|
||||
await Promise.all([host.open('mapillary', 'a'), host.open('mapillary', 'b')]);
|
||||
assert.equal(adapter.calls.mount, 1);
|
||||
assert.equal(adapter.calls.listeners, 1);
|
||||
});
|
||||
|
||||
test('unmounting while a mount is in flight does not leave it active', async () => {
|
||||
const adapter = fakeAdapter();
|
||||
const { state, host } = harness(adapter);
|
||||
const opening = host.open('mapillary', 'a');
|
||||
host.unmount();
|
||||
await opening;
|
||||
assert.equal(host.activeProviderId(), null);
|
||||
assert.equal(adapter.calls.listeners, 0);
|
||||
assert.equal(adapter.calls.unmount, 1);
|
||||
assert.equal(state.street.open, false);
|
||||
});
|
||||
Reference in new issue
Block a user