aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-18 09:27:09 -0700
committerAdam Soltys <[email protected]>2026-10-18 09:27:09 -0700
commitaaebbd5d51e0e149c36975122dd0ae59e6c011bb (patch)
treee3997c172c563fa0a29012ac18159d2be144d151 /tools
parentbfa52c7a2cf1cc06455536d9a6676b0003a30452 (diff)
downloadcoinosv3-aaebbd5d51e0e149c36975122dd0ae59e6c011bb.tar.gz
coinosv3-aaebbd5d51e0e149c36975122dd0ae59e6c011bb.zip
Call history: each call is a row in its conversation — Outgoing / Incoming video call · 4:12, Missed, Declined, No answer, Cancelled, Busy — a tap calls back. Kept like a message and sent to our own inbox (kind 1405, gift-wrapped to us alone) so our other devices list it; a call logged by two of our devices shows once, by its call-id. A call that rang while the app was shut and went unanswered is a missed call when it next opens.
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
Diffstat (limited to 'tools')
-rw-r--r--tools/call-test.js11
-rw-r--r--tools/call-wake-test.js25
2 files changed, 36 insertions, 0 deletions
diff --git a/tools/call-test.js b/tools/call-test.js
index 96271fb..2958187 100644
--- a/tools/call-test.js
+++ b/tools/call-test.js
@@ -121,6 +121,10 @@ try {
check('hanging up ends it here', await waitFor(A.page, () => /call ended/i.test(document.querySelector('.call-overlay')?.innerText || ''), 3000));
check('...and there', await waitFor(B.page, () => /call ended/i.test(document.querySelector('.call-overlay')?.innerText || ''), 10000));
check('...the cameras let go', await waitFor(A.page, () => !document.querySelector('.call-overlay'), 6000) && await waitFor(B.page, () => !document.querySelector('.call-overlay'), 6000));
+ const logs = (page) => page.evaluate(() => [...document.querySelectorAll('.chat-row.call-log')].map((r) => r.innerText.replace(/\s+/g, ' ').trim()));
+ check('the call is in the caller\'s conversation: outgoing, how long', await waitFor(A.page, () => [...document.querySelectorAll('.chat-row.call-log')].some((r) => /Outgoing video call · \d+:\d\d/.test(r.innerText)), 5000), JSON.stringify(await logs(A.page)));
+ await openDm(B, A.pk);
+ check('...and in the callee\'s: incoming', await waitFor(B.page, () => [...document.querySelectorAll('.chat-row.call-log')].some((r) => /Incoming video call · \d+:\d\d/.test(r.innerText)), 5000), JSON.stringify(await logs(B.page)));
console.log('\n[declined]');
await A.page.evaluate(() => document.querySelector('.call-start[data-call="voice"]').click());
@@ -128,6 +132,12 @@ try {
await B.page.evaluate(() => document.querySelector('.call-decline').click());
check('declined: the caller is told', await waitFor(A.page, () => /declined/i.test(document.querySelector('.call-overlay')?.innerText || ''), 10000));
await waitFor(A.page, () => !document.querySelector('.call-overlay'), 6000);
+ check('...and it\'s logged so on both sides', await waitFor(A.page, () => [...document.querySelectorAll('.chat-row.call-log')].some((r) => /Outgoing voice call · Declined/.test(r.innerText)), 5000)
+ && await waitFor(B.page, () => [...document.querySelectorAll('.chat-row.call-log.missed')].some((r) => /Declined voice call/.test(r.innerText)), 5000), JSON.stringify([await logs(A.page), await logs(B.page)]));
+ await B.page.evaluate(() => document.querySelector('.chat-row.call-log .call-log-pill').click());
+ check('a tap on a logged call calls back', await waitFor(A.page, () => !!document.querySelector('.call-overlay .call-accept'), 15000));
+ await A.page.evaluate(() => document.querySelector('.call-decline').click());
+ await waitFor(B.page, () => !document.querySelector('.call-overlay'), 8000);
console.log('\n[a remote signer\'s way: the candidates inside the offer and answer]');
for (const w of [A, B]) await w.page.evaluate(() => localStorage.setItem('coinos-call-trickle', '0'));
@@ -152,6 +162,7 @@ try {
await sleep(5000);
check('someone Bob neither follows nor wrote to doesn\'t ring him', !(await B.page.evaluate(() => !!document.querySelector('.call-overlay'))));
await C.page.evaluate(() => document.querySelector('.call-hang')?.click());
+ check('...the caller who gave up has it as cancelled', await waitFor(C.page, () => [...document.querySelectorAll('.chat-row.call-log')].some((r) => /Cancelled voice call/.test(r.innerText)), 6000));
for (const w of [A, B, C]) check(w.name + ': no page errors', !w.page.errs.length, w.page.errs.join(' | '));
} catch (e) { console.error(e); ok = false; } finally {
await browser.close(); server.stop(true);
diff --git a/tools/call-wake-test.js b/tools/call-wake-test.js
index 05705ad..e14935a 100644
--- a/tools/call-wake-test.js
+++ b/tools/call-wake-test.js
@@ -176,6 +176,31 @@ try {
await B.page.goto(`http://localhost:${PORT}/`, { waitUntil: 'domcontentloaded' });
await B.waitText('receive');
check('a stranger\'s offer, or one gone, rings nothing', !(await notifications(B.page)).some((n) => /calling/.test(n)) && !(await B.page.evaluate(() => !!document.querySelector('.call-overlay'))));
+
+ console.log('\n[rung while closed, and nobody came]');
+ await B.page.goto('about:blank');
+ const before2 = new Set(callWraps.keys());
+ await A.page.evaluate(() => document.querySelector('.call-start[data-call="voice"]').click());
+ let offer2 = null;
+ for (let i = 0; i < 50 && !offer2; i++) { await sleep(200); offer2 = [...callWraps.values()].find((w) => !before2.has(w.id) && w.tags.some((x) => x[0] === 'p' && x[1] === B.pk) && w.content.length >= 2000); }
+ await push(B, { type: 'notify', reason: 'call', wrapId: offer2.id });
+ await sleep(1500);
+ await A.page.evaluate(() => document.querySelector('.call-hang').click()); // Alice gives up
+ // Bob opens coinos later on, past the call's minute (the kept offer dated back)
+ const page2 = await B.context.newPage();
+ await page2.goto(`http://localhost:${PORT}/icon-192.png`, { waitUntil: 'domcontentloaded' }); // the origin, without the app (which would take the offer)
+ await page2.evaluate(async () => {
+ const c = await caches.open('coinos-call'), r = await c.match('/__pending-call');
+ const j = await r.json(); j.at -= 120_000;
+ await c.put('/__pending-call', new Response(JSON.stringify(j), { headers: { 'content-type': 'application/json' } }));
+ });
+ await page2.close();
+ await B.page.goto(`http://localhost:${PORT}/` + npubEncode(A.pk), { waitUntil: 'domcontentloaded' });
+ check('the app doesn\'t ring for a call long over', !(await B.page.evaluate(() => !!document.querySelector('.call-overlay'))));
+ await B.page.waitForSelector('.prof-actions', { timeout: 20000 });
+ await B.page.evaluate(() => [...document.querySelectorAll('.prof-actions button')].find((x) => /message/i.test(x.textContent)).click());
+ check('...it\'s a missed call in the conversation', await waitFor(B.page, () => [...document.querySelectorAll('.chat-row.call-log.missed')].some((r) => /Missed voice call/.test(r.innerText)), 10000),
+ JSON.stringify(await B.page.evaluate(() => [...document.querySelectorAll('.chat-row.call-log')].map((r) => r.innerText))));
for (const w of [A, B]) check(w.name + ': no page errors', !w.page.errs.length, w.page.errs.join(' | '));
} catch (e) { console.error(e); ok = false; } finally {
await browser.close(); server.stop(true);