From ffce7ccd734d7207ce4bdcd715099bc19b9b44db Mon Sep 17 00:00:00 2001 From: ugoocreates-pixel Date: Thu, 20 Aug 2026 19:00:34 +0100 Subject: [PATCH] test: add tests for lib/wallet.ts Closes #. ### Coverage Inventory * **wallet.ts exports**: `saveAccount`, `loadAccount`, `clearAccount`, `truncateAddress`, `mockAddress`, `STORAGE_KEY`. * **useWallet.test.ts**: Covers *none* of the above. It only tests the `useWallet` hook to ensure it throws when used outside a `WalletProvider`. ### The Defect (Mocking Strategy & Incorrect Assumptions) The issue description assumed that `wallet.ts` implements a real wallet integration with error paths like "Provider absent", "User rejection", and "Chain mismatch". However, as documented in `wallet.ts`, this module is purely a mock / stand-in that stores a deterministic fake address in `localStorage`. Since the module does not integrate with any real wallet provider (like Freighter), these assumed error paths and listeners **do not exist** in `wallet.ts`. I have reported these "untested paths" as `.todo()` in the test suite to formally acknowledge them as defects (i.e. the promised feature doesn't exist). I have written tests for the actual exposed methods, verifying: - Successful saving, loading, and clearing of accounts from `localStorage` - The `loadAccount` error paths (invalid JSON, missing address, regex validation failure) - Address truncation formatting - Deterministic seed generation in `mockAddress` --- src/lib/wallet.test.ts | 114 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/lib/wallet.test.ts diff --git a/src/lib/wallet.test.ts b/src/lib/wallet.test.ts new file mode 100644 index 0000000..c2adb79 --- /dev/null +++ b/src/lib/wallet.test.ts @@ -0,0 +1,114 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +import { + saveAccount, + loadAccount, + clearAccount, + truncateAddress, + mockAddress, + STORAGE_KEY, +} from "./wallet"; + +describe("wallet.ts", () => { + beforeEach(() => { + window.localStorage.clear(); + vi.restoreAllMocks(); + }); + + describe("saveAccount", () => { + it("saves account to localStorage", () => { + saveAccount({ address: "GABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQ" }); + const stored = window.localStorage.getItem(STORAGE_KEY); + expect(stored).toBe('{"address":"GABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQ"}'); + }); + + it("does nothing if window is undefined", () => { + const originalWindow = global.window; + // @ts-expect-error simulating environment + delete global.window; + expect(() => saveAccount({ address: "G123" })).not.toThrow(); + global.window = originalWindow; + }); + }); + + describe("loadAccount", () => { + it("loads a valid account from localStorage", () => { + const validAddr = mockAddress("TESTSEED"); + window.localStorage.setItem(STORAGE_KEY, JSON.stringify({ address: validAddr })); + const account = loadAccount(); + expect(account).toEqual({ address: validAddr }); + }); + + it("returns null if no account is in localStorage", () => { + expect(loadAccount()).toBeNull(); + }); + + it("returns null if JSON is invalid", () => { + window.localStorage.setItem(STORAGE_KEY, '{invalid json'); + expect(loadAccount()).toBeNull(); + }); + + it("returns null if address is not a string", () => { + window.localStorage.setItem(STORAGE_KEY, '{"address": 123}'); + expect(loadAccount()).toBeNull(); + }); + + it("returns null if address fails regex validation", () => { + // Too short + window.localStorage.setItem(STORAGE_KEY, '{"address": "G123"}'); + expect(loadAccount()).toBeNull(); + }); + + it("returns null if window is undefined", () => { + const originalWindow = global.window; + // @ts-expect-error simulating environment + delete global.window; + expect(loadAccount()).toBeNull(); + global.window = originalWindow; + }); + }); + + describe("clearAccount", () => { + it("removes the storage keys", () => { + window.localStorage.setItem(STORAGE_KEY, "test"); + window.localStorage.setItem("anchornet:wallet:seed", "test-seed"); + clearAccount(); + expect(window.localStorage.getItem(STORAGE_KEY)).toBeNull(); + expect(window.localStorage.getItem("anchornet:wallet:seed")).toBeNull(); + }); + }); + + describe("truncateAddress", () => { + it("truncates long addresses", () => { + const addr = "GABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQ"; + expect(truncateAddress(addr, 4)).toBe("GABC…NOPQ"); + }); + + it("returns original string if it is too short to truncate", () => { + expect(truncateAddress("G123", 4)).toBe("G123"); + }); + }); + + describe("mockAddress", () => { + it("generates a deterministic address from a seed", () => { + const addr1 = mockAddress("TESTSEED"); + const addr2 = mockAddress("TESTSEED"); + expect(addr1).toBe(addr2); + expect(addr1).toMatch(/^G[A-Z0-9]{55}$/); + }); + + it("persists a session seed when no seed is provided", () => { + const addr1 = mockAddress(); + const addr2 = mockAddress(); + expect(addr1).toBe(addr2); + expect(window.localStorage.getItem("anchornet:wallet:seed")).not.toBeNull(); + }); + }); + + describe("Wallet Provider / Connection Error Paths (Defects)", () => { + it.todo("Provider absent — the extension is not installed"); + it.todo("User rejection — the connection prompt is dismissed"); + it.todo("Network / chain mismatch — the wallet is on a different network than the app expects"); + it.todo("Account change mid-session — the user switches accounts while connected"); + it.todo("Disconnect — cleanup of listeners and cached state"); + }); +});