diff options
| author | Adam Soltys <[email protected]> | 2026-10-13 06:17:39 +0000 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-13 06:17:39 +0000 |
| commit | 8f80948eb174a273f3b1dea963bb91745bcf323b (patch) | |
| tree | a8e704dd6896be17df41c7f844e21f18be5fdb92 /tools | |
| parent | b330cc707fab62365c29b57d414c46aa198febef (diff) | |
| download | coinosv3-8f80948eb174a273f3b1dea963bb91745bcf323b.tar.gz coinosv3-8f80948eb174a273f3b1dea963bb91745bcf323b.zip | |
Media pager: up and down go from post to post, left and right along a post's pictures. Adam: swipes and arrows up/down should go to the next post's media, left/right to the next within a post. The pager is now posts stacked top to bottom, each a row of its pictures or clips side by side — a quoted post's join the post that quotes it — with dots along the top when a post has more than one, saying how many and which. Up and down (swipe, arrows, j/k, Page) move a post at a time and land on a post where its row was left; left and right (swipe, arrows, h/l) move within the post and stop at its ends. A post's pictures all load as it comes near (those along its row too); a clip's head start goes to the next post's (where its row rests), the next along this one, and the post after next, at half; the look-ahead (the feed paging on, the media crawl) counts posts. Pictures pan sideways as well as up and down at 1×. Tests find the slide on screen by where it sits (the pager's children are posts now); feed-picture-pager walks right within a post, down past one with none, back up to where a post was left, left to its first, and a sideways swipe next to a swipe up; quote-pager walks the quoting post's row (its own picture, then the quote's).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01KPw21NEpWeTGu6swVASmBt
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/feed-media-views-test.js | 8 | ||||
| -rw-r--r-- | tools/feed-pager-ahead-test.js | 20 | ||||
| -rw-r--r-- | tools/feed-picture-pager-test.js | 35 | ||||
| -rw-r--r-- | tools/feed-video-pager-test.js | 2 | ||||
| -rw-r--r-- | tools/pager-tail-test.js | 2 | ||||
| -rw-r--r-- | tools/quote-pager-test.js | 25 | ||||
| -rw-r--r-- | tools/video-touch-tap-test.js | 2 |
7 files changed, 52 insertions, 42 deletions
diff --git a/tools/feed-media-views-test.js b/tools/feed-media-views-test.js index 6649a93..2c84b14 100644 --- a/tools/feed-media-views-test.js +++ b/tools/feed-media-views-test.js @@ -95,8 +95,8 @@ const pagerState = () => page.evaluate(() => { const p = document.querySelector('.media-pager'); if (!p) return null; const sc = p.querySelector('.mp-scroll'); - const slides = [...sc.querySelectorAll(':scope > .mp-slide')]; // (not the loading screen at the end) - const i = Math.round(sc.scrollTop / sc.clientHeight); + const slides = [...sc.querySelectorAll('.mp-slide')]; // (not the loading screen at the end) + const i = slides.findIndex((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); // the slide on screen const vids = slides.map((s) => s.querySelector('video')); return { n: slides.length, i, src: (slides[i]?.querySelector('.mp-media')?.getAttribute('data-src') || '').split('/').pop(), playing: vids.map((v) => !!(v && !v.paused)), show: p.classList.contains('show'), @@ -145,12 +145,12 @@ try { await page.evaluate(() => { const p = document.querySelector('.media-pager'); p.querySelector('.mp-slide').dispatchEvent(new MouseEvent('click', { bubbles: true })); }); await sleep(300); st = await pagerState(); - const meta = await page.evaluate(() => { const s = [...document.querySelectorAll('.mp-slide')][Math.round(document.querySelector('.mp-scroll').scrollTop / document.querySelector('.mp-scroll').clientHeight)]; return { author: s.querySelector('.mp-author > span:last-child').textContent, caption: s.querySelector('.mp-caption')?.textContent }; }); + const meta = await page.evaluate(() => { const s = [...document.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return { author: s.querySelector('.mp-author > span:last-child').textContent, caption: s.querySelector('.mp-caption')?.textContent }; }); check('a tap shows the author and the words, without the links', st.show && st.meta === '1' && meta.author === 'Shutterbug' && meta.caption === 'two at once', JSON.stringify(meta)); await page.keyboard.press('ArrowUp'); await sleep(900); st = await pagerState(); check('↑ goes to the previous picture', st.src === 'p1.jpg', JSON.stringify(st)); - check('its like shows in the corner', await waitFor(() => { const p = document.querySelector('.media-pager'); const sc = p.querySelector('.mp-scroll'); const s = sc.children[Math.round(sc.scrollTop / sc.clientHeight)]; return s.querySelector('.mp-like .n').textContent === '1'; }, 6000)); + check('its like shows in the corner', await waitFor(() => { const p = document.querySelector('.media-pager'); const sc = p.querySelector('.mp-scroll'); const s = [...document.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return s.querySelector('.mp-like .n').textContent === '1'; }, 6000)); await shot('pager-meta'); await page.goBack(); await sleep(600); check('Back closes the pager, leaving the grid', await page.evaluate(() => !document.querySelector('.media-pager') && !!document.querySelector('.media-grid'))); diff --git a/tools/feed-pager-ahead-test.js b/tools/feed-pager-ahead-test.js index 90cbe96..057ac3e 100644 --- a/tools/feed-pager-ahead-test.js +++ b/tools/feed-pager-ahead-test.js @@ -70,10 +70,10 @@ const waitFor = async (fn, arg, ms = 10000) => { for (let i = 0; i < ms / 100; i // the slide the pager rests on, by its picture const curPic = () => page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); if (!sc) return null; - const s = [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); + const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return s ? s.getAttribute('data-k').split('|')[1].split('/').pop() : 'between'; }); -const resting = async (name) => waitFor((n) => { const sc = document.querySelector('.mp-scroll'); if (!sc) return false; const s = [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return !!s && s.getAttribute('data-k').endsWith('/' + n); }, name, 4000); +const resting = async (name) => waitFor((n) => { const sc = document.querySelector('.mp-scroll'); if (!sc) return false; const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return !!s && s.getAttribute('data-k').endsWith('/' + n); }, name, 4000); const openPic = async (word) => { await page.evaluate((w) => { const r = [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes(w)); r.scrollIntoView({ block: 'center' }); }, word); await sleep(400); @@ -82,12 +82,12 @@ const openPic = async (word) => { await waitFor(() => !!document.querySelector('.media-pager'), null, 4000); return at.y0; }; -const tapPager = async () => { const at = await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4) || sc.firstChild; const r = s.querySelector('img.mp-media').getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }); await page.mouse.click(at.x, at.y); }; +const tapPager = async () => { const at = await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }) || sc.firstChild; const r = s.querySelector('img.mp-media').getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }); await page.mouse.click(at.x, at.y); }; const rowTop = (word) => page.evaluate((w) => { const r = [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes(w)); return r ? Math.round(r.getBoundingClientRect().top) : null; }, word); -const slides = () => page.evaluate(() => document.querySelectorAll('.mp-scroll > .mp-slide').length); -const curIdx = () => page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); if (!sc) return -1; return [...sc.querySelectorAll(':scope > .mp-slide')].findIndex((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); }); -const curName = () => page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = sc && [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return s ? s.getAttribute('data-k').split('/').pop() : null; }); +const slides = () => page.evaluate(() => document.querySelectorAll('.mp-scroll .mp-slide').length); +const curIdx = () => page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); if (!sc) return -1; return [...sc.querySelectorAll('.mp-slide')].findIndex((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); }); +const curName = () => page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = sc && [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return s ? s.getAttribute('data-k').split('/').pop() : null; }); try { await page.setViewport({ width: 390, height: 844 }); @@ -128,11 +128,11 @@ try { maxSlides = Math.max(maxSlides, n); if (i >= 0 && n - 1 - i <= 4) { // give the look-ahead a moment: it should get past three ahead again (or the feed's end) - await waitFor((k) => { const sc = document.querySelector('.mp-scroll'); const j = [...sc.querySelectorAll(':scope > .mp-slide')].findIndex((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return sc.querySelectorAll(':scope > .mp-slide').length - 1 - j > 4 || sc.querySelectorAll(':scope > .mp-slide').length >= k; }, TOTAL, 6000); + await waitFor((k) => { const sc = document.querySelector('.mp-scroll'); const j = [...sc.querySelectorAll('.mp-slide')].findIndex((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return sc.querySelectorAll('.mp-slide').length - 1 - j > 4 || sc.querySelectorAll('.mp-slide').length >= k; }, TOTAL, 6000); } const before = await curIdx(); await page.keyboard.press('ArrowDown'); - await waitFor((b) => { const sc = document.querySelector('.mp-scroll'); return [...sc.querySelectorAll(':scope > .mp-slide')].findIndex((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4) > b; }, before, 2000); + await waitFor((b) => { const sc = document.querySelector('.mp-scroll'); return [...sc.querySelectorAll('.mp-slide')].findIndex((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }) > b; }, before, 2000); } // past the last picture is the pager's end (its loading screen): back up onto the last if (await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const t = sc.querySelector('.mp-tail'); return t && Math.abs(t.offsetTop - sc.scrollTop) < 4; })) { await page.keyboard.press('ArrowUp'); await sleep(800); } @@ -140,11 +140,11 @@ try { check('the pager grew as the end came near', grew, first + ' → ' + (await slides())); check('every picture in the feed became reachable', (await slides()) === TOTAL, (await slides()) + ' of ' + TOTAL); check('…past fifteen posts with none', (await curName()) === 'p' + (TOTAL - 1) + '.png', await curName()); - check('…in the feed\'s order', await page.evaluate((t) => [...document.querySelectorAll('.mp-scroll > .mp-slide')].every((s, i) => s.getAttribute('data-k').endsWith('/p' + i + '.png')), TOTAL)); + check('…in the feed\'s order', await page.evaluate((t) => [...document.querySelectorAll('.mp-scroll .mp-slide')].every((s, i) => s.getAttribute('data-k').endsWith('/p' + i + '.png')), TOTAL)); console.log('\n[closing lands the feed on the last picture\'s post]'); await page.keyboard.press('ArrowUp'); - await waitFor((n) => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return s && s.getAttribute('data-k').endsWith('/p' + n + '.png'); }, TOTAL - 2, 3000); + await waitFor((n) => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return s && s.getAttribute('data-k').endsWith('/p' + n + '.png'); }, TOTAL - 2, 3000); const want = 'picture number ' + (TOTAL - 2) + ' '; await page.keyboard.press('Escape'); check('the pager closes', await waitFor(() => !document.querySelector('.media-pager') && !!document.querySelector('.notes-feed'), null, 3000)); diff --git a/tools/feed-picture-pager-test.js b/tools/feed-picture-pager-test.js index 6c45756..32625c9 100644 --- a/tools/feed-picture-pager-test.js +++ b/tools/feed-picture-pager-test.js @@ -44,10 +44,10 @@ const waitFor = async (fn, arg, ms = 10000) => { for (let i = 0; i < ms / 100; i // the slide the pager rests on, by its picture const curPic = () => page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); if (!sc) return null; - const s = [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); + const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return s ? s.getAttribute('data-k').split('|')[1].split('/').pop() : 'between'; }); -const resting = async (name) => waitFor((n) => { const sc = document.querySelector('.mp-scroll'); if (!sc) return false; const s = [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return !!s && s.getAttribute('data-k').endsWith('/' + n); }, name, 4000); +const resting = async (name) => waitFor((n) => { const sc = document.querySelector('.mp-scroll'); if (!sc) return false; const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return !!s && s.getAttribute('data-k').endsWith('/' + n); }, name, 4000); const openPic = async (word) => { await page.evaluate((w) => { const r = [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes(w)); r.scrollIntoView({ block: 'center' }); }, word); await sleep(400); @@ -56,7 +56,7 @@ const openPic = async (word) => { await waitFor(() => !!document.querySelector('.media-pager'), null, 4000); return at.y0; }; -const tapPager = async () => { const at = await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4) || sc.firstChild; const r = s.querySelector('img.mp-media').getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }); await page.mouse.click(at.x, at.y); }; +const tapPager = async () => { const at = await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }) || sc.firstChild; const r = s.querySelector('img.mp-media').getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }); await page.mouse.click(at.x, at.y); }; const rowTop = (word) => page.evaluate((w) => { const r = [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes(w)); return r ? Math.round(r.getBoundingClientRect().top) : null; }, word); try { @@ -90,25 +90,31 @@ try { check('one tap closes it', await waitFor(() => !document.querySelector('.media-pager'), null, 3000)); check('the feed is where it was', Math.abs((await page.evaluate(() => window.scrollY)) - y0) <= 2); - console.log('\n[arrows walk the pictures, a post\'s own first]'); + console.log('\n[left and right: along a post\'s pictures; up and down: post to post]'); await openPic('two pictures'); + check('a post with two pictures shows two dots, the first lit', await page.evaluate(() => { const d = document.querySelector('.mp-group .mp-dots'); return d && d.children.length === 2 && d.children[0].classList.contains('on'); })); + await page.keyboard.press('ArrowRight'); + check('right: the post\'s second picture', await resting('a2.png'), await curPic()); + check('…its dot lit', await page.evaluate(() => { const d = document.querySelector('.mp-group .mp-dots'); return d.children[1].classList.contains('on') && !d.children[0].classList.contains('on'); })); + await page.keyboard.press('ArrowRight'); await sleep(600); + check('right at the post\'s last: stays (left and right stay in the post)', (await curPic()) === 'a2.png', await curPic()); await page.keyboard.press('ArrowDown'); - check('down: the post\'s second picture', await resting('a2.png'), await curPic()); + check('down: the next post\'s picture', await resting('b.png'), await curPic()); await page.keyboard.press('ArrowDown'); - check('down again: the next post\'s picture', await resting('b.png'), await curPic()); - await page.keyboard.press('ArrowRight'); - check('right: the next picture, past a post with none', await resting('d.png'), await curPic()); - await page.keyboard.press('ArrowLeft'); - check('left: back one', await resting('b.png'), await curPic()); + check('down again: the next, past a post with none', await resting('d.png'), await curPic()); await page.keyboard.press('ArrowUp'); - check('up: back one more', await resting('a2.png'), await curPic()); + check('up: back one post', await resting('b.png'), await curPic()); + await page.keyboard.press('ArrowUp'); + check('up again: the first post, on the picture it was left at', await resting('a2.png'), await curPic()); + await page.keyboard.press('ArrowLeft'); + check('left: its first picture', await resting('a1.png'), await curPic()); check('no history entry per step', await page.evaluate(() => history.length) < 50); console.log('\n[moved on: a tap shows the post, the next closes onto it]'); await page.keyboard.press('ArrowDown'); await resting('b.png'); await tapPager(); await sleep(400); check('the first tap shows the details', await page.evaluate(() => document.querySelector('.media-pager')?.classList.contains('show'))); - check('…with the author and the words', await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return /Picture Poster/.test(s.textContent) && /one picture/.test(s.textContent); })); + check('…with the author and the words', await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return /Picture Poster/.test(s.textContent) && /one picture/.test(s.textContent); })); await tapPager(); check('the second tap closes it', await waitFor(() => !document.querySelector('.media-pager') && !!document.querySelector('.notes-feed'), null, 3000)); await waitFor((w) => { const r = [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.textContent.includes(w)); return r && r.getBoundingClientRect().top < 120 && r.getBoundingClientRect().top > -10; }, 'one picture', 3000); @@ -117,9 +123,10 @@ try { console.log('\n[a swipe, then Escape]'); await page.evaluate(() => window.scrollTo(0, 0)); await sleep(300); await openPic('two pictures'); + await page.evaluate(() => { const r = document.querySelector('.mp-row'); r.scrollTo({ left: r.clientWidth }); }); // what a swipe to the left does + check('a sideways swipe lands on the post\'s next picture', await resting('a2.png'), await curPic()); await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); sc.scrollTo({ top: sc.clientHeight }); }); // what a swipe up does - check('the swipe lands on the next picture', await resting('a2.png'), await curPic()); - await page.keyboard.press('ArrowDown'); await resting('b.png'); + check('a swipe up lands on the next post\'s', await resting('b.png'), await curPic()); await page.keyboard.press('ArrowDown'); await resting('d.png'); await page.keyboard.press('Escape'); check('Escape closes it', await waitFor(() => !document.querySelector('.media-pager') && !!document.querySelector('.notes-feed'), null, 3000)); diff --git a/tools/feed-video-pager-test.js b/tools/feed-video-pager-test.js index ab5aa2a..9b014f9 100644 --- a/tools/feed-video-pager-test.js +++ b/tools/feed-video-pager-test.js @@ -57,7 +57,7 @@ page.on('pageerror', (e) => errs.push(String(e))); const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await page.evaluate(() => document.body.innerText)).toLowerCase().includes(x)) return true; await sleep(250); } return false; }; const waitFor = async (fn, arg, ms = 10000) => { for (let i = 0; i < ms / 100; i++) { if (await page.evaluate(fn, arg)) return true; await sleep(100); } return false; }; -const CUR = () => { const sc = document.querySelector('.mp-scroll'); return sc && [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); }; +const CUR = () => { const sc = document.querySelector('.mp-scroll'); return sc && [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); }; const curClip = () => page.evaluate((cur) => { const s = eval(cur)(); return s ? s.getAttribute('data-k').split('|')[1].split('/').pop() : null; }, '(' + CUR + ')'); const resting = (name) => waitFor(([cur, n]) => { const s = eval(cur)(); return !!s && s.getAttribute('data-k').endsWith('/' + n); }, ['(' + CUR + ')', name], 5000); const curVideo = () => page.evaluate((cur) => { const s = eval(cur)(); const v = s && s.querySelector('video.mp-media'); return v ? { t: v.currentTime, d: v.duration, paused: v.paused, src: !!v.getAttribute('src') } : null; }, '(' + CUR + ')'); diff --git a/tools/pager-tail-test.js b/tools/pager-tail-test.js index c9d26cc..b294b8f 100644 --- a/tools/pager-tail-test.js +++ b/tools/pager-tail-test.js @@ -72,7 +72,7 @@ const click = (t) => page.evaluate((x) => { const e = [...document.querySelector const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await page.evaluate(() => document.body.innerText)).toLowerCase().includes(x)) return true; await sleep(250); } return false; }; const waitFor = async (fn, arg, ms = 10000) => { for (let i = 0; i < ms / 100; i++) { if (await page.evaluate(fn, arg)) return true; await sleep(100); } return false; }; // what the pager rests on: a clip's name, or 'tail' -const AT = () => { const sc = document.querySelector('.mp-scroll'); if (!sc) return null; const s = [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return !s ? 'between' : s.classList.contains('mp-tail') ? 'tail' : s.getAttribute('data-k').split('/').pop().replace('.webm', ''); }; +const AT = () => { const sc = document.querySelector('.mp-scroll'); if (!sc) return null; const s = [...sc.querySelectorAll('.mp-slide, .mp-tail')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return !s ? 'between' : s.classList.contains('mp-tail') ? 'tail' : s.getAttribute('data-k').split('/').pop().replace('.webm', ''); }; const at = () => page.evaluate(AT); const resting = (name, ms = 5000) => waitFor(([f, n]) => eval(f)() === n, ['(' + AT + ')', name], ms); const playing = (name) => waitFor((n) => { const s = [...document.querySelectorAll('.mp-slide')].find((x) => (x.getAttribute('data-k') || '').endsWith('/' + n + '.webm')); const v = s && s.querySelector('video.mp-media'); return v && !v.paused && v.currentTime > 0; }, name, 6000); diff --git a/tools/quote-pager-test.js b/tools/quote-pager-test.js index 2a0e33b..5d9e536 100644 --- a/tools/quote-pager-test.js +++ b/tools/quote-pager-test.js @@ -1,8 +1,8 @@ // Pictures in a quoted post are in the pager too. A post quoting another // draws the quoted one inside it; a tap on a picture there opened it alone // (the pager's list held only the feed posts' own pictures), with nothing to -// swipe to. Now the pager has, after each post's own pictures, those of the -// posts it quotes: swipes walk through them and on to the next post's, the +// swipe to. Now a post's row in the pager has, after its own pictures, those +// of the posts it quotes: left and right walk them, down goes on to the next post's, the // quoted picture's details name its author, and closing lands on the post // that quoted it. // Run: bun tools/quote-pager-test.js @@ -69,7 +69,7 @@ page.on('pageerror', (e) => errs.push(String(e))); const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await page.evaluate(() => document.body.innerText)).toLowerCase().includes(x)) return true; await sleep(250); } return false; }; const waitFor = async (fn, arg, ms = 10000) => { for (let i = 0; i < ms / 100; i++) { if (await page.evaluate(fn, arg)) return true; await sleep(100); } return false; }; -const AT = () => { const sc = document.querySelector('.mp-scroll'); if (!sc) return null; const s = [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return s ? s.getAttribute('data-k').split('/').pop().replace('.png', '') : 'between'; }; +const AT = () => { const sc = document.querySelector('.mp-scroll'); if (!sc) return null; const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return s ? s.getAttribute('data-k').split('/').pop().replace('.png', '') : 'between'; }; const at = () => page.evaluate(AT); const resting = (n) => waitFor(([f, x]) => eval(f)() === x, ['(' + AT + ')', n], 4000); @@ -102,19 +102,22 @@ try { await page.mouse.click(pos2.x, pos2.y); check('it opens in the pager (not alone)', await waitFor(() => !!document.querySelector('.media-pager'), null, 4000) && !(await page.evaluate(() => !!document.querySelector('.lightbox')))); check('…on the quoted picture', await resting('q1'), await at()); - await page.keyboard.press('ArrowDown'); - check('down: the quoted post\'s second picture', await resting('q2'), await at()); + // the quote's pictures are in its post's row: its own first, then the quote's + check('…in the post\'s row, three along', await page.evaluate(() => document.querySelector('.mp-group .mp-dots').children.length === 3)); + await page.keyboard.press('ArrowRight'); + check('right: the quoted post\'s second picture', await resting('q2'), await at()); await page.keyboard.press('ArrowDown'); check('down: on to the next post\'s', await resting('b'), await at()); - await page.keyboard.press('ArrowUp'); await resting('q2'); - await page.keyboard.press('ArrowUp'); await resting('q1'); await page.keyboard.press('ArrowUp'); - check('up past the quote: the quoting post\'s own picture, before it', await resting('a'), await at()); - await page.keyboard.press('ArrowDown'); await resting('q1'); - await page.keyboard.press('ArrowDown'); await resting('q2'); + check('up: back to the quoting post, where it was left', await resting('q2'), await at()); + await page.keyboard.press('ArrowLeft'); await resting('q1'); + await page.keyboard.press('ArrowLeft'); + check('left past the quote: the quoting post\'s own picture, before it', await resting('a'), await at()); + await page.keyboard.press('ArrowRight'); await resting('q1'); + await page.keyboard.press('ArrowRight'); await resting('q2'); // details: a tap (moved on from the start, so the first tap shows them) await page.mouse.click(195, 300); await sleep(400); - check('the quoted picture\'s details name its author', await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll(':scope > .mp-slide')].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); return /Store Maker/.test(s.textContent) && /two screenshots/.test(s.textContent); })); + check('the quoted picture\'s details name its author', await page.evaluate(() => { const sc = document.querySelector('.mp-scroll'); const s = [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); return /Store Maker/.test(s.textContent) && /two screenshots/.test(s.textContent); })); await page.keyboard.press('Escape'); check('closing it', await waitFor(() => !document.querySelector('.media-pager') && !!document.querySelector('.notes-feed'), null, 3000)); await sleep(1500); diff --git a/tools/video-touch-tap-test.js b/tools/video-touch-tap-test.js index 6a45c9e..71ffe9c 100644 --- a/tools/video-touch-tap-test.js +++ b/tools/video-touch-tap-test.js @@ -111,7 +111,7 @@ try { await sleep(1200); check('…or land on the pager (pausing the clip, raising the details)', await page.evaluate(() => { const p = document.querySelector('.media-pager'); const sc = p && p.querySelector('.mp-scroll'); - const cur = sc && [...sc.children].find((x) => Math.abs(x.offsetTop - sc.scrollTop) < 4); + const cur = sc && [...sc.querySelectorAll('.mp-slide')].find((x) => { const r = x.getBoundingClientRect(); return Math.abs(r.top) < 4 && Math.abs(r.left) < 4; }); const v = cur && cur.querySelector('video.mp-media'); return !!p && !p.classList.contains('show') && !!v && !v.paused; })); |
