diff options
| author | Adam Soltys <[email protected]> | 2026-10-02 09:26:56 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-02 09:26:56 -0700 |
| commit | fe569ffa701e3f9b54330def31193217eb470ab9 (patch) | |
| tree | a13f33180c53346f49369f4bd52d03bfa38005e6 /tools | |
| parent | 65aa6c93153b28554127627dde2883a0685eeaff (diff) | |
| download | coinosv3-fe569ffa701e3f9b54330def31193217eb470ab9.tar.gz coinosv3-fe569ffa701e3f9b54330def31193217eb470ab9.zip | |
Balance card gear replaces Accounts; Spending's coins live behind it
- The "Accounts" pill on each balance face is now a gear. It opens that
face's settings: Spending leads with its coins (renewal, the
co-operative move to Savings, unilateral exit), then Security (lock,
change password), the recovery phrase, auto-logout; Savings adds its
public key. A wallet with no nostr identity also gets sign-in and
log-out there, since it has no profile page to hold them.
- The exit cost is no longer said under the balance; the Spending
settings state it, always, not only when coin chains run deep.
- The lone Manage link under the balance is gone (the gear is the way
in); warning notices that are about the coins link to the same page.
- The Accounts list, Add wallet and Delete all are no longer reached
from the wallet (Delete all stays behind profile > Log out > forget).
- Tests follow the gear: padlock-ask, lock-wallet and account-access
pass again; cooperative-exit-ui clicks the Spending gear.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dmcrt9KyNDKVWFKnb8BREo
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/account-access-test.js | 25 | ||||
| -rw-r--r-- | tools/ark/cooperative-exit-ui-test.js | 27 | ||||
| -rw-r--r-- | tools/lock-wallet-test.js | 16 | ||||
| -rw-r--r-- | tools/padlock-ask-test.js | 12 |
4 files changed, 54 insertions, 26 deletions
diff --git a/tools/account-access-test.js b/tools/account-access-test.js index d2685de..4a83fd0 100644 --- a/tools/account-access-test.js +++ b/tools/account-access-test.js @@ -31,9 +31,9 @@ try { await page.reload(); await page.waitForSelector('.header-avatar'); check('fresh local account defaults to mainnet', await page.evaluate(() => JSON.parse(localStorage.getItem('btc-wallet-watch'))[0].network === 'mainnet')); - check('anonymous avatar has an accessible Accounts label', await page.$eval('.header-avatar', (e) => e.getAttribute('aria-label') === 'Accounts')); + check('anonymous avatar has an accessible label', await page.$eval('.header-avatar', (e) => e.getAttribute('aria-label') === 'Account settings')); await page.click('.header-avatar'); - check('avatar opens Accounts with sign-in and logout', await page.evaluate(() => ['Accounts', 'Sign into another account', 'Log out'].every((s) => document.body.innerText.includes(s)))); + check('avatar opens the wallet settings with sign-in and logout', await page.evaluate(() => ['Security', 'Sign into another account', 'Log out'].every((s) => document.body.innerText.includes(s)))); const saved = await page.evaluate(() => localStorage.getItem('btc-wallet-watch')); await click('Sign into another account'); check('sign-in opens on Create new, not the seed import box', await page.evaluate(() => @@ -57,28 +57,29 @@ try { }, { key: cacheKeyFor(xpub) }); await page.reload(); await page.waitForSelector('[aria-label="Messages"]'); - await click('Accounts'); - const onAccounts = () => page.evaluate(() => [...document.querySelectorAll('h3')].some(e => e.textContent === 'Accounts')); + // the balance card's gear: a page like any other under the header + await page.click('.balance-gear'); + const onSettings = () => page.evaluate(() => history.state.nav.screen === 'accountSettings' && [...document.querySelectorAll('h3')].some(e => e.textContent === 'Security')); for (const label of ['Search', 'Messages', 'Your profile', 'Settings']) { - check(label + ' starts on Accounts', await onAccounts()); + check(label + ' starts on the wallet settings', await onSettings()); await page.click('[aria-label="' + label + '"]'); - check(label + ' opens from Accounts', await page.evaluate(label => { + check(label + ' opens from the wallet settings', await page.evaluate(label => { const nav = history.state.nav; - return nav.screen === 'wallet' && ![...document.querySelectorAll('h3')].some(e => e.textContent === 'Accounts') + return nav.screen === 'wallet' && (label === 'Search' ? !!document.querySelector('.user-search-input') : label === 'Messages' ? nav.chatOpen && nav.msgView === 'home' - : label === 'Your profile' ? !!document.querySelector('.npub-box') && nav.profilePk === 'a'.repeat(64) + : label === 'Your profile' ? nav.profilePk === 'a'.repeat(64) : nav.tab === 'settings'); }, label)); await page.goBack(); - await page.waitForFunction(() => [...document.querySelectorAll('h3')].some(e => e.textContent === 'Accounts')); - check('Back returns from ' + label + ' to Accounts', await onAccounts()); + await page.waitForFunction(() => history.state.nav.screen === 'accountSettings'); + check('Back returns from ' + label + ' to the wallet settings', await onSettings()); } await page.click('[aria-label="Lock wallet"]'); - check('Lock opens its password prompt from Accounts', await page.evaluate(() => !!document.querySelector('input[type="password"]'))); + check('Lock opens its password prompt from the wallet settings', await page.evaluate(() => !!document.querySelector('input[type="password"]'))); await click('Not now'); await page.click('.brand'); - check('the logo returns to the wallet', await page.evaluate(() => history.state.nav.screen === 'wallet' && ![...document.querySelectorAll('h3')].some(e => e.textContent === 'Accounts'))); + check('the logo returns to the wallet', await page.evaluate(() => history.state.nav.screen === 'wallet' && !!document.querySelector('.balance-gear'))); assert.deepEqual(errors, []); check('no browser errors', true); } finally { await browser.close(); server.stop(true); } diff --git a/tools/ark/cooperative-exit-ui-test.js b/tools/ark/cooperative-exit-ui-test.js index 1af394e..831ed3b 100644 --- a/tools/ark/cooperative-exit-ui-test.js +++ b/tools/ark/cooperative-exit-ui-test.js @@ -8,10 +8,11 @@ import { wordlist } from '@scure/bip39/wordlists/english'; const bundle = await Bun.build({ entrypoints: ['src/app.js'], target: 'browser', minify: true, plugins: [{ name: 'cooperative-exit-test', setup(build) { build.onLoad({ filter: /src\/app\.js$/ }, async ({ path }) => ({ loader: 'js', contents: await Bun.file(path).text() + ` -window.coopApp = { ui, wallet, ctx, render, enterWallet, get ready() { return !_bootDeciding; } }; +window.coopApp = { ui, wallet, ctx, render, enterWallet, get ready() { return !_bootDeciding; }, + setSpending: () => { const a = activeAccount(); if (a) a.kind = 'spending'; } }; ` })); build.onLoad({ filter: /src\/features\/ark\.js$/ }, async ({ path }) => ({ loader: 'js', contents: - (await Bun.file(path).text()).replace(" return {\n id: 'ark',", ` + (await Bun.file(path).text()).replace(' arkReady() { return arkAvailable(); },', ' arkReady() { return window.forceArkReady || arkAvailable(); },').replace(" return {\n id: 'ark',", ` window.coopFeature = { setManager: (mgr) => { ark = mgr; }, offboard: doArkOffboard }; return {\n id: 'ark',`) })); build.onLoad({ filter: /src\/video-compress\.js$/ }, () => ({ loader: 'js', contents: 'export async function compressVideo(f) { return f; }' })); @@ -70,6 +71,28 @@ try { const send = () => page.evaluate(() => [...document.querySelectorAll('.card')] .find((c) => c.querySelector('h3')?.textContent === 'Co-operative exit').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 + // longer says it under the balance. + await page.evaluate(() => { window.forceArkReady = true; coopApp.wallet.saveFeatureState('arkDepth', { show: true, exitFee: 4321, key: 'x' }); coopApp.setSpending(); coopApp.render(); }); + const homeText = await page.evaluate(() => document.body.innerText); + assert(!/Unilateral exit costs/.test(homeText), 'the exit cost is not on the home screen'); + assert(!/Accounts/.test(homeText), 'no Accounts button on the home screen'); + await page.screenshot({ path: '/tmp/gear-home.png' }); + const gears = await page.$$eval('.balance-gear', (es) => es.length); + assert(gears >= 1, 'the balance card wears a gear'); + const faces = await page.$$eval('.balance-face', (fs) => fs.map((f) => f.innerText.split('\n')[0])); + assert(faces.some((f) => /spending/i.test(f)), 'a Spending face: ' + JSON.stringify(faces)); + await page.evaluate(() => [...document.querySelectorAll('.balance-face')].find((f) => /spending/i.test(f.innerText.split('\n')[0])).querySelector('.balance-gear').click()); + await page.waitForSelector('.coin-table'); + const st = await page.evaluate(() => ({ nav: coopApp.ui.screen, text: document.body.innerText })); + assert.equal(st.nav, 'accountSettings'); + assert.match(st.text, /Unilateral exit costs about 4,?321 sats/, 'the exit cost lives on the settings page'); + assert.match(st.text, /Security/); assert.match(st.text, /Lock wallet/); assert.match(st.text, /Recovery phrase/); + await page.screenshot({ path: '/tmp/gear-settings.png', fullPage: true }); + console.log('✓ The Spending gear opens its settings: coins, the exit cost (gone from home), lock and recovery phrase'); + 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']); await coopButton(); diff --git a/tools/lock-wallet-test.js b/tools/lock-wallet-test.js index 13d2b91..107ccd0 100644 --- a/tools/lock-wallet-test.js +++ b/tools/lock-wallet-test.js @@ -82,8 +82,8 @@ try { await sleep(1000); console.log('\n[the padlock still asks — locking needs a password]'); - // the padlock lives on the Accounts page (the balance card's switcher opens it) - await page.evaluate(() => document.querySelector('.balance-switch')?.click()); + // the padlock lives on the wallet settings page (the balance card's gear) + await page.evaluate(() => document.querySelector('.balance-gear')?.click()); await sleep(600); await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((n) => n.title === 'Lock wallet'); if (b) b.click(); }); await sleep(600); @@ -91,7 +91,7 @@ try { check('padlock asks even after "Not now"', /protect this device/i.test(txt), txt.slice(0, 120).replace(/\n+/g, ' | ')); await click('button', 'Not now'); await sleep(600); - check('and declining lands back on the Accounts page', /add account/i.test(await body())); + check('and declining lands back on the wallet settings', /security/i.test(await body())); console.log('\n[now with a password]'); await click('button', 'Change password'); @@ -139,13 +139,17 @@ try { check('backing out deletes nothing', (await vaultSize()) > 0); console.log('\n[delete all is the destructive one]'); - await page.evaluate(() => document.querySelector('.balance-switch')?.click()); + // the same road as above, this time all the way: profile → Log out → + // forget all data → the warning → Delete all + await page.evaluate(() => document.querySelector('.header-avatar')?.click()); await sleep(800); - await click('button', 'Delete all'); + await click('button', 'Log out'); + await sleep(500); + await click('button', 'forget all data'); await sleep(600); txt = await body(); check('it warns first', /delete all/i.test(txt) && /(delete|remove|erase|wipe|lose)/i.test(txt), txt.slice(0, 160).replace(/\n+/g, ' | ')); - await page.evaluate(() => { const b = [...document.querySelectorAll('.btn-primary')].find((e) => /delete all/i.test(e.textContent)); if (b) b.click(); }); + await page.evaluate(() => { const b = [...document.querySelectorAll('.confirm-pop button')].find((e) => /delete all/i.test(e.textContent)); if (b) b.click(); }); await sleep(1200); txt = await body(); check('everything is gone', (await vaultSize()) === 0); diff --git a/tools/padlock-ask-test.js b/tools/padlock-ask-test.js index f1fa3cf..5570b8f 100644 --- a/tools/padlock-ask-test.js +++ b/tools/padlock-ask-test.js @@ -35,25 +35,25 @@ try { await click('button', 'Open wallet'); await waitText('receive', 20000); - // open the Accounts page (the balance card's switcher) — the padlock lives there - await page.evaluate(() => document.querySelector('.balance-switch')?.click()); + // open the balance card's settings (its gear) — the padlock lives there + await page.evaluate(() => document.querySelector('.balance-gear')?.click()); await sleep(900); - check('Accounts page is open', /add account/i.test(await bodyText()), (await bodyText()).slice(0, 60).replace(/\n/g, ' | ')); + check('the wallet settings page is open', /security/i.test(await bodyText()), (await bodyText()).slice(0, 60).replace(/\n/g, ' | ')); // tap the padlock — the ask must appear without navigating anywhere const tapped = await page.evaluate(() => { const b = [...document.querySelectorAll('button')].find((e) => /lock/i.test(e.getAttribute('aria-label') || '')); if (!b) return false; b.click(); return true; }); - check('padlock is reachable from the Accounts page', tapped); + check('padlock is reachable from the wallet settings', tapped); await sleep(500); const text = await bodyText(); check('the ask appears on the tap', /protect this device/i.test(text), text.split('\n').slice(0, 3).join(' | ')); - // declining lands back on the Accounts page, not somewhere else + // declining lands back on the settings page, not somewhere else await click('button', 'not now'); await sleep(600); - check('declining lands back on the Accounts page', /add account/i.test(await bodyText()), (await bodyText()).slice(0, 60).replace(/\n/g, ' | ')); + check('declining lands back on the wallet settings', /security/i.test(await bodyText()), (await bodyText()).slice(0, 60).replace(/\n/g, ' | ')); } finally { await browser.close(); server.stop(true); } |
