diff --git a/packages/thirdweb/src/react/core/hooks/wallets/useAddConnectedWallet.test.tsx b/packages/thirdweb/src/react/core/hooks/wallets/useAddConnectedWallet.test.tsx new file mode 100644 index 00000000000..7b439d2c402 --- /dev/null +++ b/packages/thirdweb/src/react/core/hooks/wallets/useAddConnectedWallet.test.tsx @@ -0,0 +1,52 @@ +import { renderHook } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { describe, expect, it } from "vitest"; +import { MockStorage } from "../../../../../test/src/mocks/storage.js"; +import { TEST_CLIENT } from "../../../../../test/src/test-clients.js"; +import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js"; +import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js"; +import { ethereum } from "../../../../chains/chain-definitions/ethereum.js"; +import { createConnectionManager } from "../../../../wallets/manager/index.js"; +import { ConnectionManagerCtx } from "../../providers/connection-manager.js"; +import { useAddConnectedWallet } from "./useAddConnectedWallet.js"; + +describe("useAddConnectedWallet", () => { + // Mock the connection manager + const mockStorage = new MockStorage(); + const manager = createConnectionManager(mockStorage); + + // Create a wrapper component with the mocked context + const wrapper = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); + }; + + const wallet = createWalletAdapter({ + adaptedAccount: TEST_ACCOUNT_A, + client: TEST_CLIENT, + chain: ethereum, + onDisconnect: () => {}, + switchChain: () => {}, + }); + + it("should add a wallet to the connection manager", async () => { + // Render the hook + const { result } = renderHook(() => useAddConnectedWallet(), { wrapper }); + result.current(wallet); + + expect(manager.connectedWallets.getValue()).toHaveLength(1); + expect(manager.connectedWallets.getValue()[0]).toEqual(wallet); + // add connected wallet should not set the active wallet + expect(manager.activeWalletStore.getValue()).toBeUndefined(); + }); + + it("should throw an error when used outside of ThirdwebProvider", () => { + // Render the hook without a provider + expect(() => { + renderHook(() => useAddConnectedWallet()); + }).toThrow("useAddConnectedWallet must be used within "); + }); +}); diff --git a/packages/thirdweb/src/react/core/hooks/wallets/useConnect.test.tsx b/packages/thirdweb/src/react/core/hooks/wallets/useConnect.test.tsx new file mode 100644 index 00000000000..eac4407c0ee --- /dev/null +++ b/packages/thirdweb/src/react/core/hooks/wallets/useConnect.test.tsx @@ -0,0 +1,105 @@ +import { renderHook } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { beforeEach, describe, expect, it } from "vitest"; +import { MockStorage } from "../../../../../test/src/mocks/storage.js"; +import { TEST_CLIENT } from "../../../../../test/src/test-clients.js"; +import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js"; +import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js"; +import { ethereum } from "../../../../chains/chain-definitions/ethereum.js"; +import { + type ConnectionManager, + createConnectionManager, +} from "../../../../wallets/manager/index.js"; +import { ConnectionManagerCtx } from "../../providers/connection-manager.js"; +import { useActiveWalletConnectionStatus } from "./useActiveWalletConnectionStatus.js"; +import { useConnect } from "./useConnect.js"; + +describe("useAddConnectedWallet", () => { + // Mock the connection manager + const mockStorage = new MockStorage(); + let manager: ConnectionManager; + + // Create a wrapper component with the mocked context + const wrapper = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); + }; + + const wallet = createWalletAdapter({ + adaptedAccount: TEST_ACCOUNT_A, + client: TEST_CLIENT, + chain: ethereum, + onDisconnect: () => {}, + switchChain: () => {}, + }); + + beforeEach(() => { + manager = createConnectionManager(mockStorage); + }); + + it("should connect a wallet to the connection manager", async () => { + const { result: statusResult } = renderHook( + () => useActiveWalletConnectionStatus(), + { + wrapper, + }, + ); + const { result } = renderHook(() => useConnect(), { wrapper }); + expect(statusResult.current).toEqual("disconnected"); + await result.current.connect(async () => wallet); + expect(statusResult.current).toEqual("connected"); + + // should add to connected wallets + expect(manager.connectedWallets.getValue()).toHaveLength(1); + expect(manager.connectedWallets.getValue()[0]).toEqual(wallet); + // should set the active wallet + expect(manager.activeWalletStore.getValue()).toEqual(wallet); + }); + + it("should handle a function that returns a wallet", async () => { + const { result: statusResult } = renderHook( + () => useActiveWalletConnectionStatus(), + { + wrapper, + }, + ); + const { result } = renderHook(() => useConnect(), { wrapper }); + expect(statusResult.current).toEqual("disconnected"); + await result.current.connect(async () => wallet); + expect(statusResult.current).toEqual("connected"); + + // should add to connected wallets + expect(manager.connectedWallets.getValue()).toHaveLength(1); + expect(manager.connectedWallets.getValue()[0]).toEqual(wallet); + // should set the active wallet + expect(manager.activeWalletStore.getValue()).toEqual(wallet); + }); + + it("should handle an error when connecting a wallet", async () => { + const { result: statusResult } = renderHook( + () => useActiveWalletConnectionStatus(), + { + wrapper, + }, + ); + expect(statusResult.current).toEqual("disconnected"); + const { result } = renderHook(() => useConnect(), { wrapper }); + await result.current.connect(async () => { + throw new Error("test"); + }); + + expect(statusResult.current).toEqual("disconnected"); + // should set the active wallet + expect(manager.activeWalletStore.getValue()).toEqual(undefined); + }); + + it("should throw an error when used outside of ThirdwebProvider", () => { + // Render the hook without a provider + expect(() => { + renderHook(() => useConnect()); + }).toThrow("useConnect must be used within "); + }); +}); diff --git a/packages/thirdweb/src/react/core/hooks/wallets/useSetActiveWallet.test.tsx b/packages/thirdweb/src/react/core/hooks/wallets/useSetActiveWallet.test.tsx new file mode 100644 index 00000000000..bef745a8d2a --- /dev/null +++ b/packages/thirdweb/src/react/core/hooks/wallets/useSetActiveWallet.test.tsx @@ -0,0 +1,53 @@ +import { renderHook } from "@testing-library/react"; +import type { ReactNode } from "react"; +import { describe, expect, it } from "vitest"; +import { MockStorage } from "../../../../../test/src/mocks/storage.js"; +import { TEST_CLIENT } from "../../../../../test/src/test-clients.js"; +import { TEST_ACCOUNT_A } from "../../../../../test/src/test-wallets.js"; +import { createWalletAdapter } from "../../../../adapters/wallet-adapter.js"; +import { ethereum } from "../../../../chains/chain-definitions/ethereum.js"; +import { createConnectionManager } from "../../../../wallets/manager/index.js"; +import { ConnectionManagerCtx } from "../../providers/connection-manager.js"; +import { useSetActiveWallet } from "./useSetActiveWallet.js"; + +describe("useAddConnectedWallet", () => { + // Mock the connection manager + const mockStorage = new MockStorage(); + const manager = createConnectionManager(mockStorage); + + // Create a wrapper component with the mocked context + const wrapper = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); + }; + + const wallet = createWalletAdapter({ + adaptedAccount: TEST_ACCOUNT_A, + client: TEST_CLIENT, + chain: ethereum, + onDisconnect: () => {}, + switchChain: () => {}, + }); + + it("should add a wallet to the connection manager", async () => { + // Render the hook + const { result } = renderHook(() => useSetActiveWallet(), { wrapper }); + result.current(wallet); + + // should add to connected wallets + expect(manager.connectedWallets.getValue()).toHaveLength(1); + expect(manager.connectedWallets.getValue()[0]).toEqual(wallet); + // should set the active wallet + expect(manager.activeWalletStore.getValue()).toEqual(wallet); + }); + + it("should throw an error when used outside of ThirdwebProvider", () => { + // Render the hook without a provider + expect(() => { + renderHook(() => useSetActiveWallet()); + }).toThrow("useSetActiveWallet must be used within "); + }); +}); diff --git a/packages/thirdweb/test/src/mocks/storage.ts b/packages/thirdweb/test/src/mocks/storage.ts index eecf647b761..35b1ebec24c 100644 --- a/packages/thirdweb/test/src/mocks/storage.ts +++ b/packages/thirdweb/test/src/mocks/storage.ts @@ -1,5 +1,6 @@ import { http, HttpResponse } from "msw"; import { getThirdwebDomains } from "../../../src/utils/domains.js"; +import type { AsyncStorage } from "../../../src/utils/storage/AsyncStorage.js"; export const handlers = [ http.post( @@ -7,11 +8,11 @@ export const handlers = [ async ({ request }) => { console.log("MSW handler hit for IPFS upload"); const formData = await request.formData(); - const files = formData.getAll('file'); + const files = formData.getAll("file"); const fileNames = files.map((file: any) => file.name); - + const mockIpfsHash = "QmTest1234567890TestHash"; - + if (fileNames.length === 1 && fileNames[0] === "file.txt") { return HttpResponse.json({ IpfsHash: mockIpfsHash }); } else { @@ -20,7 +21,7 @@ export const handlers = [ files: fileNames.reduce((acc, fileName) => { acc[fileName] = { cid: mockIpfsHash }; return acc; - }, {}) + }, {}), }); } }, @@ -40,3 +41,19 @@ export const handlers = [ }); }), ]; + +export class MockStorage implements AsyncStorage { + private storage: Map = new Map(); + + getItem(key: string): Promise { + return Promise.resolve(this.storage.get(key) ?? null); + } + setItem(key: string, value: string): Promise { + this.storage.set(key, value); + return Promise.resolve(); + } + removeItem(key: string): Promise { + this.storage.delete(key); + return Promise.resolve(); + } +}