- wallets.js: WalletManager state management with chain validation - verifier.js: WalletVerifier EIP-712 signing via window.ethereum - stream.js: WalletStreamManager with BroadcastChannel leader election, backoff + jitter - AGENTS.md: updated with coding guidelines
cbBTC Dashboard Test Suite
The test suite is organized under the /test folder. All tests run in Node.js with ES modules and mock browser APIs (localStorage, WebSocket, BroadcastChannel, window.ethereum). The setup file test/test_setup.mjs provides the global location object that stream.js requires at import time.
Files
| File | Targets | Requires | How to run |
|---|---|---|---|
test/test_wallets.js |
WalletManager — CRUD, persistence, security | None | node --experimental-modules test/test_wallets.js |
test/test_stream.js |
WalletStreamManager — WebSocket, backoff, routing | test/test_setup.mjs |
node --experimental-modules --import ./test/test_setup.mjs ./test/test_stream.js |
test/test_tabs.js |
WalletStreamManager — multi-tab leader election, failover | test/test_setup.mjs |
node --experimental-modules --import ./test/test_setup.mjs ./test/test_tabs.js |
test/test_verifier.js |
WalletVerifier — Web3 provider error handling | None | node --experimental-modules test/test_verifier.js |
test/test_setup.mjs |
Provides globalThis.location for stream.js |
— | Imported via --import |
What Can Be Tested — by Module
1. WalletManager (test_wallets.js)
The WalletManager class manages tracked wallets in localStorage. The test suite covers:
Address Validation
- Rejection of invalid address formats per chain (EVM, Bitcoin, Solana).
- Acceptance of valid addresses for EVM (
0x...), Bitcoin legacy (1...), and Solana base58 patterns.
CRUD (Create/Read/Update/Delete)
addWallet()— adds wallets, persists to storage, returns success/error.getWallets()— returns deep copies of tracked wallets.findWallet()— returns wallet object orundefined.removeWallet()— splices wallet from state and persists.
Duplicate Guard
- Same
address+chainpair is rejected on secondaddWallet()call.
Verification Lifecycle
- Wallets start with
isVerified: false. verifyWallet()sets the flag totrueand stores the signature.revokeVerification()resets the flag and clears the signature.
Security: localStorage Tampering
loadWallets()always resetsisVerifiedtofalseon restart, regardless of what was written to storage. This prevents a user from forging verification by editinglocalStoragedirectly.
2. WalletVerifier (test_verifier.js)
The WalletVerifier class handles Web3 provider interactions. The test suite covers error-path cases:
Missing Extension
- When
window.ethereumis undefined, verification fails with"No Web3 wallet extension found".
User Rejection (Account Request)
- When the user denies
eth_requestAccounts(EIP-1193 error 4001), verification cancels with"Verification cancelled".
Signature Rejection
- When the user denies the typed-data signature request, the error is mapped to
"rejected".
3. WalletStreamManager — Core (test_stream.js)
The WalletStreamManager class manages the WebSocket connection, event routing, and reconnect logic with exponential backoff. The test suite covers:
Multiplexed Subscription
- On connect, the manager sends a single
subscribeframe containing all tracked wallets (fromWalletManager).
Event Routing
- Incoming server messages are parsed and dispatched to registered
on('balance_update', ...)callbacks. - Events route by
data.type(ordata.event, or fallback'message').
Callback Error Isolation
- If one registered callback throws, remaining callbacks for the same event still execute. Exceptions are caught and logged but never propagate.
Dynamic Subscription
subscribeToNewWallet()sends an immediatesubscribe_walletframe without cycling the connection.
Exponential Backoff + Jitter
- On network drop (
simulateNetworkDrop()), the manager retries connecting. The delay followsBASE_DELAY * 2^attemptwith ±25% jitter, capped atMAX_DELAY(30s). - Tests speed up time by overriding
setTimeoutto fire instantly.
4. WalletStreamManager — Multi-Tab (test_tabs.js)
The leader/follower election and cross-tab synchronization system relies on BroadcastChannel and localStorage heartbeats. The test suite covers:
Leader Election
- First tab to connect finds no heartbeat in
localStorage, claims leadership, opens the WebSocket, and starts heartbeats. - Second tab finds the heartbeat, assigns itself as a follower, and avoids opening a second WebSocket.
Cross-Tab Event Broadcasting
- Leader receives server data on the WebSocket and posts it to
BroadcastChannel. - Followers receive the broadcast and dispatch it to their local listeners.
Proxy Subscription Forwarding
- When a follower calls
subscribeToNewWallet(), the request is sent throughBroadcastChannelto the leader, which opens the actual network subscription.
Automatic Failover
- When the leader tab closes (
disconnect()), it broadcastsLEADER_DEATHbefore closing the channel. - Followers detect the signal, claim leadership, remove the old heartbeat, write a new one, and open their own WebSocket connection.
Mock Infrastructure
All tests replace browser APIs with in-memory equivalents:
- localStorage — plain object (
mockStorage) withgetItem,setItem,removeItem,clear. - setTimeout — overridden to fire callbacks after 1ms instead of the specified delay, so backoff/retry logic runs instantly.
- WebSocket —
MockWebSocketauto-opens after 2ms, exposessimulateServerPush()andsimulateNetworkDrop()helpers. - BroadcastChannel —
MockBroadcastChanneldelivers messages synchronously to all other registered channels, simulating cross-tab delivery.
Extending the Test Suite
To add a new test:
- Add a
describe/itblock or inlineassertinside the relevanttest_<module>.jsfile. - For stream-related tests, ensure the file is imported via
--import ./test/test_setup.mjs. - Run the test from the project root with the appropriate command.
To add a new module test:
- Create
test/test_<module>.jswith imports using../<module>.js(relative to the/testfolder). - Add the required mock infrastructure at the top.
- Register it in this README with its run command.