diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/carousel-flick-test.js | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/tools/carousel-flick-test.js b/tools/carousel-flick-test.js new file mode 100644 index 0000000..81e3028 --- /dev/null +++ b/tools/carousel-flick-test.js @@ -0,0 +1,103 @@ +// Exercise the production carousel handlers and DOM morph with native browser +// scrolling; wallet/network data is replaced by two fixed account faces. +import assert from 'node:assert/strict'; +import puppeteer from './puppeteer.js'; +const source = await Bun.file('src/app.js').text(); +const dom = source.slice(source.indexOf('function h('), source.indexOf('const root = document.getElementById')); +const carousel = source.slice(source.indexOf(' if (carouselMode) {'), source.indexOf(" const dtRaw = animWindow('acct'")); +const end = source.slice(source.indexOf('function endCarouselDrag()'), source.indexOf('function renderCrash(')); +const tab = source.slice(source.indexOf(" const TAB_ORDER = ['history', 'receive', 'send'];"), source.indexOf(" return h(\n 'div',", source.indexOf(" const TAB_ORDER = ['history', 'receive', 'send'];"))); +const animation = source.slice(source.indexOf('const _uiSeen ='), source.indexOf('// A balance that counts')); +const js = `${dom} +${animation} +let _carDragging = false, _renderDeferred = false, _accDir = null; +const ui = { account: 'spending', tab: 'history' }, ACCOUNT_KEY = 'test-account'; +const accountSel = () => ui.account; +const root = document.getElementById('app'); +window.changes = []; +function balanceCard() { + const carouselMode = true; + const sel = accountSel(), faceFor = (v) => h('div', { style: 'height:110px' }, v), cardExtras = () => []; + ${carousel} +} +${end} +function tabContent() { return h('div', { style: 'height:350px' }, ui.account + ':' + ui.tab); } +function render() { + if (_carDragging) { _renderDeferred = true; return; } + ${tab} + const next = h('div', {}, balanceCard(), pane); + if (root.firstChild) morph(root.firstChild, next); else root.append(next); + if (changes.at(-1) !== ui.account) changes.push(ui.account); +} +window.state = () => ({ account: ui.account, changes: [...changes], frozen: !!document.querySelector('.bal-carousel')._skipMorph }); +render();`; +const css = await Bun.file('src/style.css').text(); +const html = `<!doctype html><style>${css}\n#app { width:400px; margin:40px auto; }</style><div id="app"></div><script>${js}</script>`; +const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'Content-Type': 'text/html' } }) }); +const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] }); +try { + const page = await browser.newPage(); + const errors = []; + page.on('pageerror', (e) => errors.push(e.message)); + await page.goto(server.url.href); + await page.waitForSelector('.bal-carousel'); + await Bun.sleep(400); + for (const injectStaleEnd of [false, true]) { + for (const direction of [-1, 1]) { + await page.evaluate(() => { changes.length = 0; changes.push(ui.account); }); + const box = await page.$eval('.bal-carousel', (e) => { const r = e.getBoundingClientRect(); return { x: r.x + r.width/2, y: r.y + 70 }; }); + await page.mouse.move(box.x, box.y); await page.mouse.down(); + await page.mouse.move(box.x + direction * 30, box.y); + await page.mouse.move(box.x + direction * 75, box.y); + if (injectStaleEnd) { + // A queued scrollend from the final direct scrollLeft write arrives + // after release, before the first smooth-scroll animation frame. + await page.evaluate(() => window.addEventListener('pointerup', () => { + document.querySelector('.bal-carousel').dispatchEvent(new Event('scrollend')); + }, { once: true })); + } + await page.mouse.up(); + await Bun.sleep(950); + const state = await page.evaluate(() => window.state()); + console.log({ injectStaleEnd, direction, ...state }); + assert.deepEqual(state.changes, direction < 0 ? ['spending', 'savings'] : ['savings', 'spending'], 'one content transition per flick'); + assert.equal(state.frozen, false); + } + } + // A small slow pull stays on the same card and still releases the freeze. + await page.evaluate(() => { + changes.length = 0; changes.push(ui.account); + const el = document.querySelector('.bal-carousel'); + el.dispatchEvent(new PointerEvent('pointerdown', { pointerType: 'mouse', button: 0, clientX: 300, bubbles: true })); + window.dispatchEvent(new PointerEvent('pointermove', { clientX: 290 })); + window.dispatchEvent(new PointerEvent('pointerup')); + }); + await Bun.sleep(800); + assert.deepEqual((await page.evaluate(() => window.state())).changes, ['spending']); + assert.equal((await page.evaluate(() => window.state())).frozen, false); + // Simulate a browser dropping the requested smooth scroll entirely: the + // fallback must finish the intended switch, not unfreeze at the old card. + await page.evaluate(() => { + const el = document.querySelector('.bal-carousel'); + el.scrollTo = function (opts) { if (opts.behavior !== 'smooth') Element.prototype.scrollTo.call(this, opts); }; + el.dispatchEvent(new PointerEvent('pointerdown', { pointerType: 'mouse', button: 0, clientX: 300, bubbles: true })); + window.dispatchEvent(new PointerEvent('pointermove', { clientX: 220 })); + window.dispatchEvent(new PointerEvent('pointerup')); + }); + await Bun.sleep(850); + assert.deepEqual((await page.evaluate(() => window.state())).changes, ['spending', 'savings']); + assert.equal((await page.evaluate(() => window.state())).frozen, false); + await page.evaluate(() => { delete document.querySelector('.bal-carousel').scrollTo; }); + // Grab again before a glide finishes, and hold beyond its old fallback. + const box = await page.$eval('.bal-carousel', (el) => { const r = el.getBoundingClientRect(); return { x: r.x + 150, y: r.y + 70 }; }); + await page.mouse.move(box.x, box.y); await page.mouse.down(); + await page.mouse.move(box.x + 80, box.y); await page.mouse.up(); + await page.mouse.down(); await page.mouse.move(box.x + 60, box.y); + await Bun.sleep(800); + assert.equal((await page.evaluate(() => window.state())).frozen, true, 'old fallback cannot unfreeze a new drag'); + await page.mouse.up(); await Bun.sleep(850); + assert.equal((await page.evaluate(() => window.state())).frozen, false); + assert.deepEqual(errors, []); + console.log('✓ short pull, missing scrollend/glide fallback and re-grab hold'); + console.log('✓ fast flicks in both directions switch content once, including a stale drag scrollend'); +} finally { await browser.close(); server.stop(true); } |
