Skip to content

Commit d464d96

Browse files
committed
test(self-custodial): cover multi-account flows across providers, hooks and settings
1 parent a9edf00 commit d464d96

18 files changed

Lines changed: 591 additions & 340 deletions

__tests__/graphql/network-error-component.spec.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ jest.mock("@app/utils/toast")
2020
jest.mock("@react-navigation/native")
2121
jest.mock("@app/utils/storage/secureStorage")
2222

23+
jest.mock("@app/hooks/use-active-wallet", () => ({
24+
useActiveWallet: () => ({
25+
isSelfCustodial: false,
26+
activeWalletId: "current-custodial-id",
27+
}),
28+
}))
29+
2330
const mockClearNetworkError = jest.fn()
2431
const mockToastShow = toastShow as jest.Mock
2532
const mockLogout = jest.fn()

__tests__/hooks/use-account-registry.spec.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ jest.mock("@app/hooks/use-app-config", () => ({
4848
}),
4949
}))
5050

51+
const mockListSelfCustodialAccounts = jest.fn()
52+
jest.mock("@app/self-custodial/storage/account-index", () => ({
53+
listSelfCustodialAccounts: () => mockListSelfCustodialAccounts(),
54+
}))
55+
5156
let mockNonCustodialEnabled = false
5257
let mockActiveAccountId: string | undefined
5358

@@ -56,6 +61,7 @@ describe("useAccountRegistry", () => {
5661
jest.clearAllMocks()
5762
mockNonCustodialEnabled = false
5863
mockActiveAccountId = undefined
64+
mockListSelfCustodialAccounts.mockResolvedValue([])
5965
})
6066

6167
it("returns custodial account when authenticated", () => {
@@ -78,15 +84,22 @@ describe("useAccountRegistry", () => {
7884
expect(result.current.activeAccount).toBeUndefined()
7985
})
8086

81-
it("includes self-custodial account when flag enabled", () => {
87+
it("includes self-custodial accounts loaded from the index", async () => {
8288
mockUseIsAuthed.mockReturnValue(true)
8389
mockNonCustodialEnabled = true
90+
mockListSelfCustodialAccounts.mockResolvedValue([
91+
{ id: "sc-uuid-1", lightningAddress: null },
92+
])
8493

8594
const { result } = renderHook(() => useAccountRegistry())
8695

96+
await act(async () => {
97+
await Promise.resolve()
98+
})
99+
87100
expect(result.current.accounts).toHaveLength(2)
88101
expect(result.current.accounts[1].type).toBe(AccountType.SelfCustodial)
89-
expect(result.current.accounts[1].label).toBe("Spark")
102+
expect(result.current.accounts[1].id).toBe("sc-uuid-1")
90103
expect(result.current.accounts[1].status).toBe(AccountStatus.RequiresRestore)
91104
})
92105

@@ -99,14 +112,21 @@ describe("useAccountRegistry", () => {
99112
expect(result.current.activeAccount?.selected).toBe(true)
100113
})
101114

102-
it("selects account matching activeAccountId", () => {
115+
it("selects account matching activeAccountId", async () => {
103116
mockUseIsAuthed.mockReturnValue(true)
104117
mockNonCustodialEnabled = true
105-
mockActiveAccountId = "self-custodial-default"
118+
mockActiveAccountId = "sc-uuid-1"
119+
mockListSelfCustodialAccounts.mockResolvedValue([
120+
{ id: "sc-uuid-1", lightningAddress: null },
121+
])
106122

107123
const { result } = renderHook(() => useAccountRegistry())
108124

109-
expect(result.current.activeAccount?.id).toBe("self-custodial-default")
125+
await act(async () => {
126+
await Promise.resolve()
127+
})
128+
129+
expect(result.current.activeAccount?.id).toBe("sc-uuid-1")
110130
expect(result.current.activeAccount?.type).toBe(AccountType.SelfCustodial)
111131
})
112132

@@ -134,14 +154,21 @@ describe("useAccountRegistry", () => {
134154
expect(mockSaveToken).toHaveBeenCalledWith("token")
135155
})
136156

137-
it("setActiveAccountId does not call saveToken for self-custodial", () => {
157+
it("setActiveAccountId does not call saveToken for self-custodial", async () => {
138158
mockUseIsAuthed.mockReturnValue(true)
139159
mockNonCustodialEnabled = true
160+
mockListSelfCustodialAccounts.mockResolvedValue([
161+
{ id: "sc-uuid-1", lightningAddress: null },
162+
])
140163

141164
const { result } = renderHook(() => useAccountRegistry())
142165

166+
await act(async () => {
167+
await Promise.resolve()
168+
})
169+
143170
act(() => {
144-
result.current.setActiveAccountId("self-custodial-default")
171+
result.current.setActiveAccountId("sc-uuid-1")
145172
})
146173

147174
expect(mockSaveToken).not.toHaveBeenCalled()
@@ -162,9 +189,9 @@ describe("createCustodialDescriptor", () => {
162189

163190
describe("createSelfCustodialDescriptor", () => {
164191
it("creates a self-custodial descriptor with correct defaults", () => {
165-
const desc = createSelfCustodialDescriptor("Spark")
192+
const desc = createSelfCustodialDescriptor("sc-id-1", "Spark")
166193

167-
expect(desc.id).toBe(DefaultAccountId.SelfCustodial)
194+
expect(desc.id).toBe("sc-id-1")
168195
expect(desc.type).toBe(AccountType.SelfCustodial)
169196
expect(desc.label).toBe("Spark")
170197
expect(desc.selected).toBe(false)
@@ -175,11 +202,11 @@ describe("createSelfCustodialDescriptor", () => {
175202
describe("markSelected", () => {
176203
const accounts = [
177204
createCustodialDescriptor("Blink"),
178-
createSelfCustodialDescriptor("Spark"),
205+
createSelfCustodialDescriptor("sc-id-1", "Spark"),
179206
]
180207

181208
it("marks account matching activeId as selected", () => {
182-
const result = markSelected(accounts, DefaultAccountId.SelfCustodial)
209+
const result = markSelected(accounts, "sc-id-1")
183210

184211
expect(result[0].selected).toBe(false)
185212
expect(result[1].selected).toBe(true)

__tests__/hooks/use-wallet-mnemonic.spec.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,74 @@
11
import { renderHook, waitFor } from "@testing-library/react-native"
22

33
import { useWalletMnemonic, useWalletMnemonicWords } from "@app/hooks/use-wallet-mnemonic"
4+
import { AccountType } from "@app/types/wallet.types"
45

5-
const mockGetMnemonic = jest.fn()
6+
const mockGetMnemonicForAccount = jest.fn()
7+
const mockUseActiveWallet = jest.fn()
8+
const mockUseAccountRegistry = jest.fn()
69

710
jest.mock("@app/utils/storage/secureStorage", () => ({
811
__esModule: true,
912
default: {
10-
getMnemonic: () => mockGetMnemonic(),
13+
getMnemonicForAccount: (accountId: string) => mockGetMnemonicForAccount(accountId),
1114
},
1215
}))
1316

17+
jest.mock("@app/hooks/use-active-wallet", () => ({
18+
useActiveWallet: () => mockUseActiveWallet(),
19+
}))
20+
21+
jest.mock("@app/hooks/use-account-registry", () => ({
22+
useAccountRegistry: () => mockUseAccountRegistry(),
23+
}))
24+
25+
const ACCOUNT_ID = "sc-uuid-1"
26+
27+
const setActiveSelfCustodial = (): void => {
28+
mockUseActiveWallet.mockReturnValue({ isSelfCustodial: true })
29+
mockUseAccountRegistry.mockReturnValue({
30+
activeAccount: { id: ACCOUNT_ID, type: AccountType.SelfCustodial },
31+
})
32+
}
33+
34+
const setNoActiveAccount = (): void => {
35+
mockUseActiveWallet.mockReturnValue({ isSelfCustodial: false })
36+
mockUseAccountRegistry.mockReturnValue({ activeAccount: undefined })
37+
}
38+
1439
describe("useWalletMnemonic", () => {
1540
beforeEach(() => {
1641
jest.clearAllMocks()
1742
})
1843

19-
it("returns empty string initially", () => {
20-
mockGetMnemonic.mockResolvedValue(null)
44+
it("returns empty string when no self-custodial account is active", () => {
45+
setNoActiveAccount()
2146

2247
const { result } = renderHook(() => useWalletMnemonic())
2348

2449
expect(result.current).toBe("")
2550
})
2651

27-
it("loads mnemonic from keychain", async () => {
28-
mockGetMnemonic.mockResolvedValue("word1 word2 word3")
52+
it("loads mnemonic from keychain for the active account", async () => {
53+
setActiveSelfCustodial()
54+
mockGetMnemonicForAccount.mockResolvedValue("word1 word2 word3")
2955

3056
const { result } = renderHook(() => useWalletMnemonic())
3157

3258
await waitFor(() => {
3359
expect(result.current).toBe("word1 word2 word3")
3460
})
61+
expect(mockGetMnemonicForAccount).toHaveBeenCalledWith(ACCOUNT_ID)
3562
})
3663

37-
it("does not set state when keychain returns null", async () => {
38-
mockGetMnemonic.mockResolvedValue(null)
64+
it("keeps state empty when keychain returns null", async () => {
65+
setActiveSelfCustodial()
66+
mockGetMnemonicForAccount.mockResolvedValue(null)
3967

4068
const { result } = renderHook(() => useWalletMnemonic())
4169

4270
await waitFor(() => {
43-
expect(mockGetMnemonic).toHaveBeenCalled()
71+
expect(mockGetMnemonicForAccount).toHaveBeenCalled()
4472
})
4573

4674
expect(result.current).toBe("")
@@ -53,15 +81,16 @@ describe("useWalletMnemonicWords", () => {
5381
})
5482

5583
it("returns empty array when no mnemonic", () => {
56-
mockGetMnemonic.mockResolvedValue(null)
84+
setNoActiveAccount()
5785

5886
const { result } = renderHook(() => useWalletMnemonicWords())
5987

6088
expect(result.current).toEqual([])
6189
})
6290

6391
it("splits mnemonic into words", async () => {
64-
mockGetMnemonic.mockResolvedValue("alpha beta gamma")
92+
setActiveSelfCustodial()
93+
mockGetMnemonicForAccount.mockResolvedValue("alpha beta gamma")
6594

6695
const { result } = renderHook(() => useWalletMnemonicWords())
6796

__tests__/persistent-storage.spec.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,18 @@ it("returns default when schema is not present", async () => {
2525
expect(state).toEqual(defaultPersistentState)
2626
})
2727

28-
it("migration from 5 to 7", async () => {
28+
it("migration from 5 to current", async () => {
2929
const state5 = {
3030
schemaVersion: 5,
3131
galoyInstance: { id: "Main" },
3232
galoyAuthToken: "myToken",
3333
}
3434

35-
const state7 = {
36-
schemaVersion: 7,
37-
galoyInstance: { id: "Main" },
38-
galoyAuthToken: "myToken",
39-
}
40-
4135
const res = await migrateAndGetPersistentState(state5)
4236

43-
expect(res).toStrictEqual(state7)
37+
expect(res).toStrictEqual({
38+
schemaVersion: 9,
39+
galoyInstance: { id: "Main" },
40+
galoyAuthToken: "myToken",
41+
})
4442
})

0 commit comments

Comments
 (0)