UNPKG

@reown/appkit-controllers

Version:

The full stack toolkit to build onchain app UX.

271 lines • 13.5 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import {} from '@reown/appkit-common'; import { ChainController, ConnectionController, CoreHelperUtil, EventsController, RouterController, SendController, SnackController } from '../../exports/index.js'; import { extendedMainnet, mockChainControllerState, solanaCaipNetwork } from '../../exports/testing.js'; import { BalanceUtil } from '../../src/utils/BalanceUtil.js'; // -- Setup -------------------------------------------------------------------- const token = { name: 'Optimism', address: 'eip155:10:0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', symbol: 'OP', chainId: 'eip155:10', value: 6.05441523113072, price: 4.5340112, quantity: { decimals: '18', numeric: '1.335333100000000000' }, iconUrl: 'https://token-icons.s3.amazonaws.com/0x4200000000000000000000000000000000000042.png' }; const sendTokenAmount = '0.1'; const receiverAddress = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'; const receiverProfileName = 'john.eth'; const receiverProfileImageUrl = 'https://ipfs.com/0x123.png'; // -- Tests -------------------------------------------------------------------- describe('SendController', () => { it('should have valid default state', () => { expect(SendController.state).toEqual({ tokenBalances: [], loading: false }); }); it('should update state correctly on setToken()', () => { SendController.setToken(token); expect(SendController.state.token).toEqual(token); }); it('should update state correctly on setTokenAmount()', () => { SendController.setTokenAmount(sendTokenAmount); expect(SendController.state.sendTokenAmount).toEqual(sendTokenAmount); }); it('should update state correctly on receiverAddress()', () => { SendController.setReceiverAddress(receiverAddress); expect(SendController.state.receiverAddress).toEqual(receiverAddress); }); it('should update state correctly on receiverProfileName()', () => { SendController.setReceiverProfileName(receiverProfileName); expect(SendController.state.receiverProfileName).toEqual(receiverProfileName); }); it('should update state correctly on receiverProfileName()', () => { SendController.setReceiverProfileImageUrl(receiverProfileImageUrl); expect(SendController.state.receiverProfileImageUrl).toEqual(receiverProfileImageUrl); }); it('should update state correctly on resetSend()', () => { SendController.resetSend(); expect(SendController.state).toEqual({ tokenBalances: [], loading: false }); }); describe('fetchTokenBalance()', () => { beforeEach(() => { mockChainControllerState({ activeCaipNetwork: extendedMainnet, activeCaipAddress: 'eip155:1:0x123' }); vi.spyOn(BalanceUtil, 'getMyTokensWithBalance').mockResolvedValue([]); vi.spyOn(CoreHelperUtil, 'isAllowedRetry').mockReturnValue(true); vi.spyOn(SnackController, 'showError').mockImplementation(() => { }); }); it('should not fetch balance if its not allowed to retry', async () => { vi.spyOn(CoreHelperUtil, 'isAllowedRetry').mockReturnValue(false); SendController.state.lastRetry = Date.now(); const result = await SendController.fetchTokenBalance(); expect(result).toEqual([]); expect(BalanceUtil.getMyTokensWithBalance).not.toHaveBeenCalled(); expect(SendController.state.loading).toBe(false); }); it('should not fetch balance if chainId is not defined', async () => { mockChainControllerState({ activeCaipNetwork: { ...extendedMainnet, // @ts-expect-error - edge case caipNetworkId: undefined } }); const result = await SendController.fetchTokenBalance(); expect(result).toEqual([]); expect(BalanceUtil.getMyTokensWithBalance).not.toHaveBeenCalled(); }); it('should not fetch balance if namespace is not defined', async () => { mockChainControllerState({ // @ts-expect-error - edge case activeCaipNetwork: { ...extendedMainnet, chainNamespace: undefined }, activeCaipAddress: 'eip155:1:0x123' }); const result = await SendController.fetchTokenBalance(); expect(result).toEqual([]); expect(BalanceUtil.getMyTokensWithBalance).not.toHaveBeenCalled(); }); it('should not fetch balance if address is not defined', async () => { mockChainControllerState({ activeCaipNetwork: extendedMainnet, activeCaipAddress: undefined }); const result = await SendController.fetchTokenBalance(); expect(result).toEqual([]); expect(BalanceUtil.getMyTokensWithBalance).not.toHaveBeenCalled(); }); it('should set the retry if something fails', async () => { const mockError = new Error('API Error'); vi.spyOn(BalanceUtil, 'getMyTokensWithBalance').mockRejectedValue(mockError); const onError = vi.fn(); const now = Date.now(); vi.setSystemTime(now); const result = await SendController.fetchTokenBalance(onError); expect(result).toEqual([]); expect(SendController.state.lastRetry).toBe(now); expect(onError).toHaveBeenCalledWith(mockError); expect(SnackController.showError).toHaveBeenCalledWith('Token Balance Unavailable'); }); it('should fetch balance if everything is correct', async () => { const mockBalances = [ { quantity: { decimals: '18' }, symbol: 'ETH', address: '0x123' }, { quantity: { decimals: '0' }, symbol: 'ZERO', address: '0x456' }, { quantity: { decimals: '6' }, symbol: 'USDC', address: '0x789' } ]; vi.spyOn(BalanceUtil, 'getMyTokensWithBalance').mockResolvedValue(mockBalances); const result = await SendController.fetchTokenBalance(); expect(result).toEqual(mockBalances); expect(SendController.state.tokenBalances).toEqual(mockBalances); expect(SendController.state.lastRetry).toBeUndefined(); expect(SendController.state.loading).toBe(false); }); it('should use ChainController.getAccountData before falling back to activeCaipAddress', async () => { const mockNamespace = 'eip155'; const mockCaipAddressFromAccount = 'eip155:1:0xChainController'; const mockActiveCaipAddress = 'eip155:1:0xChainController'; mockChainControllerState({ activeCaipNetwork: extendedMainnet, activeChain: mockNamespace, activeCaipAddress: mockActiveCaipAddress }); const getCaipAddressSpy = vi.spyOn(ChainController, 'getAccountData').mockReturnValue({ caipAddress: mockCaipAddressFromAccount }); vi.spyOn(BalanceUtil, 'getMyTokensWithBalance').mockResolvedValue([]); await SendController.fetchTokenBalance(); expect(getCaipAddressSpy).toHaveBeenCalledWith(mockNamespace); }); it('should fallback to activeCaipAddress when ChainController.getAccountData returns undefined', async () => { const mockNamespace = 'eip155'; const mockActiveCaipAddress = 'eip155:1:0xFallback'; mockChainControllerState({ activeCaipNetwork: extendedMainnet, activeChain: mockNamespace, activeCaipAddress: mockActiveCaipAddress }); const getCaipAddressSpy = vi .spyOn(ChainController, 'getAccountData') .mockReturnValue(undefined); const getPlainAddressSpy = vi.spyOn(CoreHelperUtil, 'getPlainAddress'); vi.spyOn(BalanceUtil, 'getMyTokensWithBalance').mockResolvedValue([]); await SendController.fetchTokenBalance(); expect(getCaipAddressSpy).toHaveBeenCalledWith(mockNamespace); expect(getPlainAddressSpy).toHaveBeenCalledWith(mockActiveCaipAddress); }); }); describe('sendSolanaToken()', () => { beforeEach(() => { vi.spyOn(RouterController, 'pushTransactionStack').mockImplementation(() => { }); vi.spyOn(RouterController, 'replace').mockImplementation(() => { }); vi.spyOn(ConnectionController, 'sendTransaction').mockResolvedValue(undefined); vi.spyOn(ConnectionController, '_getClient').mockReturnValue({ updateBalance: vi.fn() }); vi.spyOn(CoreHelperUtil, 'isCaipAddress').mockReturnValue(false); vi.spyOn(SendController, 'resetSend').mockImplementation(() => { }); vi.spyOn(EventsController, 'sendEvent').mockImplementation(() => { }); mockChainControllerState({ activeCaipNetwork: extendedMainnet }); }); it('should call sendTransaction without tokenMint', async () => { SendController.setTokenAmount('0.1'); SendController.setReceiverAddress('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM'); await SendController.sendSolanaToken(); expect(RouterController.pushTransactionStack).toHaveBeenCalledWith({ onSuccess: expect.any(Function) }); expect(ConnectionController.sendTransaction).toHaveBeenCalledWith({ chainNamespace: 'solana', tokenMint: undefined, to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', value: 0.1 }); expect(ConnectionController._getClient()?.updateBalance).toHaveBeenCalledWith('solana'); expect(SendController.resetSend).toHaveBeenCalled(); }); it('should call sendTransaction with tokenMint', async () => { vi.spyOn(CoreHelperUtil, 'isCaipAddress').mockReturnValue(true); const solanaToken = { name: 'USDC', address: 'solana:mainnet:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', symbol: 'USDC', chainId: 'solana:mainnet', value: 100, price: 1, quantity: { decimals: '6', numeric: '100000000' } }; SendController.setToken(solanaToken); SendController.setTokenAmount('50'); SendController.setReceiverAddress('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM'); await SendController.sendSolanaToken(); expect(RouterController.pushTransactionStack).toHaveBeenCalledWith({ onSuccess: expect.any(Function) }); expect(ConnectionController.sendTransaction).toHaveBeenCalledWith({ chainNamespace: 'solana', tokenMint: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', value: 50 }); expect(ConnectionController._getClient()?.updateBalance).toHaveBeenCalledWith('solana'); expect(SendController.resetSend).toHaveBeenCalled(); }); it('should trigger SEND_SUCCESS event after successful transaction', async () => { mockChainControllerState({ activeCaipNetwork: solanaCaipNetwork }); const solanaToken = { name: 'SOL', address: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:11111111111111111111111111111111', symbol: 'SOL', chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', value: 100, price: 1, quantity: { decimals: '9', numeric: '100000000' } }; SendController.setToken(solanaToken); SendController.setTokenAmount('50'); SendController.setReceiverAddress('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM'); await SendController.sendSolanaToken(); expect(EventsController.sendEvent).toHaveBeenCalledWith({ type: 'track', event: 'SEND_SUCCESS', properties: { isSmartAccount: false, token: 'SOL', amount: 50, network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', hash: '' } }); }); }); describe('resetSend()', () => { it('should not reset the hash', () => { /* * DO NOT RESET SendController.state.hash as it is required * to track the hash for the appKit.openSend(...) function */ SendController.state.hash = '0x123'; SendController.resetSend(); expect(SendController.state.hash).toEqual('0x123'); }); }); }); //# sourceMappingURL=SendController.test.js.map