diff options
| author | Adam Soltys <[email protected]> | 2026-10-07 08:39:07 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-07 08:39:07 -0700 |
| commit | 6c51dcf2542314d5fe883f756f0840ef51860d67 (patch) | |
| tree | 88f66bae1e8015219c4d31614ba09454af30cc1e | |
| parent | 8dfc629d129c6c1a8a3ed38a935e659eee9f67c7 (diff) | |
| download | coinosv3-6c51dcf2542314d5fe883f756f0840ef51860d67.tar.gz coinosv3-6c51dcf2542314d5fe883f756f0840ef51860d67.zip | |
Feed tests (port 5298 group): bottom-nav Feed, Following seeded, today's prepend and editor
- feed-list, markdown-note: open the Feed tab, on Following; feed-list opens
the editor from the feed's ⋯ menu
- feed-stability: the warm-up query asks kinds 1+1068 (polls); a live post with
the header on screen goes in above the first post, held still (it no longer
waits); the per-frame drift check uses a second post once the header is away
- relays-settings: a new identity puts out its DM inbox list at boot, and the
page has other Save buttons; the no-change save clears the log and clicks
the card's own Save
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | tools/feed-list-test.js | 10 | ||||
| -rw-r--r-- | tools/feed-stability-test.js | 53 | ||||
| -rw-r--r-- | tools/markdown-note-test.js | 6 | ||||
| -rw-r--r-- | tools/relays-settings-test.js | 4 |
4 files changed, 44 insertions, 29 deletions
diff --git a/tools/feed-list-test.js b/tools/feed-list-test.js index bd88846..641d43d 100644 --- a/tools/feed-list-test.js +++ b/tools/feed-list-test.js @@ -71,9 +71,8 @@ const type = async (sel, text) => { await page.click(sel); await page.type(sel, text); }; const openFeed = async () => { - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(800); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => (n.querySelector('.chat-name') || {}).textContent === 'Feed'); if (e) e.click(); }); + // the Feed is a tab in the bottom nav (it was a row on the chat home) + await page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed')?.click()); await sleep(1200); }; @@ -97,6 +96,7 @@ try { await page.evaluate(([k, pk, ns]) => { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a wallet with no feed remembered opens on Popular }, [base, AUTHOR, seeded]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); @@ -149,7 +149,9 @@ try { const d2 = pub2 && (pub2.tags.find((x) => x[0] === 'd') || [])[1]; console.log('\n[unticking removes the list from the relays; the feed stays]'); - await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => /edit feed/i.test(b.getAttribute('aria-label') || '')).click()); await sleep(400); + // the editor lives in the feed's ⋯ menu now + await page.click('[aria-label="Feed options"]'); await sleep(300); + await page.evaluate(() => [...document.querySelectorAll('.msg-sheet button')].find((b) => /edit feed/i.test(b.textContent)).click()); await sleep(400); check('the editor has the list toggle on', await page.evaluate(() => { const c = document.querySelector('.feed-list-toggle'); return c && c.checked; })); await page.evaluate(() => { window.__published = []; document.querySelector('.feed-list-toggle').click(); }); await click('save'); await sleep(1500); 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); diff --git a/tools/markdown-note-test.js b/tools/markdown-note-test.js index e27336f..8e7cca1 100644 --- a/tools/markdown-note-test.js +++ b/tools/markdown-note-test.js @@ -69,12 +69,12 @@ try { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Markdown Poster', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // a wallet with no feed remembered opens on Popular }, [cacheKeyFor(mn + '\n'), AUTHOR, notes]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /message/i.test(e.getAttribute('aria-label') || '')); if (b) b.click(); }); - await sleep(1000); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => /feed/i.test(n.textContent)); if (e) e.click(); }); + // the Feed is a tab in the bottom nav (it was a row on the chat home) + await page.evaluate(() => [...document.querySelectorAll('.app-nav-button')].find((b) => b.getAttribute('aria-label') === 'Feed')?.click()); // rows are admitted once their pictures have been tried (a dead host takes the deadline) for (let i = 0; i < 60; i++) { if ((await page.evaluate(() => document.querySelectorAll('.notes-feed > .row').length)) >= 6) break; await sleep(250); } await sleep(500); diff --git a/tools/relays-settings-test.js b/tools/relays-settings-test.js index 7045b21..df89388 100644 --- a/tools/relays-settings-test.js +++ b/tools/relays-settings-test.js @@ -94,7 +94,9 @@ try { check('...and the DM inbox list', JSON.stringify(await dms()) === JSON.stringify(['relay.coinos.io', 'inbox.example.com']), JSON.stringify(await dms())); console.log('\n[editing, then saving]'); - await click('save'); await sleep(400); + // (a new identity has already put out its DM inbox list at boot; the card's + // Save is .relays-save — the page has other Save buttons now) + await page.evaluate(() => { window.__published = []; document.querySelector('.relays-save').click(); }); await sleep(400); check('saving with nothing changed publishes nothing', !(await page.evaluate(() => window.__published)).some((e) => e.kind === 10002 || e.kind === 10050)); // nos.lol becomes read+write; relay.example.com goes; a bare host is added await page.evaluate(() => { |
