aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/ark/ui-test.js63
-rw-r--r--tools/comments-test.js23
2 files changed, 76 insertions, 10 deletions
diff --git a/tools/ark/ui-test.js b/tools/ark/ui-test.js
index 4460cd3..e4e2e5a 100644
--- a/tools/ark/ui-test.js
+++ b/tools/ark/ui-test.js
@@ -89,6 +89,28 @@ const waitFace = async (label, want, ms = 30000) => {
}
return false;
};
+// An address arrives the way a paste does — whole. Typed a key at a time,
+// a re-render mid-typing can drop or reorder characters (the app then
+// rightly refuses a bad checksum).
+const pasteDest = (text) => page.evaluate((text) => {
+ const el = document.querySelector('textarea.send-dest');
+ el.focus();
+ el.value = text;
+ el.dispatchEvent(new Event('input', { bubbles: true }));
+}, text);
+// Select a balance card and open its Receive tab. The carousel glides to the
+// card and landing on one opens its history — and a glide can settle back
+// on the other card — so keep asking until the account sticks, then open
+// Receive (the tab a receive's celebration takes over).
+const openReceive = async (account) => {
+ for (let i = 0; i < 20; i++) {
+ await page.evaluate((a) => uiApp.ctx.setAccount(a), account);
+ await sleep(750);
+ if (await page.evaluate((a) => uiApp.ctx.getAccount() === a, account)) break;
+ }
+ await sleep(750);
+ await page.evaluate(() => { uiApp.ui.tab = 'receive'; uiApp.render(); });
+};
const tabPane = () => page.evaluate(() => document.querySelector('.tab-pane')?.innerText || '');
try {
@@ -110,14 +132,32 @@ try {
await snap('spending');
console.log('\n[2] receive over Ark');
- await page.evaluate(() => uiApp.ctx.setAccount('spending'));
+ await openReceive('spending');
let arkAddr = '';
for (let i = 0; i < 120 && !arkAddr; i++) {
arkAddr = ((await tabPane()).match(/tark1[a-z0-9]+/) || [''])[0];
if (!arkAddr) await sleep(250);
}
- check('ark address on the Receive tab', !!arkAddr, arkAddr.slice(0, 24) + '…');
+ check('ark address on the Receive tab', !!arkAddr, arkAddr ? arkAddr.slice(0, 24) + '…'
+ : 'tab ' + (await page.evaluate(() => uiApp.ui.tab)) + ': ' + (await tabPane()).slice(0, 200).replace(/\n/g, ' | '));
if (!arkAddr) { await snap('no-ark-address'); throw new Error('ark never connected — is captaind up?'); }
+ // A new wallet's first mailbox read is its baseline: whatever it finds is
+ // history and never celebrated. Pay only once that read is behind us, or
+ // the payment can land inside it and the celebration check is a coin toss.
+ const baselineDone = () => page.evaluate(() => new Promise((res) => {
+ const req = indexedDB.open('coinos-ark');
+ req.onerror = () => res(false);
+ req.onsuccess = () => {
+ try {
+ const all = req.result.transaction('states').objectStore('states').getAll();
+ all.onsuccess = () => res(all.result.some((v) => v && Array.isArray(v.movements) && !v.baselinePending));
+ all.onerror = () => res(false);
+ } catch { res(false); }
+ };
+ }));
+ let baselined = false;
+ for (let i = 0; i < 120 && !baselined; i++) { baselined = await baselineDone(); if (!baselined) await sleep(250); }
+ check('the first mailbox read (the baseline) is done', baselined);
sh(`${ALICE} -q send ${arkAddr} "25000 sat"`);
check('spending shows 25,000', await waitFace('Spending', 25000), String(await faceSat('Spending')));
check('payment celebration shown', await waitText('Payment received', 10000));
@@ -130,7 +170,7 @@ try {
await sleep(500);
const aliceAddr = sh(`${ALICE} -q address`);
const before = aliceBalance();
- await page.type('textarea.send-dest', aliceAddr);
+ await pasteDest(aliceAddr);
await sleep(800);
check('no fee controls for an ark dest', /no mining fee/i.test(await tabPane()) && !/fee rate|coin control/i.test(await tabPane()));
await page.type('.tab-pane input[placeholder="0"]', '7000');
@@ -142,16 +182,23 @@ try {
await clickText('.tab-pane button.btn-primary', 'Send');
check('sent view', await waitText('Sent!', 15000));
await snap('sent');
- check('alice +7000', aliceBalance() === before + 7000, String(aliceBalance() - before));
+ // alice's CLI wallet sees the coin once it has read its mailbox
+ let aliceGot = 0;
+ for (let i = 0; i < 30 && aliceGot !== 7000; i++) { aliceGot = aliceBalance() - before; if (aliceGot !== 7000) await sleep(500); }
+ check('alice +7000', aliceGot === 7000, String(aliceGot));
check('spending shows 18,000', await waitFace('Spending', 18000), String(await faceSat('Spending')));
await clickText('.card', 'Sent!'); // tap anywhere to proceed
- await sleep(400);
- const hist = await bodyText();
+ let hist = '';
+ for (let i = 0; i < 40; i++) {
+ hist = await bodyText();
+ if (/received[\s\S]*\+25,000/i.test(hist) && /-7,000/.test(hist)) break;
+ await sleep(250);
+ }
check('history shows the receive', /received[\s\S]*\+25,000/i.test(hist));
check('history shows the send', /-7,000/.test(hist));
console.log('\n[4] Savings -> Spending (board)');
- await page.evaluate(() => uiApp.ctx.setAccount('savings'));
+ await openReceive('savings');
let btcAddr = '';
for (let i = 0; i < 40 && !btcAddr; i++) {
btcAddr = ((await tabPane()).match(/bcrt1[a-z0-9]+/) || [''])[0];
@@ -165,7 +212,7 @@ try {
// sending Savings to your own Spending address opens the move panel instead
await clickText('button', 'Send');
await sleep(500);
- await page.type('textarea.send-dest', arkAddr);
+ await pasteDest(arkAddr);
check('own Spending address opens the move panel', await waitText('Moving from Savings instead', 10000));
await page.type('.balance-move input', '30000');
await sleep(300);
diff --git a/tools/comments-test.js b/tools/comments-test.js
index 01d47d5..6801f44 100644
--- a/tools/comments-test.js
+++ b/tools/comments-test.js
@@ -25,12 +25,31 @@ const html = await buildHtml({ minify: true, pwa: false });
const server = Bun.serve({ port: 5283, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) });
const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] });
const page = await browser.newPage();
-await page.evaluateOnNewDocument(() => {
+// The article and its comments, fetched above, are also handed to the app
+// on every relay socket that asks for them: the app reaching the same public
+// relays as this script (one of them can be refusing this IP) is not what's
+// under test.
+await page.evaluateOnNewDocument((known) => {
window.__sent = [];
window.__all = [];
+ const matches = (f, e) => {
+ if (f.ids && !f.ids.includes(e.id)) return false;
+ if (f.kinds && !f.kinds.includes(e.kind)) return false;
+ if (f.authors && !f.authors.includes(e.pubkey)) return false;
+ for (const [k, vals] of Object.entries(f)) {
+ if (k[0] !== '#' || !Array.isArray(vals)) continue;
+ if (!e.tags.some((t) => t[0] === k.slice(1) && vals.includes(t[1]))) return false;
+ }
+ return !!(f.ids || Object.keys(f).some((k) => k[0] === '#'));
+ };
const send = WebSocket.prototype.send;
WebSocket.prototype.send = function (d) {
window.__all.push(this.url + ' ' + String(d).slice(0, 60));
+ if (typeof d === 'string' && d.startsWith('["REQ"')) {
+ const [, sub, ...filters] = JSON.parse(d);
+ const hits = known.filter((e) => filters.some((f) => matches(f, e)));
+ if (hits.length) setTimeout(() => { for (const e of hits) this.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', sub, e]) })); }, 30);
+ }
if (typeof d === 'string' && d.startsWith('["EVENT"')) { // every event: nothing leaves the test
const ev = JSON.parse(d)[1];
window.__sent.push(ev);
@@ -40,7 +59,7 @@ await page.evaluateOnNewDocument(() => {
}
return send.call(this, d);
};
-});
+}, [root, ...comments]);
const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t);
const waitText = async (x, ms = 25000) => { for (let i = 0; i < ms / 250; i++) { if ((await page.evaluate(() => document.body.innerText)).toLowerCase().includes(x)) return true; await sleep(250); } return false; };
const waitFor = async (fn, ms = 15000) => { for (let i = 0; i < ms / 200; i++) { if (await page.evaluate(fn)) return true; await sleep(200); } return false; };