aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-22 23:12:42 -0700
committerAdam Soltys <[email protected]>2026-10-22 23:12:42 -0700
commitaf351e13947831ba54d4233ba80f46dab89e8c42 (patch)
tree52b85c84140034b5b3ba6e6a3ccb9d747be5a19a /tools
parent56ebe0367021835be7371c70fb2295c439b594c4 (diff)
downloadcoinosv3-af351e13947831ba54d4233ba80f46dab89e8c42.tar.gz
coinosv3-af351e13947831ba54d4233ba80f46dab89e8c42.zip
Manage page: Renew, Withdraw and Exit sit under the coin table and act on the ticked coins; a unilateral exit can be partial; the explainers moved to the confirmations
Adam, 2026-10-23: the three buttons belong right below the table so it is clear they act on the selection, "co-operative exit" is Withdraw and "unilateral exit" is Exit, Exit names the ticked total and takes just those coins, and the explainer cards were a wall — they now appear where the action is decided: the renewal text and fee schedule on the Renew confirmation, the withdraw note on the form it opens, the exit cost comparison on the Exit confirmation. The page is the coins, their three buttons, and the custody strategy. The buttons stay (disabled) while every coin is in a round. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'tools')
-rw-r--r--tools/ark/cooperative-exit-ui-test.js14
-rw-r--r--tools/ark/custody-ui-test.js19
2 files changed, 18 insertions, 15 deletions
diff --git a/tools/ark/cooperative-exit-ui-test.js b/tools/ark/cooperative-exit-ui-test.js
index 506bca6..03e65d5 100644
--- a/tools/ark/cooperative-exit-ui-test.js
+++ b/tools/ark/cooperative-exit-ui-test.js
@@ -66,10 +66,9 @@ try {
});
await page.waitForSelector('.coin-table');
};
- const coopButton = () => page.evaluate(() => [...document.querySelectorAll('.card')]
- .find((c) => c.querySelector('h4')?.textContent === 'Co-operative exit').querySelector('button').click());
+ const coopButton = () => page.evaluate(() => document.querySelector('.ark-coop-go').click());
const send = () => page.evaluate(() => [...document.querySelectorAll('.card')]
- .find((c) => c.querySelector('h3')?.textContent === 'Co-operative exit').querySelector('.btn-primary').click());
+ .find((c) => c.querySelector('h3')?.textContent === 'Withdraw').querySelector('.btn-primary').click());
const edit = (selector, value) => page.$eval(selector, (e, value) => { e.value = value; e.dispatchEvent(new Event('input', { bubbles: true })); }, value);
// The balance card's gear is the way in: Spending's settings lead with
// its coins and carry what a unilateral exit costs; the home screen no
@@ -114,13 +113,16 @@ try {
await page.evaluate(() => { window.forceArkReady = false; coopApp.wallet.saveFeatureState('arkDepth', null); coopApp.ui.screen = 'wallet'; coopApp.render(); });
await manage();
- assert.deepEqual(await page.$$eval('.card h4', (es) => es.map((e) => e.textContent)), ['Renewal', 'Co-operative exit', 'Unilateral exit']);
+ // the three actions sit right under the table, named for the ticked total; no explainer cards
+ assert.deepEqual(await page.$$eval('.ark-coin-actions button', (es) => es.map((e) => e.textContent)), ['Renew now', 'Withdraw 20,000 sats', 'Exit 20,000 sats']);
+ assert.deepEqual(await page.$$eval('.card h4', (es) => es.map((e) => e.textContent)), []);
await coopButton();
await page.waitForSelector('input.mono-input');
const savings = await page.evaluate(() => coopApp.wallet.freshReceive().address);
assert.equal(await page.$eval('input.mono-input', (e) => e.value), savings);
assert.equal(await page.$eval('input[type="number"]', (e) => e.value), '20000');
assert.equal(await page.$eval('.card .btn-primary', (e) => e.textContent), 'Send');
+ assert.match(await page.evaluate(() => document.body.innerText), /one transaction into Savings, ready after a single confirmation/, 'the withdraw explainer is on the form');
assert.deepEqual(await page.evaluate(() => coopCalls), [], 'opening the form never starts an exit');
await page.evaluate(() => [...document.querySelectorAll('.card button')].find((b) => b.textContent.includes('Back')).click());
await page.waitForSelector('.coin-table');
@@ -165,7 +167,7 @@ try {
// ticking a subset on Manage: exactly those coins go, no split, the amount is theirs and not a field
await manage();
await page.evaluate(() => { coopApp.ui.arkCoinsSel = new Set(['coin-b']); coopApp.render(); });
- assert.match(await page.evaluate(() => [...document.querySelectorAll('.card')].find((c) => c.querySelector('h4')?.textContent === 'Co-operative exit').innerText), /Only the coin ticked above will move[\s\S]*Move 8,000 sats to Savings/);
+ assert.deepEqual(await page.$$eval('.ark-coin-actions button', (es) => es.map((e) => e.textContent)), ['Renew 1 selected for free', 'Withdraw 8,000 sats', 'Exit 8,000 sats'], 'the buttons name the ticked total');
await coopButton();
await page.waitForSelector('.ark-coop-chosen');
assert.match(await page.$eval('.ark-coop-chosen', (e) => e.textContent), /1 selected coin · 8,000 sats/);
@@ -180,7 +182,7 @@ try {
assert.equal(await page.evaluate(() => coopApp.ui.arkOffboardSend), null);
await manage();
await page.evaluate(() => { coopManager.state.vtxos.forEach((v) => v.state = 'pending'); coopManager.state.actions = [{ type: 'refresh', step: 'submitted', inputIds: ['coin-a', 'coin-b'], inAmountSat: 20000 }]; coopApp.render(); });
- assert(await page.evaluate(() => [...document.querySelectorAll('.card')].find((c) => c.querySelector('h4')?.textContent === 'Co-operative exit').querySelector('button').disabled));
+ assert(await page.evaluate(() => document.querySelector('.ark-coop-go').disabled));
await page.evaluate(() => {
const { ui, render } = coopApp;
ui.arkCoinsPage = null; ui.tab = 'send';
diff --git a/tools/ark/custody-ui-test.js b/tools/ark/custody-ui-test.js
index 419ed21..f8fc886 100644
--- a/tools/ark/custody-ui-test.js
+++ b/tools/ark/custody-ui-test.js
@@ -85,20 +85,22 @@ try {
assert.doesNotMatch(tx, /Coins renew in the last hours|Every transfer adds a hop/, 'only the chosen strategy is described');
assert.doesNotMatch(tx, /timelock|Spending coin carries|old Coinos/, 'no jargon on the card');
const order = await page.$$eval('.card h4, .card h3', (es) => es.map((e) => e.textContent).filter((x) => /Spending coins|Custody|Renewal/.test(x)));
- assert.deepEqual(order, ['Spending coins', 'Custody', 'Renewal'], 'coins first, then the strategy, then renewal');
+ assert.deepEqual(order, ['Spending coins', 'Custody'], 'coins first, then the strategy; no explainer cards');
assert.match(tx, /held by Coinos/, 'the coin past its timelock is held, not "expired"');
assert.doesNotMatch(tx, /5,000 sats\s+expired/, 'the row never says "expired"');
- assert.match(tx, /With “Allow coins to expire”, nothing renews on its own/);
+ assert.doesNotMatch(tx, /nothing renews on its own/, 'the renewal explainer is not on the page…');
+ const renewSheet = async () => { await page.click('.ark-renew-ask'); await sleep(200); const t = await text(); await page.evaluate(() => [...document.querySelectorAll('.ark-renew-confirm button')].find((b) => b.textContent === 'Cancel').click()); await sleep(200); return t; };
+ assert.match(await renewSheet(), /With “Allow coins to expire”, nothing renews on its own[\s\S]*more than 15 days left/, '…it is on the Renew confirmation, with the fee schedule');
if (process.env.SHOT) await page.screenshot({ path: process.env.SHOT, fullPage: true });
console.log('✓ the custody card: a drop-down of three strategies with their prices, coins expire by default, one description, a past-timelock coin shown as held');
// the unilateral exit only counts the coin still inside its timelock
- assert.match(tx, /Exit now/);
- const exitBtn = await page.$$eval('button', (bs) => bs.filter((b) => b.textContent === 'Exit now').map((b) => b.disabled));
- assert.deepEqual(exitBtn, [false]);
- await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => b.textContent === 'Exit now').click()); await sleep(200);
+ assert.equal(await page.$eval('.ark-exit-ask', (b) => b.textContent), 'Exit 20,800 sats', 'the exit button names the ticked coins still inside their timelock');
+ assert.equal(await page.$eval('.ark-exit-ask', (b) => b.disabled), false);
+ await page.click('.ark-exit-ask'); await sleep(200);
tx = await text();
assert.match(tx, /Coins\s*2/); assert.match(tx, /Amount\s*20,800 sats/);
+ assert.match(tx, /If the server vanished today/, 'the exit explainer is on the confirmation');
await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => b.textContent === 'Cancel').click()); await sleep(200);
console.log('✓ a unilateral exit counts only the coin still inside its timelock');
@@ -108,8 +110,7 @@ try {
assert.equal(await page.evaluate(() => coopApp.wallet.loadFeatureState('arkCustody')), 'self', 'saved with the wallet');
tx = await text();
assert.match(tx, /Custody choice saved/);
- assert.doesNotMatch(tx, /nothing renews on its own/);
- assert.match(tx, /renews coins automatically once they are close to expiring/);
+ assert.match(await renewSheet(), /renews coins automatically once they are close to expiring/);
assert.match(await page.$eval('.ark-custody-desc', (e) => e.textContent), /Coins renew in the last hours/);
const calls = await page.evaluate(() => coopFeature.autoRefreshCalls);
assert.deepEqual(calls[0], ['ensure', 6, 8, 1], 'backstop orders registered at once (regtest margin 6)');
@@ -125,7 +126,7 @@ try {
console.log('✓ "Keep my exit cheap" renews the deep coin too, when the fee is under a tenth of it');
await page.select('#ark-custody-select', 'custodial'); await sleep(300);
assert.equal(await page.evaluate(() => coopFeature.getCustody()), 'custodial');
- assert.match(await text(), /nothing renews on its own/);
+ assert.match(await renewSheet(), /nothing renews on its own/);
console.log('✓ the other tiers switch the same way, and back');
assert.deepEqual(errors, [], 'no page errors');
console.log('\n✅ custody is a choice, made on the Spending page');