aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-07 08:39:07 -0700
committerAdam Soltys <[email protected]>2026-10-07 08:39:07 -0700
commit6c51dcf2542314d5fe883f756f0840ef51860d67 (patch)
tree88f66bae1e8015219c4d31614ba09454af30cc1e
parent8dfc629d129c6c1a8a3ed38a935e659eee9f67c7 (diff)
downloadcoinosv3-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.js10
-rw-r--r--tools/feed-stability-test.js53
-rw-r--r--tools/markdown-note-test.js6
-rw-r--r--tools/relays-settings-test.js4
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(() => {