aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/app.js23
-rw-r--r--tools/amount-field-unit-test.js16
2 files changed, 33 insertions, 6 deletions
diff --git a/src/app.js b/src/app.js
index ffe9ba8..8d73b71 100644
--- a/src/app.js
+++ b/src/app.js
@@ -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); }