diff options
| author | Adam Soltys <[email protected]> | 2026-10-02 14:35:25 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-02 14:35:25 -0700 |
| commit | db8b88b973635b7329cfd5f234c6f8dd6e9e2ccf (patch) | |
| tree | 3e083c874efb716810ceb73b3f42ff242309cce2 /tools | |
| parent | 12b129de5e579e0596c87f65524bd48cfbbe3e87 (diff) | |
| download | coinosv3-db8b88b973635b7329cfd5f234c6f8dd6e9e2ccf.tar.gz coinosv3-db8b88b973635b7329cfd5f234c6f8dd6e9e2ccf.zip | |
Send: the recipient field grows to show it all; history steps aside while sending
- The recipient is a one-line textarea that wraps and grows (rows from
the text's length over the field's measured width), so a whole address
is in view in the field itself — no second copy in small type below.
Enter doesn't break it and pasted line breaks are dropped. Empty, it is
as tall as its hint (measured in the hint's own face), so that's whole
too.
- Past the recipient — the amount, a review, a Lightning/Ark
confirmation, a raw tx to broadcast — the history list below the send
pane is left out.
- tools/send-dest-test.js (390px and 1280px).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dmcrt9KyNDKVWFKnb8BREo
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/profile-actions-test.js | 18 | ||||
| -rw-r--r-- | tools/send-dest-test.js | 63 |
2 files changed, 72 insertions, 9 deletions
diff --git a/tools/profile-actions-test.js b/tools/profile-actions-test.js index 830d7a0..e44c037 100644 --- a/tools/profile-actions-test.js +++ b/tools/profile-actions-test.js @@ -59,7 +59,7 @@ try { // the suggestion's metadata and fetch the large image before selection. await page.evaluate(() => { profileActions.showSend({ fresh: true }); profileActions.render(); }); const portraitResponse = page.waitForResponse(r => r.url().endsWith('/recipient-portrait.svg')); - await page.type('input.mono-input', 'carol'); + await page.type('textarea.send-dest', 'carol'); await page.waitForSelector('.send-suggest .ava-img'); await portraitResponse; await page.evaluate(() => { @@ -73,7 +73,7 @@ try { }); await page.click('.send-suggest .chat-thread-row'); await page.waitForSelector('.send-avatar'); - assert.equal(await page.$eval('input.mono-input', e => e.value), npubEncode(searchPeer)); + assert.equal(await page.$eval('textarea.send-dest', e => e.value), npubEncode(searchPeer)); assert(await page.$eval('.send-avatar', e => e.classList.contains('ava-img') && e.style.backgroundImage.includes('/recipient-portrait.svg') && getComputedStyle(e).visibility !== 'hidden'), 'selection immediately uses the already-fetched suggestion portrait'); assert(await page.evaluate(() => recipientFrames.length > 0 && recipientFrames.every(f => !f.punk && !f.hidden && f.picture.includes('/recipient-portrait.svg'))), 'every selected-recipient frame has the real avatar, without fallback or placeholder'); await page.evaluate(() => recipientObserver.disconnect()); @@ -99,11 +99,11 @@ try { Object.assign(ui, previous); render(); }, previous); - assert.equal(await page.$('input.mono-input'), null, 'previous payment displays its success view'); + assert.equal(await page.$('textarea.send-dest'), null, 'previous payment displays its success view'); await openProfile(nextPeer); await action('Pay'); - await page.waitForSelector('input.mono-input'); - assert.equal(await page.$eval('input.mono-input', (e) => e.value), npubEncode(nextPeer)); + await page.waitForSelector('textarea.send-dest'); + assert.equal(await page.$eval('textarea.send-dest', (e) => e.value), npubEncode(nextPeer)); assert(!(await page.$('.check-badge')), 'previous success must not win over the new payment form'); const state = await page.evaluate(() => ({ recipients: profileActions.ui.send.recipients.map(({ address, amount }) => ({ address, amount })), @@ -125,8 +125,8 @@ try { ui.zap = { stale: true }; ui.nameResolve = { text: '[email protected]' }; }); await action('Pay'); - await page.waitForSelector('input.mono-input'); - assert.equal(await page.$eval('input.mono-input', (e) => e.value), npubEncode(peer)); + await page.waitForSelector('textarea.send-dest'); + assert.equal(await page.$eval('textarea.send-dest', (e) => e.value), npubEncode(peer)); assert(await page.evaluate(() => { const { ui } = profileActions; return !ui.send.max && !ui.arkLnFromSavingsOk && ui.send.recipients.length === 1 && ui.send.recipients[0].amount === '' @@ -183,7 +183,7 @@ try { assert.equal(await page.evaluate(() => profileActions.ui.profilePk), peer); await assertBlank(); await page.goBack(); - await page.waitForSelector('input.mono-input'); + await page.waitForSelector('textarea.send-dest'); await assertBlank(); assert.equal(await page.$('.send-person'), null, 'returning to Send has no old recipient portrait'); @@ -194,7 +194,7 @@ try { await page.waitForFunction(() => profileActions.ui.tab === 'receive'); await assertBlank(); await page.goForward(); - await page.waitForSelector('input.mono-input'); + await page.waitForSelector('textarea.send-dest'); await assertBlank(); console.log('✓ Leaving Send for navigation tabs, a recipient profile or browser Back clears it; returning starts blank'); diff --git a/tools/send-dest-test.js b/tools/send-dest-test.js new file mode 100644 index 0000000..6dc331c --- /dev/null +++ b/tools/send-dest-test.js @@ -0,0 +1,63 @@ +// The Send form's recipient: one line that grows to show a whole address +// (it used to be repeated in small type underneath), and the history list +// steps aside once you're on to the amount. +// Run: bun tools/send-dest-test.js +import puppeteer from 'puppeteer-core'; +import { generateMnemonic } from '@scure/bip39'; +import { wordlist } from '@scure/bip39/wordlists/english'; +import { p2tr } from '@scure/btc-signer'; +import { schnorr } from '@noble/curves/secp256k1'; +import { buildHtml } from '../build.js'; +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 REGTEST = { bech32: 'bcrt', pubKeyHash: 0x6f, scriptHash: 0xc4, wif: 0xef }; +const taproot = p2tr(schnorr.getPublicKey(schnorr.utils.randomPrivateKey()), undefined, REGTEST).address; // 64 characters +const html = await buildHtml({ minify: true, pwa: false }); +const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) }); +const browser = await puppeteer.launch({ executablePath: process.env.CHROME || '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); +const page = await browser.newPage(); +const errs = []; page.on('pageerror', (e) => errs.push(String(e))); +const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase() === x || n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); +const field = () => page.$eval('textarea.send-dest', (e) => ({ rows: e.rows, h: e.clientHeight, sh: e.scrollHeight, v: e.value, lines: Math.round((e.clientHeight - parseFloat(getComputedStyle(e).paddingTop) - parseFloat(getComputedStyle(e).paddingBottom)) / parseFloat(getComputedStyle(e).lineHeight)) })); +const history = () => page.evaluate(() => [...document.querySelectorAll('.tab-pane .small.faint')].some((e) => /^history$/i.test(e.textContent.trim()))); +try { + for (const width of [390, 1280]) { + await page.setViewport({ width, height: 844 }); + await page.goto(server.url.origin, { waitUntil: 'domcontentloaded' }); + if (width === 390) { + await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest')); + await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400); + await click('create a new wallet'); await sleep(500); await click('to import'); await sleep(400); + await page.waitForSelector('textarea'); await page.type('textarea', generateMnemonic(wordlist)); await click('open wallet'); + } + for (let i = 0; i < 60 && !(await page.evaluate(() => /receive/i.test(document.body.innerText))); i++) await sleep(300); + await page.evaluate(() => [...document.querySelectorAll('.tabs button, [role=tab], button')].find((b) => b.textContent.trim() === 'Send')?.click()); + await page.waitForSelector('textarea.send-dest', { timeout: 10000 }); + console.log(`[${width}px]`); + let f = await field(); + if (process.env.SHOT) { const r = await page.$eval('textarea.send-dest', (e) => { const b = e.closest('.input-group').getBoundingClientRect(); return [b.x, b.y, b.width, b.height, e.clientWidth]; }); console.log('field width', r[4]); await page.screenshot({ path: process.env.SHOT + '-' + width + '.png', clip: { x: r[0] - 6, y: r[1] - 6, width: r[2] + 12, height: r[3] + 12 } }); } + check('an empty recipient shows its whole hint, no taller', f.sh <= f.h + 1 && f.rows <= (width > 600 ? 1 : 2), JSON.stringify(f)); + check('...and the history is there below the form', await history()); + await page.focus('textarea.send-dest'); + await page.keyboard.type(taproot, { delay: 0 }); await sleep(600); + f = await field(); + check('a 64-character address is all in view, the field grown to fit', f.v === taproot && f.sh <= f.h + 1 && (width > 600 || f.lines >= 2), JSON.stringify(f)); + check('nothing repeats it underneath', !(await page.$('.addr-check'))); + check('with the amount showing, the history steps aside', !!(await page.$('input[type=number]')) && !(await history())); + await page.keyboard.press('Enter'); await sleep(100); + check('Enter does not break the line', (await field()).v === taproot); + // clear it: back to the recipient step, history back + await page.$eval('textarea.send-dest', (e) => { e.value = ''; e.dispatchEvent(new Event('input', { bubbles: true })); }); await sleep(600); + f = await field(); + check('cleared, it shrinks back to its hint', f.rows <= (width > 600 ? 1 : 2) && f.sh <= f.h + 1, JSON.stringify(f)); + check('...and the history returns', await history()); + } + check('no page errors', errs.length === 0, errs.join('; ').slice(0, 300)); +} catch (e) { + check('run completed', false, e.stack); +} finally { + await browser.close(); server.stop(true); +} +console.log(ok ? '\n✅ the recipient grows to fit; the history steps aside while sending' : '\n❌ failures above'); +process.exit(ok ? 0 : 1); |
