mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
fix(street-level): modal viewer, pinned redirects, real-route CI checks
From the IC8 review of #768, each with tests that fail before: - P2: the expanded viewer is a real modal: while it is on screen every other <body> child is inert (only the ones it set are restored, on shrink, close and destroy, and when Clean View/recording/cockpit hide it). The voice pill and toasts are not clickable while it is expanded. - P2: the tile proxy follows redirects by hand, at most 3 hops, each one HTTPS on exactly https://tiles.mapillary.com without credentials; anything else is a 502 and the token never leaves that origin. - P2: the real dev and preview server smoke tests request /api/mapillary/status and a tile, and the browser gate first checks the server's real status route before any interception, so CI fails if the Mapillary plugin is not registered. - P2: both gates press panel headers through one shared helper (scripts/qa-panelDrag.mjs): re-resolve the header until it is under the pointer, record what the press hit, wait on the real floating/docked state, and retry only a press that provably missed the header. Verified: unit 5910 pass; fixture gate 17/17; panel gate 5/5; live gate with a real Mapillary token 22/22. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
bd97d38a99
commit
038d8facc2
12 files changed
+881
-97
No files matched your search
@@ -742,6 +742,134 @@ test('a hidden expanded viewer (Clean View, recording, cockpit) holds neither Es
|
||||
controls.destroy();
|
||||
}));
|
||||
|
||||
/** A MutationObserver stand-in the test fires by hand. */
|
||||
function fakeMutationObserver() {
|
||||
const observers = [];
|
||||
class FakeMutationObserver {
|
||||
constructor(callback) {
|
||||
this.callback = callback;
|
||||
this.target = null;
|
||||
this.options = null;
|
||||
observers.push(this);
|
||||
}
|
||||
observe(target, options) {
|
||||
this.target = target;
|
||||
this.options = options;
|
||||
}
|
||||
disconnect() {
|
||||
this.target = null;
|
||||
}
|
||||
}
|
||||
const fire = () => {
|
||||
for (const observer of observers)
|
||||
if (observer.target) observer.callback([], observer);
|
||||
};
|
||||
const watching = () => observers.some((observer) => observer.target);
|
||||
return { FakeMutationObserver, fire, watching };
|
||||
}
|
||||
|
||||
/** Run `fn` with a fake MutationObserver installed. */
|
||||
async function withObserver(fn) {
|
||||
const saved = globalThis.MutationObserver;
|
||||
const fake = fakeMutationObserver();
|
||||
globalThis.MutationObserver = fake.FakeMutationObserver;
|
||||
try {
|
||||
return await fn(fake);
|
||||
} finally {
|
||||
if (saved === undefined) delete globalThis.MutationObserver;
|
||||
else globalThis.MutationObserver = saved;
|
||||
}
|
||||
}
|
||||
|
||||
/** Every <body> child but the viewer, by id. */
|
||||
const inertOutside = (dom) =>
|
||||
dom.document.body.children
|
||||
.filter((node) => node.id !== 'sl-viewer-wrap')
|
||||
.map((node) => [node.id, node.inert === true]);
|
||||
|
||||
test('the expanded viewer makes the rest of the app inert and gives it back on shrink (IC8 P2)', () =>
|
||||
withObserver(() =>
|
||||
withDom(async (dom) => {
|
||||
const { controls } = stubPanel(dom, uiState({ open: true }));
|
||||
// Another dialog's own inert backdrop: it is not ours to release.
|
||||
const owned = dom.document.body.appendChild(
|
||||
new dom.globe.constructor(dom.document, 'div', { id: 'owned' }),
|
||||
);
|
||||
owned.inert = true;
|
||||
controls.setViewerExpanded(true);
|
||||
assert.deepEqual(inertOutside(dom), [
|
||||
['street-level-panel', true],
|
||||
['cesiumContainer', true],
|
||||
['location-search', true],
|
||||
['owned', true],
|
||||
]);
|
||||
const wrap = dom.document.body.querySelector('#sl-viewer-wrap');
|
||||
assert.notEqual(wrap.inert, true, 'the viewer itself stays live');
|
||||
// Shrinking returns focus into the panel: it must be live again first.
|
||||
dom.root.querySelector('#sl-status').focus();
|
||||
controls.setViewerExpanded(false);
|
||||
assert.deepEqual(inertOutside(dom), [
|
||||
['street-level-panel', false],
|
||||
['cesiumContainer', false],
|
||||
['location-search', false],
|
||||
['owned', true],
|
||||
]);
|
||||
controls.destroy();
|
||||
}),
|
||||
));
|
||||
|
||||
test('a CSS-hidden expanded viewer releases the app; showing it again, and new children, go inert (IC8 P2)', () =>
|
||||
withObserver((observer) =>
|
||||
withDom(async (dom) => {
|
||||
const { controls } = stubPanel(dom, uiState({ open: true }));
|
||||
controls.setViewerExpanded(true);
|
||||
assert.equal(observer.watching(), true);
|
||||
const wrap = dom.document.body.querySelector('#sl-viewer-wrap');
|
||||
let visible = true;
|
||||
wrap.checkVisibility = () => visible;
|
||||
// Clean View (or recording, or the cockpit) hides the viewer by CSS.
|
||||
visible = false;
|
||||
dom.document.body.classList.add('ui-clean-view');
|
||||
observer.fire();
|
||||
assert.ok(
|
||||
inertOutside(dom).every(([, inert]) => !inert),
|
||||
'a hidden modal leaves nothing inert',
|
||||
);
|
||||
// Leaving the mode shows the viewer again: the app is inert once more,
|
||||
// and so is a child added to <body> meanwhile.
|
||||
visible = true;
|
||||
const toast = dom.document.body.appendChild(
|
||||
new dom.globe.constructor(dom.document, 'div', { id: 'toast' }),
|
||||
);
|
||||
observer.fire();
|
||||
assert.ok(inertOutside(dom).every(([, inert]) => inert));
|
||||
assert.equal(toast.inert, true);
|
||||
controls.destroy();
|
||||
assert.ok(
|
||||
inertOutside(dom).every(([, inert]) => !inert),
|
||||
'destroy releases the app',
|
||||
);
|
||||
assert.equal(observer.watching(), false, 'and stops watching');
|
||||
}),
|
||||
));
|
||||
|
||||
test('an image closed while expanded releases the app (IC8 P2)', () =>
|
||||
withObserver(() =>
|
||||
withDom(async (dom) => {
|
||||
const { layer, controls } = stubPanel(dom, uiState({ open: true }));
|
||||
controls.setViewerExpanded(true);
|
||||
assert.ok(inertOutside(dom).every(([, inert]) => inert));
|
||||
layer.publish(uiState({ open: false }));
|
||||
assert.equal(controls.isViewerExpanded(), false);
|
||||
assert.ok(inertOutside(dom).every(([, inert]) => !inert));
|
||||
assert.equal(
|
||||
dom.document.activeElement,
|
||||
dom.root.querySelector('#sl-status'),
|
||||
);
|
||||
controls.destroy();
|
||||
}),
|
||||
));
|
||||
|
||||
test('FIT / FILL show the selection while the viewer is expanded (P3)', () =>
|
||||
withDom(async (dom) => {
|
||||
const { layer, controls } = stubPanel(dom, uiState({ open: true }));
|
||||
|
||||
Reference in new issue
Block a user