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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
// Receive → More → Lightning invoice: the invoice says to keep coinos open
// until it's paid (the server only finishes a receive while the wallet is
// there to take it) and gives way to "expired" when its hour is up.
// The Ark side is faked. Run: bun tools/names-invoice-expiry-test.js
import assert from 'node:assert/strict';
import puppeteer from './puppeteer.js';
const app = await Bun.file('src/app.js').text();
const domHelper = app.slice(app.indexOf('function h(tag,'), app.indexOf('// ---------------------------------------------------------------- morphing'));
const entry = `
import { namesFeature } from './src/features/names.js';
${domHelper}
const state = { names: { byDomain: { 'coinos.io': { name: 'alice', domain: 'coinos.io' } } } };
const ui = { screen: 'wallet', namesMore: 'bolt11' };
const wallet = { loadFeatureState: (k, d) => (k in state ? state[k] : d), saveFeatureState: (k, v) => { state[k] = v; }, nostrSign: async (e) => e, nostrPubkey: () => 'aa'.repeat(32), _cacheKey: () => 'w1' };
const ctx = {
h, ui, wallet, render, toast: () => {}, copy: () => {}, copyBtn: (x, l) => h('button', { class: 'btn-sm' }, l), brandHeader: () => null, goBack: () => {}, setAccount: () => {},
hook(name, ...a) {
if (name === 'arkReady') return true;
if (name === 'arkMakeInvoice') return Promise.resolve({ invoice: 'lnbc10u1testinvoice', amountSat: a[0], expiresAt: Date.now() + (window.__life || 3600_000) });
return null;
},
};
const feature = namesFeature(ctx);
function render() { document.querySelector('#app').replaceChildren(feature.receiveModes()[0].render(h('div', { class: 'seg' }))); }
window.test = { render };
render();
`;
const bundle = await Bun.build({
entrypoints: ['names-inv-entry'], target: 'browser',
plugins: [{ name: 'names-inv', setup(build) {
build.onResolve({ filter: /^names-inv-entry$/ }, () => ({ path: 'entry', namespace: 'names-inv' }));
build.onLoad({ filter: /.*/, namespace: 'names-inv' }, () => ({ contents: entry, loader: 'js', resolveDir: process.cwd() }));
} }],
});
assert(bundle.success, bundle.logs.join('\n'));
const html = `<!doctype html><meta charset="utf-8"><style>${await Bun.file('src/style.css').text()}</style><div id="app"></div><script type="module">${await bundle.outputs[0].text()}</script>`;
const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) });
const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] });
const page = await browser.newPage();
const errors = [];
page.on('pageerror', (e) => errors.push(e.message));
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
const check = (name, value, detail = '') => { assert(value, name + (detail ? ' — ' + detail : '')); console.log(' ✓ ' + name); };
const text = () => page.evaluate(() => document.body.innerText.replace(/\s+/g, ' '));
const make = async () => {
await page.evaluate(() => { const i = document.querySelector('input[type=number]'); i.value = '1000'; i.dispatchEvent(new Event('input', { bubbles: true })); });
await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => /create/i.test(b.textContent)).click());
await pause(100);
};
try {
await page.goto(server.url.href);
await page.waitForSelector('input[type=number]');
await make();
check('the invoice shows', /lnbc10u1testinvoice/.test(await text()));
check('...saying to keep coinos open until it\'s paid, and that it lasts an hour', /Keep coinos open until this is paid/.test(await text()) && /goes back to the sender/.test(await text()) && /expires in an hour/.test(await text()));
await page.evaluate(() => { window.__life = 1500; document.querySelector('.btn-sm:last-child').click(); });
await pause(50);
await page.evaluate(() => test.render());
await make();
check('a fresh one shows its QR', !!(await page.$('svg')) && !/expired/.test(await text()));
await pause(2300);
check('when its time is up it says expired, the QR gone', /This invoice has expired/.test(await text()) && !(await page.$('svg')) && !/lnbc10u1testinvoice/.test(await text()));
check('...with a button for a new one', await page.evaluate(() => [...document.querySelectorAll('button')].some((b) => b.textContent.trim() === 'New invoice')));
check('no page errors', !errors.length, errors.join(' | '));
console.log('\n✅ invoice: keep open, expires');
} finally {
await browser.close();
server.stop(true);
}
|