From dfb017c1b10cb52c24f4f0bcf745c6abea11b98f Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 7 Oct 2026 08:27:40 -0700 Subject: Feed, composer and zap tests follow today's app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feed tests reach the Feed from the bottom nav (the chat-home row is gone) and seed this wallet's last feed as Following (new wallets open on Popular, c89050e) plus the author's profile (readiness gate) - post-actions: its picture is made locally (it read another session's scratchpad), the post's own ⋯ (not the feed header's), a real tap on the DM picture (a tap beside a fitted picture is a tap on the post) - composer-media / video-compress: intercept every public media host (ditto is first now; the tests were uploading for real) - compose-preview: Preview is one toggle button (aria-pressed) - thread-reply: harness has a wallet (else visitor: Reply asks to sign in), registers , holds the root's id-only query; atomic send tap - video-unmute: stubs what videoNode reaches outside its slice; preload none means the in-view clip is told to load - zap-deep-profile: zap button scrolled to mid-screen (the bottom nav covered it) Co-Authored-By: Claude Opus 5.5 --- tools/thread-reply-test.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'tools/thread-reply-test.js') diff --git a/tools/thread-reply-test.js b/tools/thread-reply-test.js index 4362468..16b6f50 100644 --- a/tools/thread-reply-test.js +++ b/tools/thread-reply-test.js @@ -7,6 +7,7 @@ const dom = app.slice(app.indexOf('function h(tag,'), app.indexOf('// ---------- const entry = ` import { messagesFeature } from './src/features/messages.js'; import { createThreadStore } from './src/thread-cache.js'; +import './src/rich-text.js'; // , the reply field (app.js registers it) ${dom} const author = 'a'.repeat(64), mine = 'b'.repeat(64), rootId = 'c'.repeat(64); const root = { id: rootId, pubkey: author, kind: 1, content: 'Root post', tags: [], created_at: 1 }; @@ -14,10 +15,12 @@ const reply = { ...root, id: 'd'.repeat(64), content: 'The reply you tapped', ta const other = { ...reply, id: 'e'.repeat(64), content: 'Another reply not returned by the relay', created_at: 3 }; const ui = { screen: 'wallet' }, waiting = []; window.sent = []; -window.query = filter => filter.kinds?.[0] === 1 && (filter.ids || filter['#e']) ? new Promise(resolve => waiting.push({ filter, resolve })) : Promise.resolve([]); +// the root (asked for by id, any kind) and its replies are held until the test lets them go +window.query = filter => filter.ids || (filter.kinds?.[0] === 1 && filter['#e']) ? new Promise(resolve => waiting.push({ filter, resolve })) : Promise.resolve([]); const signer = { signEvent: async e => ({ ...e, id: 'f'.repeat(64), pubkey: mine }) }; let feature; -const wallet = { loaded: true, nostrRelays: () => [], loadFeatureState: (k, d) => d, saveFeatureState() {} }; +// (a wallet is open: without one the app is a visitor's, and Reply asks you to sign in) +const wallet = { xpub: 'test-wallet', loaded: true, nostrRelays: () => [], loadFeatureState: (k, d) => d, saveFeatureState() {} }; function render() { if (!feature) return; document.querySelector('#app').replaceChildren(feature.screenView() || feature.row(author, reply, 'Someone')); @@ -26,6 +29,7 @@ feature = messagesFeature({ h, wallet, ui, render, toast() {}, brandHeader: () = ? h('div', { style: 'height:60px' }) : null, hook: k => k === 'nostrLoginIdentity' ? { pubkey: mine, signer } : null }); window.test = { ui, reply, other, root, render, waiting, showNav: false, + held: () => waiting.some(q => q.filter.ids) && waiting.some(q => q.filter['#e']), 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(), @@ -75,10 +79,12 @@ try { check('Reply immediately shows its composer beneath the tapped post', await inline()); check('the input receives focus before relay queries finish', await page.evaluate(() => document.activeElement.matches('.thread-reply-input'))); await page.type('.thread-reply-input', 'A reply before the root loads'); - await page.click('.thread-reply-send'); + // (one atomic tap: a repaint landing between finding the button and clicking it replaces it) + await page.waitForFunction(() => !document.querySelector('.thread-reply-send')?.disabled); + await page.evaluate(() => document.querySelector('.thread-reply-send').click()); await page.waitForFunction(() => sent.length === 1); check('publishing targets the selected reply and its original root', await page.evaluate(() => sent[0].tags.some(t => t[0] === 'e' && t[1] === test.reply.id && t[3] === 'reply') && sent[0].tags.some(t => t[0] === 'e' && t[1] === test.root.id && t[3] === 'root'))); - await page.waitForFunction(() => test.waiting.length === 2); + await page.waitForFunction(() => test.held()); await page.evaluate(() => test.release()); await page.waitForFunction(() => document.body.innerText.includes('Root post')); // the composer folds away once the reply is sent; the tapped reply itself stays @@ -90,7 +96,7 @@ try { check('Reply leaves the covering profile and inserts an uncached reply into the thread', await inline() && await page.evaluate(() => !test.ui.profOverThread && document.activeElement.matches('.thread-reply-input'))); await page.evaluate(() => test.missing()); await page.click('#outside .note-act'); - await page.waitForFunction(() => test.waiting.length === 2); + await page.waitForFunction(() => test.held()); await page.evaluate(() => test.empty()); await page.waitForFunction(() => !document.querySelector('#app .spinner')); check('an unavailable root still leaves the selected post replyable', await inline()); -- cgit v1.2.3