import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import sdk from '@farcaster/miniapp-sdk'; import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { useSendToken } from './useSendToken'; const sendTokenMock = { success: true, }; vi.mock('@farcaster/miniapp-sdk', () => ({ default: { actions: { sendToken: vi.fn(), }, }, })); const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); // eslint-disable-next-line react/display-name return ({ children }: { children: React.ReactNode }) => ( {children} ); }; describe('useSendToken', () => { beforeEach(() => { (sdk.actions.sendToken as Mock).mockResolvedValue(sendTokenMock); vi.clearAllMocks(); }); it('should return sendToken and sendTokenAsync functions', () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); expect(typeof result.current.sendToken).toBe('function'); expect(typeof result.current.sendTokenAsync).toBe('function'); }); it('should call sdk.actions.sendToken when sendToken is executed', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should call sdk.actions.sendToken when sendTokenAsync is executed', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { await result.current.sendTokenAsync(params); }); expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); it('should pass token parameter correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should pass amount parameter correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { amount: '1000000', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should pass recipientAddress parameter correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should pass recipientFid parameter correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { recipientFid: 12345, }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should pass all parameters correctly when provided', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', recipientFid: 12345, }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle empty parameters object', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = {}; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should pass different token formats correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); // Test with ETH native token on Ethereum mainnet const ethParams = { token: 'eip155:1/native', amount: '1000000000000000000', // 1 ETH in wei recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(ethParams); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(ethParams); }); }); it('should pass polygon token formats correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); // Test with Polygon tokens const polygonParams = { token: 'eip155:137/erc20:0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC on Polygon amount: '1000000', // 1 USDC recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(polygonParams); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(polygonParams); }); }); it('should return the send token result on success', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; let response; await act(async () => { response = await result.current.sendTokenAsync(params); }); expect(response).toBe(sendTokenMock); }); it('should handle errors when sendToken fails', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); (sdk.actions.sendToken as Mock).mockRejectedValue( new Error('Send token failed'), ); const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(result.current.isError).toBe(true); }); consoleSpy.mockRestore(); }); it('should handle mutation states correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); expect(result.current.isPending).toBe(false); expect(result.current.isError).toBe(false); expect(result.current.isSuccess).toBe(false); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; act(() => { result.current.sendToken(params); }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); }); it('should handle network error correctly', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); (sdk.actions.sendToken as Mock).mockRejectedValue( new Error('Network error'), ); const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { try { await result.current.sendTokenAsync(params); } catch (error) { expect(error).toBeInstanceOf(Error); expect((error as Error).message).toBe('Network error'); } }); consoleSpy.mockRestore(); }); it('should handle large amount values', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:1/erc20:0xA0b86a33E6441D5D56b5FF87d65f2B8e9F90C54c', amount: '999999999999999999999999', // Very large amount recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle decimal amount values correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1500000', // 1.5 USDC (with 6 decimals) recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should reset mutation state on multiple calls', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params1 = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; const params2 = { token: 'eip155:10/native', amount: '2000000', recipientFid: 54321, }; // First call await act(async () => { result.current.sendToken(params1); }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); }); // Reset and second call await act(async () => { result.current.reset(); }); await waitFor(() => { expect(result.current.isSuccess).toBe(false); expect(result.current.isError).toBe(false); }); await act(async () => { result.current.sendToken(params2); }); await waitFor(() => { expect(result.current.isSuccess).toBe(true); expect(sdk.actions.sendToken).toHaveBeenCalledWith(params2); }); }); it('should handle sending to recipient via FID only', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientFid: 12345, }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle sending to recipient via address only', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle sending with both recipient address and FID', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', recipientFid: 12345, }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle zero FID value', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', amount: '1000000', recipientFid: 0, }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle very small amount values', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); const params = { token: 'eip155:1/native', amount: '1', // 1 wei recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(params); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(params); }); }); it('should handle different chain IDs correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); // Test with Arbitrum const arbitrumParams = { token: 'eip155:42161/erc20:0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // USDC on Arbitrum amount: '1000000', recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(arbitrumParams); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(arbitrumParams); }); }); it('should handle Optimism token formats correctly', async () => { const { result } = renderHook(() => useSendToken(), { wrapper: createWrapper(), }); // Test with Optimism tokens const optimismParams = { token: 'eip155:10/erc20:0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC on Optimism amount: '5000000', // 5 USDC recipientAddress: '0x1234567890123456789012345678901234567890', }; await act(async () => { result.current.sendToken(optimismParams); }); await waitFor(() => { expect(sdk.actions.sendToken).toHaveBeenCalledWith(optimismParams); }); }); });