diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 38be0445a..1e9cc00ad 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -2796,8 +2796,12 @@ the settings follow. SINCE is a stepped range input (any date, 10, 5, 3, 2 and 1 years, 6, 3 and 1 months) whose readout names the cut-off date. The panel is portable (`street-level-panel` spec in `PanelPositionControls`, minimum 320 × 280, `dockOnCollapse`): in a resized window the viewer takes the spare -height and only `.sl-settings` scrolls, and collapsing the window or SHRINK -after EXPAND docks it at its default size. +height and only `.sl-settings` scrolls, and a user's collapse, a header +double-click or SHRINK after EXPAND docks it at its default size (cockpit +entry, restores and the Cyber accordion never dock a floating window). At +phone width (≤720 px) the rail is the band between mid-screen and the credit +line, so the viewer's height is derived from that band instead of 16:10 and +the whole photo and caption fit without scrolling. Street-level providers implement the contract documented in `src/layers/streetLevel/registry.js`: a definition (`id`, `name`, `label`, diff --git a/scripts/qa-street-level.mjs b/scripts/qa-street-level.mjs index a06a9aa18..f18aaeb4e 100644 --- a/scripts/qa-street-level.mjs +++ b/scripts/qa-street-level.mjs @@ -621,6 +621,63 @@ async function main() { }); }, ); + await step( + 'on a phone the whole photo fits in the docked panel', + async () => { + await page.setViewport({ + width: 390, + height: 844, + isMobile: true, + hasTouch: true, + }); + await sleep(800); + if ( + await page.$eval('#street-level-panel', (node) => + node.classList.contains('collapsed'), + ) + ) + await page.click( + '.panel-collapse-btn[data-collapse-target="street-level-panel"]', + ); + await page.evaluate((id) => { + void window.__godsEyeView.dataManager.layers + .get('street-level') + .module.openImage('mapillary', id); + }, firstImageId); + await page.waitForFunction( + () => { + const s = window.__godsEyeView.dataManager.layers + .get('street-level') + .module.getUIState().street; + return (s.imageId && !s.loading) || s.error; + }, + { timeout: 90_000 }, + ); + await sleep(600); + const fit = await page.evaluate(() => { + const inner = document + .querySelector('.street-level-panel-inner') + .getBoundingClientRect(); + const meta = document + .getElementById('sl-image-meta') + .getBoundingClientRect(); + const viewer = document + .getElementById('sl-viewer') + .getBoundingClientRect(); + return { + cut: Math.round(meta.bottom - inner.bottom), + top: Math.round(viewer.top - inner.top), + height: Math.round(viewer.height), + overflowX: document.documentElement.scrollWidth > innerWidth, + }; + }); + assert.ok(fit.cut <= 1, `photo and caption fit (${fit.cut}px cut)`); + assert.ok(fit.height >= 100, `viewer stays usable (${fit.height}px)`); + assert.equal(fit.overflowX, false, 'no sideways scroll'); + await page.setViewport(VIEWPORTS[0]); + await sleep(600); + }, + ); await step('no page errors', () => { assert.deepEqual(errors, []); }); diff --git a/src/streetLevelMarkup.test.mjs b/src/streetLevelMarkup.test.mjs index 320c0d55b..9e327bec7 100644 --- a/src/streetLevelMarkup.test.mjs +++ b/src/streetLevelMarkup.test.mjs @@ -122,6 +122,19 @@ test('labels say what the buttons do', () => { ); }); +test('on phones the viewer is sized from the rail band, not its aspect ratio', () => { + assert.match( + panelCss, + /@media \(max-width: 720px\) \{\s*\.sl-viewer \{[^}]*height: clamp\(/, + ); + // Floating and expanded viewers must not inherit the phone height. + for (const selector of [ + ".panel-floating\\[style\\*='height'\\] \\.sl-viewer", + '\\.sl-viewer-wrap-expanded \\.sl-viewer', + ]) + assert.match(panelCss, new RegExp(`${selector} \\{[^}]*height: auto`)); +}); + test('the viewer comes first and the panel is a portable, resizable window', () => { const controlsBlock = html.slice(html.indexOf('