diff options
| author | Adam Soltys <[email protected]> | 2026-10-07 08:40:21 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-07 08:40:21 -0700 |
| commit | 3aff6e4cc9eb97fc0fa488f1f95001f69f24330a (patch) | |
| tree | 932ed498851f9e2167a1c1419c96d630be0d02e2 /tools/feed-stability-test.js | |
| parent | 6d288aeb194d2765282cf9fd72bd1f5fdd747a71 (diff) | |
| parent | 6c51dcf2542314d5fe883f756f0840ef51860d67 (diff) | |
| download | coinosv3-3aff6e4cc9eb97fc0fa488f1f95001f69f24330a.tar.gz coinosv3-3aff6e4cc9eb97fc0fa488f1f95001f69f24330a.zip | |
Merge branch 'worktree-agent-ad1d843e9e394974e'
Diffstat (limited to 'tools/feed-stability-test.js')
| -rw-r--r-- | tools/feed-stability-test.js | 53 |
1 files changed, 32 insertions, 21 deletions
diff --git a/tools/feed-stability-test.js b/tools/feed-stability-test.js index 8b969f1..1426bba 100644 --- a/tools/feed-stability-test.js +++ b/tools/feed-stability-test.js @@ -83,6 +83,8 @@ await page.evaluateOnNewDocument((fixtures) => { }, { profile, quote }); const click = (text) => page.evaluate((text) => [...document.querySelectorAll('button')].find((e) => e.textContent.toLowerCase().includes(text))?.click(), text); const row = (id) => `.notes-feed > [data-key="${id}"]`; +// the Feed is a tab in the bottom nav (it was a row on the chat home) +const feedTab = () => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed'); const snapshot = () => page.evaluate(() => [...document.querySelectorAll('.notes-feed > [data-zap-post]')].map((n) => { const b = n.getBoundingClientRect(); return { id: n.dataset.key, top: b.top, height: b.height, text: n.innerText, avatar: n.querySelector('.note-avatar')?.getAttribute('style') }; @@ -103,15 +105,14 @@ try { localStorage.setItem(base + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(base + ':feedNotes', JSON.stringify(seeded)); localStorage.setItem(base + ':profiles', JSON.stringify({ [pk]: { name: 'Cached Author', picture: origin + '/avatar.png', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + base, 'following'); // a wallet with no feed remembered opens on Popular }, [base, pk, seeded, origin]); await page.reload(); await page.waitForFunction(() => document.body.innerText.toLowerCase().includes('receive')); await sleep(2000); check('a small feed query starts before opening the feed', await page.evaluate(() => - !document.querySelector('.notes-feed') && (window.__feedRequests || []).some((f) => f.kinds?.length === 1 && f.kinds[0] === 1 && f.limit === 10))); - await page.evaluate(() => [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || ''))?.click()); - await page.waitForSelector('.item'); - await page.evaluate(() => [...document.querySelectorAll('.item')].find((e) => /feed/i.test(e.textContent))?.click()); + !document.querySelector('.notes-feed') && (window.__feedRequests || []).some((f) => f.kinds?.includes(1) && !f.kinds.some((k) => k !== 1 && k !== 1068) && f.limit === 10))); + await page.evaluate((tab) => eval(tab)()?.click(), feedTab.toString()); await sleep(1000); // Cached posts paint at once — never a spinner over posts we hold — and // settle into their prepared presentation once faces and pictures are in. @@ -127,21 +128,30 @@ try { const settledRows = new Map((await snapshot()).map((r) => [r.id, r])); check('late image responses do not change admitted rows', initial.every((r) => JSON.stringify(r) === JSON.stringify(settledRows.get(r.id)))); - // With the header visible, a prepend must wait: anchoring only the first - // post would otherwise scroll the header offscreen. - const headerBefore = await page.evaluate(() => ({ y: scrollY, text: document.querySelector('.chat-page > .row').innerText })); + // With the header visible, a new post goes in above the first post, which + // is held where it is (the page grows upward; the header scrolls off by + // what went in above it). + const firstBefore = await page.evaluate(() => { const r = document.querySelector('.notes-feed > [data-zap-post]'); return { id: r.dataset.key, top: r.getBoundingClientRect().top }; }); const fresh = event('A new live post', now + 1); check('a live subscription received the event', await page.evaluate((e) => window.__inject(e), fresh) > 0); - await sleep(1000); - check('live arrival leaves the visible header and posts untouched', await page.evaluate((before) => - scrollY === before.y && document.querySelector('.chat-page > .row').innerText === before.text, headerBefore)); - check('new post waits until it can be inserted offscreen', !(await page.$(row(fresh.id)))); + await page.waitForSelector(row(fresh.id), { timeout: 5000 }).catch(() => {}); + check('live arrival at the top holds the first post still', await page.evaluate((b) => { + const r = document.querySelector('.notes-feed > [data-key="' + b.id + '"]'); + return !!r && Math.abs(r.getBoundingClientRect().top - b.top) <= 1; + }, firstBefore)); + check('...and goes in above it', await page.evaluate(([f, id]) => { + const a = document.querySelector('.notes-feed > [data-key="' + f + '"]'), b = document.querySelector('.notes-feed > [data-key="' + id + '"]'); + return !!a && !!b && a.getBoundingClientRect().top < b.getBoundingClientRect().top; + }, [fresh.id, firstBefore.id])); - // Once the reader scrolls the header away, preserve every visible row on - // every animation frame, including the first frame of the prepend. + // Once the reader has scrolled the header away, preserve every visible row + // on every animation frame, including the first frame of the prepend. await page.evaluate(() => { - const first = document.querySelector('.notes-feed > [data-zap-post]'); + const first = [...document.querySelectorAll('.notes-feed > [data-zap-post]')][1]; scrollBy(0, Math.ceil(first.getBoundingClientRect().top)); + }); + await sleep(500); + await page.evaluate(() => { window.__drift = []; window.__held = [...document.querySelectorAll('.notes-feed > [data-zap-post]')].filter((r) => { const b = r.getBoundingClientRect(); return b.bottom > 0 && b.top < innerHeight; @@ -152,11 +162,13 @@ try { if (window.__sampling) requestAnimationFrame(frame); }; requestAnimationFrame(frame); }); - await page.waitForSelector(row(fresh.id)); + const fresh2 = event('Another live post', now + 2); + check('a second live post is received', await page.evaluate((e) => window.__inject(e), fresh2) > 0); + await page.waitForSelector(row(fresh2.id)); await sleep(500); const drift = await page.evaluate(() => { window.__sampling = false; return window.__drift; }); check('offscreen arrival never moves a visible row, even for one frame', !drift.length, JSON.stringify(drift.slice(0, 3))); - check('new rows do not animate their height', await page.$eval(row(fresh.id), (n) => !n.getAnimations().length)); + check('new rows do not animate their height', await page.$eval(row(fresh2.id), (n) => !n.getAnimations().length)); const gap = event('Gap inside visible posts', now - 630); await page.evaluate((e) => { @@ -206,12 +218,11 @@ try { }, [base, seeded.filter((_, i) => ![1, 3, 8, 12].includes(i)).slice(0, 10)]); await page.reload(); await sleep(5500); - await page.evaluate(() => [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || ''))?.click()); - await page.waitForSelector('.item'); - check('first opening after background preparation has rows and no spinner', await page.evaluate(() => { - [...document.querySelectorAll('.item')].find((e) => /feed/i.test(e.textContent))?.click(); + await page.waitForSelector('.app-nav-button'); + check('first opening after background preparation has rows and no spinner', await page.evaluate((tab) => { + eval(tab)()?.click(); return !!document.querySelector('.notes-feed > .row') && !document.querySelector('.chat-page .spinner'); - })); + }, feedTab.toString())); check('no browser errors', !errors.length, errors.join(' | ')); } finally { await browser.close(); server.stop(true); } process.exit(ok ? 0 : 1); |
