From 5e2a8d90083d3704c354480efafea0264cda1c19 Mon Sep 17 00:00:00 2001 From: Likang0122 Date: Mon, 2 Dec 2024 15:56:27 +0800 Subject: [PATCH] chore: add test --- .../src/provider/__tests__/basic.test.tsx | 185 +++++++++++ .../src/provider/__tests__/connect.test.tsx | 299 ++++++++++++++++++ .../tron/src/provider/__tests__/utils.tsx | 26 ++ packages/tron/src/utils.ts | 11 - packages/tron/src/wallets/bitgetWallet.tsx | 3 +- packages/tron/src/wallets/bybitWallet.tsx | 3 +- packages/tron/src/wallets/okxTronWallet.tsx | 3 +- packages/tron/src/wallets/tronlinkWallet.tsx | 3 +- 8 files changed, 518 insertions(+), 15 deletions(-) create mode 100644 packages/tron/src/provider/__tests__/basic.test.tsx create mode 100644 packages/tron/src/provider/__tests__/connect.test.tsx create mode 100644 packages/tron/src/provider/__tests__/utils.tsx diff --git a/packages/tron/src/provider/__tests__/basic.test.tsx b/packages/tron/src/provider/__tests__/basic.test.tsx new file mode 100644 index 000000000..417da4172 --- /dev/null +++ b/packages/tron/src/provider/__tests__/basic.test.tsx @@ -0,0 +1,185 @@ +import { useEffect, useState } from 'react'; +import { useProvider } from '@ant-design/web3'; +import { fireEvent, render } from '@testing-library/react'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { TronWeb3ConfigProvider } from '../index'; +import { xrender } from './utils'; + +const mockCreateConnectionInstance = vi.fn(); + +describe('TronWeb3ConfigProvider', () => { + beforeEach(() => { + mockCreateConnectionInstance.mockClear(); + + vi.resetAllMocks(); + }); + + afterAll(() => { + vi.resetModules(); + }); + + const mockedData = vi.hoisted(() => { + const mockAddress = 'TUguNkmfvjeHanGyQZLGJfj28w1tMtvNDT'; + const balance = 10002; + + const mockedDisconnect = vi.fn(); + + return { + address: { + value: mockAddress, + }, + balance, + mockedDisconnect, + }; + }); + + vi.mock('@solana/wallet-adapter-react', async () => { + const originModules = await vi.importActual('@solana/wallet-adapter-react'); + const { remember } = await import('./utils'); + + const address = mockedData.address.value; + + const ConnectionProvider: React.FC< + React.PropsWithChildren<{ endpoint: string; config: any }> + > = ({ + children, + endpoint, + // default value: copy from ConnectionProvider in @solana/wallet-adapter-react + config = { commitment: 'confirmed' }, + }) => { + useEffect(() => { + mockCreateConnectionInstance(endpoint, config?.commitment); + }, [endpoint, config]); + + return ( +
+
{endpoint}
+
{config?.commitment}
+ {children} +
+ ); + }; + + const WalletProvider: React.FC = ({ children }) => <>{children}; + + const connectedRef = remember(false); + const currentWalletRef = remember(null); + + return { + ...originModules, + useWallet: () => { + // provide a state to emit re-render + const [, setConnected] = useState(connectedRef.value); + const [, setCurrentWallet] = useState(currentWalletRef.value); + + return { + address, + connected: connectedRef.value, + connect: () => { + connectedRef.value = true; + setConnected(true); + }, + select: (_wallet: any) => { + currentWalletRef.value = _wallet; + setCurrentWallet(_wallet); + }, + disconnect: () => { + mockedData.mockedDisconnect(); + }, + wallet: currentWalletRef.value, + }; + }, + ConnectionProvider, + WalletProvider, + }; + }); + + it('mount correctly', () => { + const App = () => ( + +
test
+
+ ); + + const { baseElement } = render(); + expect(baseElement.querySelector('.content')?.textContent).toBe('test'); + }); + + it('available show account address', async () => { + const { useWallet } = await import('@tronweb3/tronwallet-adapter-react-hooks'); + const connectRunned = vi.fn(); + + const Address: React.FC = () => { + const { account } = useProvider(); + return
{account?.address}
; + }; + + const App = () => { + const { connect } = useWallet(); + return ( + +
+
test
+ +
+
+
+ ); + }; + + const { selector } = xrender(App); + expect(selector('.content')?.textContent).toBe('test'); + + const connectBtn = selector('.connect')!; + const address = selector('.address'); + + expect(connectBtn).not.toBeNull(); + + // default address is empty + expect(address?.textContent).toBe(''); + + fireEvent.click(connectBtn); + + await vi.waitFor(() => { + expect(connectRunned).toBeCalled(); + expect(address?.textContent).toBe(mockedData.address.value); + }); + }); + + it('available disconnect', () => { + const CustomConnector: React.FC = () => { + const { disconnect } = useProvider(); + return ( +
+ +
+ ); + }; + + const App: React.FC = () => { + return ( + + + + ); + }; + + const { selector } = xrender(App); + + const btn = selector('button')!; + + expect(btn?.textContent).toBe('Disconnect'); + fireEvent.click(btn); + + expect(mockedData.mockedDisconnect).toBeCalled(); + }); +}); diff --git a/packages/tron/src/provider/__tests__/connect.test.tsx b/packages/tron/src/provider/__tests__/connect.test.tsx new file mode 100644 index 000000000..cef9dc2e7 --- /dev/null +++ b/packages/tron/src/provider/__tests__/connect.test.tsx @@ -0,0 +1,299 @@ +import { useEffect, useState } from 'react'; +import { useProvider } from '@ant-design/web3'; +import { fireEvent } from '@testing-library/react'; +import { WalletReadyState } from '@tronweb3/tronwallet-abstract-adapter'; +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { BitgetWallet, OkxTronWallet, TronlinkWallet } from '../../wallets'; +import { TronWeb3ConfigProvider } from '../index'; +import { xrender } from './utils'; + +const mockedData = vi.hoisted(() => { + const mockAddress = 'TUguNkmfvjeHanGyQZLGJfj28w1tMtvNDT'; + + return { + address: { + value: mockAddress, + }, + }; +}); + +const mockSelectWalletFn = vi.fn(); +const mockWalletChanged = vi.fn(); +const mockSelectWalletFnNotWalletName = vi.fn(); + +vi.mock('@solana/wallet-adapter-react', async () => { + const originModules = await vi.importActual('@solana/wallet-adapter-react'); + const { remember } = await import('./utils'); + + const publicKey = mockedData.address.value; + + const ConnectionProvider: React.FC> = ({ + children, + endpoint, + }) => ( +
+
{endpoint}
+ {children} +
+ ); + const WalletProvider: React.FC = ({ children }) => <>{children}; + + const connectedRef = remember(false); + const currentWalletRef = remember(null); + + const useWallet = () => { + // provide a state to emit re-render + const [connected, setConnected] = useState(() => 0); + const [currentWallet, setCurrentWallet] = useState(() => currentWalletRef.value); + const [connecting, setConnecting] = useState(() => false); + + useEffect(() => { + mockWalletChanged(); + + if (currentWallet) { + connectedRef.value = true; + setConnected((p) => p + 1); + } + }, [currentWallet]); + + useEffect(() => { + if (connecting) { + setConnecting(false); + connectedRef.value = true; + setConnected((p) => p + 1); + } + }, [connecting]); + + return { + publicKey, + connecting, + connected: connected, + connect: async () => { + setConnecting(true); + }, + select: (walletName: string | null) => { + mockSelectWalletFnNotWalletName(walletName); + const mockWalletAdapter = { + adapter: { name: walletName, readyState: WalletReadyState.Found }, + }; + currentWalletRef.value = mockWalletAdapter; + setCurrentWallet(mockWalletAdapter); + mockSelectWalletFn(); + }, + disconnect: () => {}, + wallet: currentWalletRef.value, + wallets: [ + { + adapter: { + name: 'Tronlink Wallet', + readyState: WalletReadyState.Found, + }, + }, + { + adapter: { + name: 'OKX Wallet', + readyState: WalletReadyState.Found, + }, + }, + { + adapter: { + name: 'Bitget Wallet', + readyState: WalletReadyState.Found, + }, + }, + ], + }; + }; + + return { + ...originModules, + useWallet, + ConnectionProvider, + WalletProvider, + }; +}); + +describe('Solana Connect', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + afterAll(() => { + vi.resetModules(); + vi.clearAllMocks(); + mockSelectWalletFn.mockClear(); + }); + + it('could connect', async () => { + const { useWallet } = await import('@tronweb3/tronwallet-adapter-react-hooks'); + const switchWalletRunned = vi.fn(); + const connectRunned = vi.fn(); + const gotAddressAfterConnect = vi.fn(); + + const CustomConnectBtn: React.FC = () => { + const { connect, availableWallets } = useProvider(); + const { connect: connectWallet } = useWallet(); + + const [connectRunDone, setConnectRunDone] = useState(false); + + return ( +
+ + +
{connectRunDone ? 'true' : 'false'}
+
+ ); + }; + + const App = () => { + return ( + +
+
test
+ +
+
+ ); + }; + + const { selector } = xrender(App); + expect(selector('.content')?.textContent).toBe('test'); + + const connectBtn = selector('.btn-connect')!; + const switchWalletBtn = selector('.btn-switchwallet')!; + const shownConnectRunDone = selector('.shown-connectRunDone')!; + + expect(connectBtn).not.toBeNull(); + expect(shownConnectRunDone.textContent).toBe('false'); + + fireEvent.click(switchWalletBtn); + await vi.waitFor(() => { + expect(switchWalletRunned).toBeCalled(); + }); + + await vi.waitFor(() => { + expect(mockWalletChanged).toBeCalled(); + }); + + fireEvent.click(connectBtn); + await vi.waitFor(() => { + // 1st. Phantom + // 2nd. Coinbase Wallet + // 3rd. null (because of no wallet installed) + expect(mockSelectWalletFn).toBeCalledTimes(3); + }); + + await vi.waitFor( + () => { + expect(connectRunned).toBeCalled(); + expect(shownConnectRunDone.textContent).toBe('true'); + expect(gotAddressAfterConnect).toBeCalledWith(mockedData.address.value); + }, + { + timeout: 5000, + }, + ); + }); + + it('call connect but not provide wallet', async () => { + const { useWallet } = await import('@tronweb3/tronwallet-adapter-react-hooks'); + const connectRunned = vi.fn(); + + const CustomConnectBtn: React.FC = () => { + const { connect } = useProvider(); + const { connect: connectWallet } = useWallet(); + + return ( +
+ +
+ ); + }; + + const App = () => { + return ( + +
+
test
+ +
+
+ ); + }; + + const { selector } = xrender(App); + expect(selector('.content')?.textContent).toBe('test'); + + const connectBtn = selector('.btn-connect')!; + expect(connectBtn).not.toBeNull(); + + fireEvent.click(connectBtn); + await vi.waitFor(() => { + expect(connectRunned).toBeCalled(); + expect(mockSelectWalletFnNotWalletName).toBeCalledWith(null); + }); + }); + + it('hasExtensionInstalled', async () => { + const Display = () => { + const { availableWallets } = useProvider(); + const [hasExtensionInstalled, setHasExtensionInstalled] = useState(false); + + useEffect(() => { + availableWallets![0]?.hasExtensionInstalled?.().then((v) => { + setHasExtensionInstalled(v); + }); + }, [availableWallets]); + + return
{hasExtensionInstalled ? 'true' : 'false'}
; + }; + + const App = () => { + return ( + + + + ); + }; + + const { selector } = xrender(App); + + const pluginCheck = selector('.plugin-check')!; + + await vi.waitFor(async () => { + expect(pluginCheck.textContent).toBe('true'); + }); + }); +}); diff --git a/packages/tron/src/provider/__tests__/utils.tsx b/packages/tron/src/provider/__tests__/utils.tsx new file mode 100644 index 000000000..43a5ea8b0 --- /dev/null +++ b/packages/tron/src/provider/__tests__/utils.tsx @@ -0,0 +1,26 @@ +import { render } from '@testing-library/react'; + +type RenderResult = ReturnType; +type RenderWithUtils = RenderResult & { + selector: (selector: string) => T | null; + selectors: (selector: string) => NodeListOf; +}; +type XRender = (Comp: React.FC, options?: Parameters[1]) => RenderWithUtils; + +export const xrender: XRender = (Comp, options) => { + const { baseElement, ...others } = render(, options); + return { + baseElement, + ...others, + selector: (selector) => baseElement.querySelector(selector), + selectors: (selector) => baseElement.querySelectorAll(selector), + }; +}; + +/** + * Need to wrap an object to ensure that when `use*` is called multiple times, + * the `value` returns the same value + */ +export function remember(value: T) { + return { value }; +} diff --git a/packages/tron/src/utils.ts b/packages/tron/src/utils.ts index 66755dacd..f5db31882 100644 --- a/packages/tron/src/utils.ts +++ b/packages/tron/src/utils.ts @@ -1,15 +1,4 @@ import { WalletReadyState } from '@tronweb3/tronwallet-abstract-adapter'; -import type { - AdapterWalletFactory as AdapterWalletFactoryType, - WalletFactory as WalletFactoryType, -} from './wallets/types'; - export const hasWalletReady = (readyState?: WalletReadyState) => readyState === WalletReadyState.Found || readyState === WalletReadyState.Loading; - -export const isAdapterWalletFactory = ( - factory: WalletFactoryType, -): factory is AdapterWalletFactoryType => { - return (factory as AdapterWalletFactoryType).adapter !== undefined; -}; diff --git a/packages/tron/src/wallets/bitgetWallet.tsx b/packages/tron/src/wallets/bitgetWallet.tsx index b4026336e..15dd04966 100644 --- a/packages/tron/src/wallets/bitgetWallet.tsx +++ b/packages/tron/src/wallets/bitgetWallet.tsx @@ -1,6 +1,7 @@ +import type { WalletMetadata } from '@ant-design/web3-common'; import { BitgetWalletColorful, ChromeCircleColorful } from '@ant-design/web3-icons'; -export const BitgetWallet = { +export const BitgetWallet: WalletMetadata = { icon: , name: 'Bitget', remark: 'Bitget', diff --git a/packages/tron/src/wallets/bybitWallet.tsx b/packages/tron/src/wallets/bybitWallet.tsx index d5993baac..eab48a225 100644 --- a/packages/tron/src/wallets/bybitWallet.tsx +++ b/packages/tron/src/wallets/bybitWallet.tsx @@ -1,6 +1,7 @@ +import type { WalletMetadata } from '@ant-design/web3-common'; import { BybitWalletCircleColorful, ChromeCircleColorful } from '@ant-design/web3-icons'; -export const BybitWallet = { +export const BybitWallet: WalletMetadata = { icon: , name: 'Bybit', remark: 'Bybit', diff --git a/packages/tron/src/wallets/okxTronWallet.tsx b/packages/tron/src/wallets/okxTronWallet.tsx index 3367e9970..26b78a9ed 100644 --- a/packages/tron/src/wallets/okxTronWallet.tsx +++ b/packages/tron/src/wallets/okxTronWallet.tsx @@ -1,6 +1,7 @@ import { metadata_OkxWallet } from '@ant-design/web3-assets'; +import type { WalletMetadata } from '@ant-design/web3-common'; -export const OkxTronWallet = { +export const OkxTronWallet: WalletMetadata = { ...metadata_OkxWallet, key: 'okxTronWallet', group: 'Popular', diff --git a/packages/tron/src/wallets/tronlinkWallet.tsx b/packages/tron/src/wallets/tronlinkWallet.tsx index fc9ba5c73..be5328244 100644 --- a/packages/tron/src/wallets/tronlinkWallet.tsx +++ b/packages/tron/src/wallets/tronlinkWallet.tsx @@ -1,6 +1,7 @@ +import type { WalletMetadata } from '@ant-design/web3-common'; import { ChromeCircleColorful, TronlinkColorful } from '@ant-design/web3-icons'; -export const TronlinkWallet = { +export const TronlinkWallet: WalletMetadata = { icon: , name: 'TronLink', remark: 'TronLink',