diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 00:14:38 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 00:14:38 +0200 |
| commit | 3443c1c65e9f1753e1e656b35d08416c1fa298f2 (patch) | |
| tree | 4e43236f460145a4d75d1b4616dcb7aa6ef08f51 /viewer/tools/smoke/mobile.mjs | |
| download | worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.tar.gz worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.zip | |
worldmap-viewer: initial public history
Diffstat (limited to 'viewer/tools/smoke/mobile.mjs')
| -rw-r--r-- | viewer/tools/smoke/mobile.mjs | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/viewer/tools/smoke/mobile.mjs b/viewer/tools/smoke/mobile.mjs new file mode 100644 index 0000000..6c94dee --- /dev/null +++ b/viewer/tools/smoke/mobile.mjs @@ -0,0 +1,48 @@ +// mobile.mjs — the viewer on a phone screen: controls fit without overlapping, a tap opens the inspector sheet, +// the ☰ menu holds the layer picker, map buttons are finger-sized. SHOTS=<dir> (default /tmp): screenshots. +// LANDSCAPE=1: the phone on its side. +const dir = process.env.SHOTS || '/tmp'; +const land = !!process.env.LANDSCAPE; +const tag = land ? 'landscape' : 'portrait'; +export const viewport = land ? { width: 844, height: 390 } : { width: 390, height: 844 }; +const overlap = `const vis = id => { const e = document.getElementById(id); if (!e || e.hidden) return null; + const b = e.getBoundingClientRect(); return b.width && b.height && getComputedStyle(e).display !== 'none' ? b : null; }; + const ids = ['toolbar', 'panel', 'nav', 'status', 'legend', 'exag-box', 'skybar'].filter(vis); + const bad = []; + for (let i = 0; i < ids.length; i++) for (let j = i + 1; j < ids.length; j++) { + const a = vis(ids[i]), b = vis(ids[j]); + if (a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom) bad.push(ids[i] + 'x' + ids[j]); + } + const off = ids.filter(id => { const b = vis(id); return b.left < 0 || b.right > innerWidth + 1 || b.top < 0 || b.bottom > innerHeight + 1; }); + const covered = ids.reduce((s, id) => { const b = vis(id); return s + b.width * b.height; }, 0) / (innerWidth * innerHeight); + return { bad, off, covered: +covered.toFixed(2) };`; +const tap = `const c = document.querySelector('#map canvas'), b = c.getBoundingClientRect(), x = b.left + b.width * 0.3, y = b.top + b.height * 0.35; + for (const t of ['pointerdown', 'pointerup']) c.dispatchEvent(new PointerEvent(t, { clientX: x, clientY: y, bubbles: true, pointerId: 1, pointerType: 'touch', isPrimary: true, button: 0 })); + c.dispatchEvent(new MouseEvent('click', { clientX: x, clientY: y, bubbles: true })); + await new Promise(r => setTimeout(r, 1500));`; +export const steps = [ + { name: 'boots on a phone', wait: 5000, expect: v => v.ok && !v.toast && v.compact, + js: `return { ok: !!window.mapViewer?.texture, toast: document.getElementById('toast').textContent, + compact: document.body.classList.contains('compact') };` }, + { name: `${tag}: controls do not overlap or leave the screen, the map stays visible`, shot: `${dir}/phone-${tag}.png`, + expect: v => !v.bad.length && !v.off.length && v.covered < 0.35, js: overlap }, + { name: 'tap the map: the inspector shows', shot: `${dir}/phone-${tag}-inspect.png`, expect: v => v.visible, + js: `${tap} + const s = document.getElementById('tab-inspector'), r = s.getBoundingClientRect(); + return { visible: !s.hidden && r.height > 40 && r.top < innerHeight - 40, text: s.textContent.slice(0, 40) };` }, + { name: 'panel open: still no overlaps', expect: v => !v.bad.length && !v.off.length, js: overlap }, + { name: 'the menu holds the layer picker and fits the screen', shot: `${dir}/phone-${tag}-menu.png`, + expect: v => v.layer && v.fits, + js: `document.getElementById('menu-toggle').click(); await new Promise(r => setTimeout(r, 300)); + const l = document.getElementById('layer').getBoundingClientRect(), t = document.getElementById('toolbar').getBoundingClientRect(); + return { layer: l.width > 150 && l.height >= 36, fits: t.bottom <= innerHeight };` }, + { name: 'a tap on the map closes the menu', expect: v => v === false, + js: `const c = document.querySelector('#map canvas'); + c.dispatchEvent(new PointerEvent('pointerdown', { clientX: 200, clientY: 300, bubbles: true, pointerId: 2, pointerType: 'touch' })); + c.dispatchEvent(new PointerEvent('pointerup', { clientX: 200, clientY: 300, bubbles: true, pointerId: 2, pointerType: 'touch' })); + return document.body.classList.contains('menu-open');` }, + { name: 'layer textures fit a phone GPU (≤ 4096 px wide)', expect: v => v > 0 && v <= 4096, + js: `return window.mapViewer.texture?.image?.width ?? 0;` }, + { name: 'map buttons are finger-sized', expect: v => v.length >= 4 && v.every(b => b.w >= 40 && b.h >= 40), + js: `return [...document.querySelectorAll('#nav button')].filter(b => b.offsetParent).map(b => ({ w: b.offsetWidth, h: b.offsetHeight }));` }, +]; |
