aboutsummaryrefslogtreecommitdiffstats
path: root/tools/ark/ln-pending-history-test.js
blob: 39b59ba2d9074e90a95cea2f2aaeb3224bddf3e2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// A Lightning payment still settling (a zap, mostly) shows in history as a
// pending Sent row with the recipient's face, and gives way to the completed
// row once the payment lands. Run: bun tools/ark/ln-pending-history-test.js
import assert from 'node:assert/strict';

const { arkFeature } = await import('../../src/features/ark.js');
const h = (tag, props, ...children) => ({ tag, props, children: children.flat().filter(x => x != null && x !== false) });
const text = n => n && typeof n === 'object' ? (n.children || []).map(text).join('') : n == null ? '' : String(n);
const store = { zapnotes: {} };
const ui = {};
let state;
const feature = arkFeature({ h, ui, render() {}, fmtAmount: String, unitLabel: () => 'sats', unitTag: () => 'sats', goBack() {}, copyBtn: () => null,
  hook: (name, pk) => name === 'profileChip' ? h('span', {}, 'face:' + pk.slice(0, 4)) : null,
  wallet: { registerCacheExtension() {}, loadArkState: () => state, loadFeatureState: (k, d) => store[k] ?? d, saveFeatureState: (k, v) => { store[k] = v; } } });

const zapped = 'c'.repeat(64);
const invoice = 'lnbc210n1zap';
// what a one-tap zap does before paying: remember whose invoice this is
store.zapnotes['inv:' + invoice] = zapped;
const started = Date.now() - 5000;
const action = { id: `lnpay-${started}`, type: 'ln-pay', step: 'htlc-sent', invoice, paymentHash: 'ab'.repeat(32), amountSat: 21, feeSat: 1, parts: [{ inputId: 'v:0', htlcSat: 22 }] };
state = { movements: [], actions: [action], vtxos: [] };

let rows = feature._histBuild(state);
assert.equal(rows.length, 1, 'the in-flight payment has a row');
assert.equal(rows[0].time, started, 'dated when it was sent');
let row = text(rows[0].render());
assert.match(row, /Sent/);
assert.match(row, /in progress/);
assert.match(row, /-21/, 'shown as money out');
assert.match(row, /face:cccc/, 'with who was zapped');
console.log('✓ An in-flight zap shows as a pending Sent row with the recipient');

// tapping it opens a detail view while it settles…
ui.arkMoveDetail = 'lnpay:' + action.id;
assert.match(text(feature.historyDetail()), /in progress/);

// …and once it lands, the completed movement replaces it, row and detail
action.step = 'done';
state.movements.push({ id: 'mv1', type: 'ln-send', status: 'complete', amountSat: 21, ts: Date.now(), invoice, paymentHash: action.paymentHash, preimage: 'ef'.repeat(32) });
rows = feature._histBuild(state);
assert.equal(rows.length, 1, 'no duplicate once settled');
row = text(rows[0].render());
assert.ok(!/in progress/.test(row), 'the settled row is not pending');
assert.match(row, /face:cccc/);
const detail = text(feature.historyDetail());
assert.ok(!/in progress/.test(detail) && /Sent/.test(detail), 'an open detail follows the payment to its completed record');
console.log('✓ The settled payment replaces the pending row, detail included');

// a failed payment leaves no pending row (its failed movement tells it)
const failed = { ...action, id: `lnpay-${started + 1}`, step: 'failed', paymentHash: 'cd'.repeat(32) };
assert.equal(feature._histBuild({ movements: [], actions: [failed], vtxos: [] }).length, 0);
console.log('✓ A failed payment drops its pending row');
process.exit(0);