diff options
| -rw-r--r-- | src/app.js | 23 | ||||
| -rw-r--r-- | tools/amount-field-unit-test.js | 16 |
2 files changed, 33 insertions, 6 deletions
@@ -1209,31 +1209,44 @@ let unit = (() => { // been fetched — a third position showing blanks is worse than two that work. function toggleUnit() { const ring = haveRate() ? ['sats', 'btc', 'fiat'] : ['sats', 'btc']; + const was = unit; unit = ring[(ring.indexOf(unit) + 1) % ring.length]; try { localStorage.setItem(UNIT_KEY, unit); } catch {} + // An amount already typed into the send form is the same money in the new + // unit, not the same digits: 1000 sats does not become 1000 BTC. Whatever + // doesn't parse is left as typed. + for (const r of (ui.send && ui.send.recipients) || []) { + if (!r.amount || !String(r.amount).trim()) continue; + const sat = parseField(r.amount, was); + if (sat) r.amount = amountField(sat); + } render(); } // A sats figure as the amount field types it in the current unit — what a // Max button or a fixed-amount invoice fills in. Fiat is floored to the // cent so a filled-in maximum never parses back to more than was offered. -// The figure it filled is remembered with its exact sats: a fiat maximum +// The figures it filled are remembered with their exact sats: a fiat maximum // rounded to the cent would otherwise parse back a few sats short and leave -// them behind. Typing anything else parses as typed. -let lastFill = null; +// them behind. Typing anything else parses as typed. Several are kept so a +// form with more than one amount round-trips each of them. +const fills = new Map(); function amountField(sat) { let s; const r = unit === 'fiat' && rateNow(); if (unit === 'sats') s = String(sat); else if (r) s = (Math.floor((sat / 1e8) * r * 100) / 100).toFixed(2); else s = (sat / 1e8).toFixed(8); - lastFill = { s, unit, sat }; + fills.delete(unit + '|' + s); + fills.set(unit + '|' + s, sat); + while (fills.size > 16) fills.delete(fills.keys().next().value); return s; } function parseField(v, u = unit) { - if (lastFill && lastFill.unit === u && String(v).trim() === lastFill.s) return lastFill.sat; + const k = u + '|' + String(v).trim(); + if (fills.has(k)) return fills.get(k); return parseAmount(v, u, rateNow()); } diff --git a/tools/amount-field-unit-test.js b/tools/amount-field-unit-test.js index b984064..596969d 100644 --- a/tools/amount-field-unit-test.js +++ b/tools/amount-field-unit-test.js @@ -2,6 +2,8 @@ // showing: in fiat that is money to the cent, floored so a filled-in maximum // never parses back to more sats than were offered — not a BTC figure under a // CAD label (2026-10-08: Max on an ark recipient filled 0.00004978 'CAD'). +// Switching the unit with an amount already typed into the send form converts +// it (2026-10-23): the same money in the new unit, round-tripping exactly. // Run: bun tools/amount-field-unit-test.js import assert from 'node:assert/strict'; import puppeteer from './puppeteer.js'; @@ -9,7 +11,7 @@ import puppeteer from './puppeteer.js'; const bundle = await Bun.build({ entrypoints: ['src/app.js'], target: 'browser', minify: true, plugins: [{ name: 'amount-field-test', setup(build) { build.onLoad({ filter: /src\/app\.js$/ }, async ({ path }) => ({ loader: 'js', contents: await Bun.file(path).text() + ` -window.amtApp = { ctx, toggleUnit, get unit() { return unit; } }; +window.amtApp = { ctx, ui, toggleUnit, get unit() { return unit; } }; ` })); build.onLoad({ filter: /src\/video-compress\.js$/ }, () => ({ loader: 'js', contents: 'export async function compressVideo(f) { return f; }' })); } }] }); @@ -37,6 +39,13 @@ try { while (amtApp.unit !== 'sats') amtApp.toggleUnit(); out.sats = ctx.amountField(62212); amtApp.toggleUnit(); out.btc = ctx.amountField(62212); + // typed into the send form, then the unit toggled around the ring + while (amtApp.unit !== 'sats') amtApp.toggleUnit(); + const rs = amtApp.ui.send.recipients; + rs[0].amount = '62212'; rs.push({ address: '', amount: '93.318' }, { address: '', amount: '' }, { address: '', amount: 'abc' }); + out.ring = []; + for (let i = 0; i < 3; i++) { amtApp.toggleUnit(); out.ring.push([amtApp.unit, ...rs.map((r) => r.amount)]); } + rs.splice(1); return out; }); console.log(r); @@ -46,6 +55,11 @@ try { assert.equal(r.typed, 62207, 'the same money typed as something else parses as typed'); assert.equal(r.sats, '62212'); assert.equal(r.btc, '0.00062212'); + assert.deepEqual(r.ring, [ + ['btc', '0.00062212', '0.00000093', '', 'abc'], + ['fiat', '93.31', '0.13', '', 'abc'], + ['sats', '62212', '93', '', 'abc'], + ], 'a typed amount follows the unit around the ring and comes back exact; blanks and junk are left alone'); assert.deepEqual(errors, []); console.log('✅ amount fills follow the display unit'); } finally { await browser.close(); server.stop(true); } |
