diff options
| author | Adam Soltys <[email protected]> | 2026-10-07 08:27:40 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-07 08:27:40 -0700 |
| commit | dfb017c1b10cb52c24f4f0bcf745c6abea11b98f (patch) | |
| tree | b8150ed4805f206947e2475fe433be953c46fb70 /tools | |
| parent | b70e169bb5b5beccb89fd59427726e32d414b4a5 (diff) | |
| download | coinosv3-dfb017c1b10cb52c24f4f0bcf745c6abea11b98f.tar.gz coinosv3-dfb017c1b10cb52c24f4f0bcf745c6abea11b98f.zip | |
Feed, composer and zap tests follow today's app
- 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 <coinos-text>, 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 <[email protected]>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/compose-preview-test.js | 11 | ||||
| -rw-r--r-- | tools/composer-media-test.js | 10 | ||||
| -rw-r--r-- | tools/image-tap-test.js | 3 | ||||
| -rw-r--r-- | tools/link-preview-test.js | 5 | ||||
| -rw-r--r-- | tools/post-actions-test.js | 19 | ||||
| -rw-r--r-- | tools/quote-card-test.js | 3 | ||||
| -rw-r--r-- | tools/thread-reply-test.js | 16 | ||||
| -rw-r--r-- | tools/video-autoplay-test.js | 5 | ||||
| -rw-r--r-- | tools/video-compress-test.js | 4 | ||||
| -rw-r--r-- | tools/video-unmute-test.js | 6 | ||||
| -rw-r--r-- | tools/youtube-embed-test.js | 9 | ||||
| -rw-r--r-- | tools/zap-deep-profile-test.js | 2 |
12 files changed, 60 insertions, 33 deletions
diff --git a/tools/compose-preview-test.js b/tools/compose-preview-test.js index 2138ea6..c943655 100644 --- a/tools/compose-preview-test.js +++ b/tools/compose-preview-test.js @@ -53,6 +53,8 @@ try { await page.evaluate(([k, n]) => { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', n.pubkey]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify([n])); + localStorage.setItem(k + ':profiles', JSON.stringify({ [n.pubkey]: { name: 'fiatjaf', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, { id: 'f'.repeat(63) + '1', pubkey: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d', kind: 1, created_at: Math.floor(Date.now() / 1000) - 60, content: 'a post to answer', tags: [], @@ -61,10 +63,8 @@ try { await waitText('receive', 20000); // into the feed, where posts are written - 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(); }); - await sleep(2500); + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); + for (let i = 0; i < 60 && !(await page.$('.notes-feed:not([data-booting]) > .row')); i++) await sleep(250); const opened = await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((n) => (n.getAttribute('aria-label') || n.textContent).trim().toLowerCase() === 'new post'); if (b) { b.click(); return true; } return false; }); check('the composer opens', opened && !!(await page.$('coinos-text'))); @@ -98,8 +98,9 @@ try { /plus a word/.test(await page.evaluate(() => (document.querySelector('.draft-preview') || {}).innerText || ''))); // and it puts itself away + // (the same Preview button, pressed: a toggle) await page.evaluate(() => [...document.querySelectorAll('button')] - .find((b) => /hide preview/i.test(b.getAttribute('aria-label') || ''))?.click()); + .find((b) => /preview/i.test(b.getAttribute('aria-label') || '') && b.getAttribute('aria-pressed') === 'true')?.click()); await sleep(500); check('and it closes again', !(await page.$('.draft-preview'))); diff --git a/tools/composer-media-test.js b/tools/composer-media-test.js index 9b2b41c..6f30995 100644 --- a/tools/composer-media-test.js +++ b/tools/composer-media-test.js @@ -17,16 +17,19 @@ const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome const page = await browser.newPage(); const uploads = []; await page.setRequestInterception(true); +// every public media host the upload may try first (nothing leaves the machine) +const HOSTS = /blossom\.ditto\.pub|blossom\.primal\.net|nostr\.download|nostr\.build/; page.on('request', (req) => { const u = req.url(); - if (/nostr\.download|nostr\.build/.test(u) && req.method() === 'PUT') { + if (HOSTS.test(u) && req.method() === 'PUT') { uploads.push(u); return req.respond({ status: 200, contentType: 'application/json', headers: { 'access-control-allow-origin': '*' }, body: JSON.stringify({ url: 'https://nostr.download/' + 'f'.repeat(64) + '.gif', sha256: 'f'.repeat(64), size: 10, type: 'image/gif' }) }); } - if (/nostr\.download|nostr\.build/.test(u) && req.method() === 'OPTIONS') { + if (HOSTS.test(u) && req.method() === 'OPTIONS') { return req.respond({ status: 204, headers: { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'PUT, HEAD, GET', 'access-control-allow-headers': '*' } }); } + if (HOSTS.test(u) && req.method() === 'HEAD') return req.respond({ status: 200, headers: { 'access-control-allow-origin': '*' } }); req.continue(); }); const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); @@ -50,10 +53,11 @@ try { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', a]], c: '', at: now })); localStorage.setItem(k + ':feedNotes', JSON.stringify([{ id: 'c'.repeat(64), pubkey: a, kind: 1, created_at: now, content: 'a post to answer', tags: [] }])); localStorage.setItem(k + ':profiles', JSON.stringify({ [a]: { name: 'Poster', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, A]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => document.querySelector('.app-bottom-nav .app-nav-button').click()); + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); await waitText('a post to answer', 20000); await page.evaluate(() => [...document.querySelectorAll('.notes-feed [data-key]')].find((n) => n.innerText.includes('a post to answer')).querySelector('button[aria-label="Reply"]').click()); check('the reply box appears', await waitFor(() => !!document.querySelector('.thread-reply-input'), 10000)); diff --git a/tools/image-tap-test.js b/tools/image-tap-test.js index c531bba..5ed182e 100644 --- a/tools/image-tap-test.js +++ b/tools/image-tap-test.js @@ -38,10 +38,11 @@ try { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', a]], c: '', at: now })); localStorage.setItem(k + ':feedNotes', JSON.stringify([{ id: 'c'.repeat(64), pubkey: a, kind: 1, created_at: now, content: 'a tall one http://localhost:5281/tall.png', tags: [] }])); localStorage.setItem(k + ':profiles', JSON.stringify({ [a]: { name: 'Poster', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, A]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => document.querySelector('.app-bottom-nav .app-nav-button').click()); + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); check('the picture is in the post', await waitFor(() => { const i = document.querySelector('.notes-feed img.note-img'); return i && i.complete && i.naturalWidth > 0; }, 20000)); // on the phone the box spans the column and the picture sits in its middle // (object-fit: contain); desktop Chrome sizes the box to the picture, so diff --git a/tools/link-preview-test.js b/tools/link-preview-test.js index d90dbd2..0b9e863 100644 --- a/tools/link-preview-test.js +++ b/tools/link-preview-test.js @@ -89,12 +89,11 @@ try { await page.evaluate(([k, pk]) => { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Linker', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, APK]); 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(500); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => /feed/i.test(n.textContent)); if (e) e.click(); }); + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); await page.waitForSelector('.notes-feed:not([data-booting]) > .row', { timeout: 30000 }); await sleep(800); const rows = await page.evaluate(() => [...document.querySelectorAll('.notes-feed > .row')].map((r) => ({ diff --git a/tools/post-actions-test.js b/tools/post-actions-test.js index 17f8372..4c5d92e 100644 --- a/tools/post-actions-test.js +++ b/tools/post-actions-test.js @@ -16,7 +16,10 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); let ok = true; const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; }; const html = await buildHtml({ minify: true, pwa: false }); -const png = await Bun.file('/tmp/claude-1000/-home-adam-coinosv3/61dea255-d26d-411d-be8b-92dfb0a75887/scratchpad/big.png').arrayBuffer().catch(() => null); +// a picture served locally (a remote one that fails to load stays a link) +const PNG = '/tmp/coinos-post-actions-test.png'; +Bun.spawnSync(['ffmpeg', '-y', '-loglevel', 'error', '-f', 'lavfi', '-i', 'color=c=green:s=600x400', '-frames:v', '1', PNG]); +const png = await Bun.file(PNG).arrayBuffer().catch(() => null); const server = Bun.serve({ port: 5258, async fetch(req) { const u = new URL(req.url); if (u.pathname === '/pic.png' && png) return new Response(png, { headers: { 'content-type': 'image/png' } }); @@ -54,12 +57,12 @@ try { ])); // a known face: a row is admitted once its author's picture has been tried localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Flower Poster', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, AUTHOR, NOTE_ID]); 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 + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); for (let i = 0; i < 48; i++) { if (await page.$('.note-act')) break; await sleep(250); } await sleep(600); @@ -105,7 +108,7 @@ try { await sleep(1500); // the ellipsis sheet, and muting from it - await page.evaluate(() => { const b = [...document.querySelectorAll('.btn-sm')].find((x) => x.textContent.trim() === '⋯'); if (b) b.click(); }); + await page.evaluate(() => { const b = [...document.querySelectorAll('.notes-feed .btn-sm')].find((x) => x.textContent.trim() === '⋯'); if (b) b.click(); }); await sleep(600); const sheet = await page.evaluate(() => { const s = document.querySelector('.confirm-pop'); return s ? s.innerText.replace(/\n+/g, ' | ') : ''; }); check('the ellipsis offers mute and block', /Mute/.test(sheet) && /Block/.test(sheet), sheet.slice(0, 70)); @@ -139,7 +142,11 @@ try { return 'opened'; }); await sleep(1200); - const img = await page.evaluate(() => { const i = document.querySelector('.chat-bubble img.note-img'); if (!i) return null; i.click(); return true; }); + // a real tap on the picture itself (a tap beside a fitted picture is a tap on the post) + for (let i = 0; i < 20 && !(await page.evaluate(() => { const i = document.querySelector('.chat-bubble img.note-img'); return i && i.complete && i.naturalWidth > 0; })); i++) await sleep(250); + const at = await page.evaluate(() => { const i = document.querySelector('.chat-bubble img.note-img'); if (!i) return null; i.scrollIntoView({ block: 'center' }); const b = i.getBoundingClientRect(); return { x: b.left + b.width / 2, y: b.top + b.height / 2 }; }); + const img = !!at; + if (at) await page.mouse.click(at.x, at.y); await sleep(600); check('an image in a DM opens full screen too', !!img && await page.evaluate(() => !!document.querySelector('.lightbox')), img ? 'tapped' : 'no image in the bubble (' + opened + ')'); diff --git a/tools/quote-card-test.js b/tools/quote-card-test.js index d2d7d7e..2cb9d41 100644 --- a/tools/quote-card-test.js +++ b/tools/quote-card-test.js @@ -68,11 +68,12 @@ try { { id: 'e'.repeat(64), pubkey: pk, kind: 1, created_at: Math.floor(Date.now()/1000) - 60, content: 'I remember this.\n\nnostr:' + FAR, tags: [] }, ])); localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Quoter', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, A, nevent, FAR]); if (process.argv.includes('--slow')) await page.evaluate(() => localStorage.setItem('__slowQuote', '1')); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); - await page.evaluate(() => document.querySelector('.app-bottom-nav .app-nav-button').click()); + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); await sleep(7000); if (process.argv.includes('--slow')) { for (let i = 0; i < 40 && !(await page.evaluate(() => !!document.querySelector('.quote-card'))); i++) await sleep(250); 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'; // <coinos-text>, 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()); diff --git a/tools/video-autoplay-test.js b/tools/video-autoplay-test.js index 583c908..93453ee 100644 --- a/tools/video-autoplay-test.js +++ b/tools/video-autoplay-test.js @@ -62,12 +62,11 @@ 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: 'Clip Poster', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, AUTHOR, seeded]); 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(800); - await page.evaluate(() => { const e = [...document.querySelectorAll('.item')].find((n) => (n.querySelector('.chat-name') || {}).textContent === 'Feed'); if (e) e.click(); }); + await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); const players = () => ytRequests.filter((u) => /youtube-nocookie\.com\/embed/.test(u)).length; // the poster still comes from ytimg, as before console.log('\n[a video in a post]'); diff --git a/tools/video-compress-test.js b/tools/video-compress-test.js index ce1a936..c1175f8 100644 --- a/tools/video-compress-test.js +++ b/tools/video-compress-test.js @@ -40,7 +40,7 @@ await page.evaluateOnNewDocument(() => { const open = XMLHttpRequest.prototype.open, send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (m, url) { this.__m = m; this.__url = String(url); return open.apply(this, arguments); }; XMLHttpRequest.prototype.send = function (body) { - if (this.__m === 'PUT' && /nostr\.build|nostr\.download/.test(this.__url) && body instanceof Blob) { + if (this.__m === 'PUT' && /blossom\.ditto\.pub|blossom\.primal\.net|nostr\.build|nostr\.download/.test(this.__url) && body instanceof Blob) { body.arrayBuffer().then((buf) => { window.__put = { size: body.size, type: body.type, buf }; }); } return send.apply(this, arguments); @@ -56,7 +56,7 @@ await page.evaluateOnNewDocument(() => { await page.setRequestInterception(true); page.on('request', (req) => { const u = req.url(); - if (!/nostr\.build|nostr\.download/.test(u)) return req.continue(); + if (!/blossom\.ditto\.pub|blossom\.primal\.net|nostr\.build|nostr\.download/.test(u)) return req.continue(); const cors = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'PUT, HEAD, GET', 'access-control-allow-headers': '*' }; if (req.method() === 'OPTIONS') return req.respond({ status: 204, headers: cors }); if (req.method() === 'HEAD') return req.respond({ status: 200, headers: cors }); diff --git a/tools/video-unmute-test.js b/tools/video-unmute-test.js index 16cb56f..fec3955 100644 --- a/tools/video-unmute-test.js +++ b/tools/video-unmute-test.js @@ -16,6 +16,10 @@ const server = Bun.serve({ port: 5302, fetch(req) { // Hold autoplay until after repaint to make the race deterministic. window.IntersectionObserver = undefined; const t = key => key; + // what videoNode reaches for outside the slice: no cached stills or sizes, + // and no clip over the data budget + const videoThumbs = new Map(), videoInfo = new Map(), tappedClips = new Set(); + const feedMedia = () => null, rememberVideo = () => {}, probeVideo = async () => {}, videoHeavy = () => false, heavyLabel = () => ''; ${dom} ${video} const url = '/clip.webm'; @@ -24,6 +28,8 @@ const server = Bun.serve({ port: 5302, fetch(req) { const originalButton = live.querySelector('button'); const fresh = videoNode(url); morph(live, fresh); + // (a clip loads nothing until it comes into view; this one is in view) + live.querySelector('video').preload = 'auto'; window.test = { live, fresh, originalButton, rebuild() { const next = videoNode(url); live.replaceWith(next); return next.querySelector('video').muted; } }; diff --git a/tools/youtube-embed-test.js b/tools/youtube-embed-test.js index 1c66676..5d50b35 100644 --- a/tools/youtube-embed-test.js +++ b/tools/youtube-embed-test.js @@ -40,13 +40,14 @@ await page.evaluate(([k, pk]) => { { id: 'f'.repeat(64), pubkey: pk, kind: 1, created_at: now - 120, content: 'a shorts one https://www.youtube.com/shorts/abc123XYZ_-', tags: [] }, { id: '0'.repeat(64), pubkey: pk, kind: 1, created_at: now - 180, content: 'not a video https://example.com/watch?v=nope', tags: [] }, ])); + localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Tuber', t: Date.now() } })); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // new wallets open on Popular }, [base, A]); 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(); }); -await sleep(3500); +await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]').click()); +for (let i = 0; i < 60 && !(await page.$('.notes-feed:not([data-booting]) > .row')); i++) await sleep(250); +await sleep(1500); let ok = true; const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; }; diff --git a/tools/zap-deep-profile-test.js b/tools/zap-deep-profile-test.js index 555b54c..55c7b5d 100644 --- a/tools/zap-deep-profile-test.js +++ b/tools/zap-deep-profile-test.js @@ -41,6 +41,8 @@ try { const zapBtn = await until(() => page.$('.note-actions .zap, button.zap, [aria-label*="zap" i]'), 20000); check('a post with a zap button', !!zapBtn); if (zapBtn) { + // (to the middle of the screen: at the bottom edge the tap lands on the bottom nav) + await zapBtn.evaluate((b) => b.scrollIntoView({ block: 'center' })); await zapBtn.click(); // whatever the zap ends in, it says so in a toast or on the zap screen const said = await until(() => page.evaluate(() => { |
