diff options
| author | Adam Soltys <[email protected]> | 2026-09-24 08:33:46 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-24 08:33:46 -0700 |
| commit | 189c1a3d24b859d3ebf351cff24bbec788a0251a (patch) | |
| tree | 13e52ea66f136ccf753114f98b297acd64981d4f /tools | |
| parent | cdca3fa061c856f9063aadfabf1c1e713f512123 (diff) | |
| download | coinosv3-189c1a3d24b859d3ebf351cff24bbec788a0251a.tar.gz coinosv3-189c1a3d24b859d3ebf351cff24bbec788a0251a.zip | |
Preserve restored thread state across rapid refreshes
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/boot-signin-flash-test.js | 17 | ||||
| -rw-r--r-- | tools/thread-reply-test.js | 17 |
2 files changed, 27 insertions, 7 deletions
diff --git a/tools/boot-signin-flash-test.js b/tools/boot-signin-flash-test.js index 6e22ebb..75f5b50 100644 --- a/tools/boot-signin-flash-test.js +++ b/tools/boot-signin-flash-test.js @@ -66,15 +66,19 @@ try { await page.evaluate(({ rootId, replyId, author }) => { const root = { id: rootId, pubkey: author, kind: 1, content: 'Cached thread root', tags: [], created_at: 1 }; const reply = { ...root, id: replyId, content: 'Cached thread reply', tags: [['e', rootId, '', 'root']], created_at: 2 }; - localStorage.setItem('btc-wallet-note-threads', JSON.stringify([{ rootId, root, replies: [reply], profiles: {}, counts: {} }])); + const replies = [reply, ...Array.from({ length: 24 }, (_, i) => ({ ...reply, + id: (i + 20).toString(16).padStart(64, '0'), content: 'Another cached reply '.repeat(8), created_at: i + 3 }))]; + localStorage.setItem('btc-wallet-note-threads', JSON.stringify([{ rootId, root, replies, profiles: {}, counts: {} }])); const nav = { ...history.state.nav, screen: 'wallet', noteThread: { rootId, focusId: replyId, seed: reply } }; history.replaceState({ nav, i: 0 }, '', location.href); }, { rootId, replyId, author }); await page.evaluateOnNewDocument(() => { - window.__threadBoot = { homeFrames: 0, animatedFrames: 0, threadAt: null }; + window.__threadBoot = { homeFrames: 0, partialHeaderFrames: 0, animatedFrames: 0, maxScroll: 0, threadAt: null }; const tick = () => { const state = window.__threadBoot; + state.maxScroll = Math.max(state.maxScroll, window.scrollY); if (document.querySelector('.balance')) state.homeFrames++; + if (document.querySelector('.brand') && !document.querySelector('.header-avatar')) state.partialHeaderFrames++; const thread = document.querySelector('.thread-page'); if (thread?.classList.contains('anim-page')) state.animatedFrames++; if (thread) state.threadAt ??= Math.round(performance.now()); @@ -83,10 +87,15 @@ try { requestAnimationFrame(tick); }); await page.reload({ waitUntil: 'domcontentloaded' }); + await sleep(250); + await page.evaluate(() => window.scrollTo(0, 0)); + await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(1200); const thread = await page.evaluate(() => window.__threadBoot); - const steady = thread.threadAt != null && !thread.homeFrames && !thread.animatedFrames; - console.log(` ${steady ? '✓' : '✗'} thread reload: ${thread.homeFrames} home frames, ${thread.animatedFrames} page animation frames`); + const atTop = await page.evaluate(() => window.scrollY === 0); + const steady = thread.threadAt != null && !thread.homeFrames && !thread.partialHeaderFrames && !thread.animatedFrames + && !thread.maxScroll && atTop; + console.log(` ${steady ? '✓' : '✗'} rapid second thread reload: ${thread.homeFrames} home frames, ${thread.partialHeaderFrames} partial-header frames, ${thread.animatedFrames} page animation frames, max scroll ${thread.maxScroll}px`); if (!steady) ok = false; } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ no sign-in flash' : '\n❌ the sign-in page flashed'); diff --git a/tools/thread-reply-test.js b/tools/thread-reply-test.js index 3dba87f..37c22b4 100644 --- a/tools/thread-reply-test.js +++ b/tools/thread-reply-test.js @@ -22,9 +22,11 @@ function render() { if (!feature) return; document.querySelector('#app').replaceChildren(feature.screenView() || feature.row(author, reply, 'Someone')); } -feature = messagesFeature({ h, wallet, ui, render, toast() {}, brandHeader: () => null, +feature = messagesFeature({ h, wallet, ui, render, toast() {}, brandHeader: () => window.test?.showNav + ? h('div', { style: 'height:60px' }) : null, hook: k => k === 'nostrLoginIdentity' ? { pubkey: mine, signer } : null }); -window.test = { ui, reply, other, root, render, waiting, +window.test = { ui, reply, other, root, render, waiting, showNav: false, + stable: () => feature.testStable(), seedFace: () => createThreadStore().save({ root, replies: [reply] }, reply.id, { [author]: { name: 'Nostrich', picture: 'https://example.com/nostrich.webp', eventAt: 10, t: Date.now(), thumbFor: 'https://example.com/nostrich.webp', thumb: 'data:image/png;base64,iVBORw0KGgo=', thumbPx: 144 }, @@ -46,7 +48,7 @@ const bundle = await Bun.build({ entrypoints: ['thread-test-entry'], target: 'br build.onResolve({ filter: /^thread-test-entry$/ }, () => ({ path: 'entry', namespace: 'thread-test' })); build.onLoad({ filter: /.*/, namespace: 'thread-test' }, () => ({ contents: entry, loader: 'js', resolveDir: process.cwd() })); build.onLoad({ filter: /src\/features\/messages\.js$/ }, async ({ path }) => ({ loader: 'js', contents: (await Bun.file(path).text()) - .replace("id: 'messages',", "id: 'messages', row: noteRow, actions: noteActions,") })); + .replace("id: 'messages',", "id: 'messages', row: noteRow, actions: noteActions, testStable: renderThreadStable,") })); build.onLoad({ filter: /src\/nostr\.js$/ }, async ({ path }) => ({ loader: 'js', contents: (await Bun.file(path).text()) .replace('export async function queryOn(relays, filter, maxWait = 1500) {', 'export async function queryOn(relays, filter, maxWait = 1500) { return window.query(filter);') .replace('export async function publishOn(relays, evt) {', 'export async function publishOn(relays, evt) { window.sent.push(evt); return true;') @@ -114,6 +116,15 @@ try { const buttons = row?.querySelectorAll('.note-acts > button'); return buttons?.[1].textContent === '7' && buttons?.[3].textContent === '29' && buttons?.[4].textContent === '210'; })); + await page.evaluate(() => { + document.querySelector('#outside').style.height = '2000px'; + window.scrollTo(0, 0); + test.showNav = true; + test.stable(); + }); + await new Promise((r) => setTimeout(r, 80)); + check('adding header controls while at the top does not scroll to the first post', + await page.evaluate(() => window.scrollY === 0)); assert.deepEqual(errors, []); console.log('✓ no browser errors'); } finally { await browser.close(); server.stop(true); } |
