import { useAnalytics } from '@/core/analytics/hooks/useAnalytics'; import { WalletEvent, WalletOption } from '@/core/analytics/types'; import { useOnchainKit } from '@/useOnchainKit'; import { fireEvent, render, screen } from '@testing-library/react'; import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { useAccount } from 'wagmi'; import { WalletAdvancedTransactionActions } from './WalletAdvancedTransactionActions'; import { useWalletContext } from './WalletProvider'; import { usePortfolio } from '../hooks/usePortfolio'; vi.mock('wagmi', () => ({ useAccount: vi.fn(), })); vi.mock('@/useOnchainKit', () => ({ useOnchainKit: vi.fn(), })); vi.mock('./WalletProvider', () => ({ useWalletContext: vi.fn(), })); vi.mock('../hooks/usePortfolio', () => ({ usePortfolio: vi.fn(), })); vi.mock('@/core/analytics/hooks/useAnalytics', () => ({ useAnalytics: vi.fn(() => ({ sendAnalytics: vi.fn(), })), })); describe('WalletAdvancedTransactionActons', () => { const mockUseOnchainKit = useOnchainKit as ReturnType; const mockUseAccount = useAccount as ReturnType; const mockUseWalletContext = useWalletContext as ReturnType; const defaultMockUseWalletAdvancedContext = { setActiveFeature: vi.fn(), animations: { content: '', }, }; const mockProjectId = '123-ABC'; const mockAddress = '0x123'; const mockChain = { name: 'Base' }; const mockSendAnalytics = vi.fn(); beforeEach(() => { vi.clearAllMocks(); vi.spyOn(window, 'open').mockImplementation(() => null); mockUseOnchainKit.mockReturnValue({ projectId: mockProjectId, chain: mockChain, }); mockUseAccount.mockReturnValue({ address: mockAddress, }); mockUseWalletContext.mockReturnValue({ ...defaultMockUseWalletAdvancedContext, }); (useAnalytics as ReturnType).mockReturnValue({ sendAnalytics: mockSendAnalytics, }); (usePortfolio as Mock).mockReturnValue({ data: { tokenBalances: [], }, isFetching: false, }); }); it('renders the WalletAdvancedTransactionActions component', () => { render(); const buyButton = screen.getByRole('button', { name: 'Buy' }); const sendButton = screen.getByRole('button', { name: 'Send' }); const swapButton = screen.getByRole('button', { name: 'Swap' }); expect(buyButton).toBeDefined(); expect(sendButton).toBeDefined(); expect(swapButton).toBeDefined(); }); it('opens the buy page when the buy button is clicked and projectId, address and chain.name are defined', () => { render(); const buyButton = screen.getByRole('button', { name: 'Buy' }); fireEvent.click(buyButton); const urlString = vi.mocked(window.open).mock.calls[0][0] as string; const url = new URL(urlString); const params = Object.fromEntries(url.searchParams); expect(url.origin + url.pathname).toBe( 'https://pay.coinbase.com/buy/select-asset', ); expect(params).toEqual({ appId: mockProjectId, destinationWallets: JSON.stringify([ { address: mockAddress, blockchains: [mockChain.name.toLowerCase()], }, ]), defaultAsset: 'USDC', defaultPaymentMethod: 'CRYPTO_ACCOUNT', presetFiatAmount: '25', }); expect(vi.mocked(window.open).mock.calls[0][1]).toBe('popup'); expect(vi.mocked(window.open).mock.calls[0][2]).toBe( 'width=400,height=600,scrollbars=yes', ); }); it('does not open the buy page when the buy button is clicked and projectId, address or chain.name are not defined', () => { mockUseOnchainKit.mockReturnValue({ projectId: null, chain: mockChain, }); const { rerender } = render(); const buyButton = screen.getByRole('button', { name: 'Buy' }); fireEvent.click(buyButton); expect(window.open).not.toHaveBeenCalled(); mockUseOnchainKit.mockReturnValue({ projectId: mockProjectId, chain: mockChain, }); mockUseAccount.mockReturnValue({ address: null, }); rerender(); fireEvent.click(buyButton); expect(window.open).not.toHaveBeenCalled(); mockUseAccount.mockReturnValue({ address: mockAddress, }); mockUseOnchainKit.mockReturnValue({ projectId: mockProjectId, chain: null, }); rerender(); fireEvent.click(buyButton); expect(window.open).not.toHaveBeenCalled(); }); it('sets activeFeature to send when the send button is clicked', () => { render(); const sendButton = screen.getByRole('button', { name: 'Send' }); fireEvent.click(sendButton); expect( defaultMockUseWalletAdvancedContext.setActiveFeature, ).toHaveBeenCalledWith('send'); }); it('sets activeFeature to swap when the swap button is clicked', () => { render(); const swapButton = screen.getByRole('button', { name: 'Swap' }); fireEvent.click(swapButton); expect( defaultMockUseWalletAdvancedContext.setActiveFeature, ).toHaveBeenCalledWith('swap'); }); it('renders a placeholder when fetcher is loading', () => { (usePortfolio as Mock).mockReturnValue({ data: { tokenBalances: [], }, isFetching: true, }); render(); const placeholder = screen.getByTestId('ockSkeleton'); expect(placeholder).toHaveClass('my-3 h-16 w-80'); }); it('applies custom classNames when provided', () => { const customClassNames = { container: 'custom-container', leftAction: { container: 'custom-left-button', icon: 'custom-left-icon', label: 'custom-left-label', }, middleAction: { container: 'custom-middle-button', icon: 'custom-middle-icon', label: 'custom-middle-label', }, rightAction: { container: 'custom-right-button', icon: 'custom-right-icon', label: 'custom-right-label', }, }; render(); const container = screen.getByTestId( 'ockWalletAdvanced_TransactionActions', ); expect(container.className).toContain('custom-container'); const buyButton = screen.getByRole('button', { name: 'Buy' }); expect(buyButton.className).toContain('custom-left-button'); expect(buyButton.querySelector('span:first-child')?.className).toContain( 'custom-left-icon', ); expect(buyButton.querySelector('span:last-child')?.className).toContain( 'custom-left-label', ); const sendButton = screen.getByRole('button', { name: 'Send' }); expect(sendButton.className).toContain('custom-middle-button'); expect(sendButton.querySelector('span:first-child')?.className).toContain( 'custom-middle-icon', ); expect(sendButton.querySelector('span:last-child')?.className).toContain( 'custom-middle-label', ); const swapButton = screen.getByRole('button', { name: 'Swap' }); expect(swapButton.className).toContain('custom-right-button'); expect(swapButton.querySelector('span:first-child')?.className).toContain( 'custom-right-icon', ); expect(swapButton.querySelector('span:last-child')?.className).toContain( 'custom-right-label', ); }); describe('analytics', () => { it('sends analytics when buy button is clicked', () => { render(); const buyButton = screen.getByRole('button', { name: 'Buy' }); fireEvent.click(buyButton); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.OptionSelected, { option: WalletOption.Buy, }, ); }); it('sends analytics when send button is clicked', () => { render(); const sendButton = screen.getByRole('button', { name: 'Send' }); fireEvent.click(sendButton); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.OptionSelected, { option: WalletOption.Send, }, ); }); it('sends analytics when swap button is clicked', () => { render(); const swapButton = screen.getByRole('button', { name: 'Swap' }); fireEvent.click(swapButton); expect(mockSendAnalytics).toHaveBeenCalledWith( WalletEvent.OptionSelected, { option: WalletOption.Swap, }, ); }); it('sends analytics for buy action only when required parameters are present', () => { mockUseOnchainKit.mockReturnValue({ projectId: null, chain: mockChain, }); const { rerender } = render(); const buyButton = screen.getByRole('button', { name: 'Buy' }); fireEvent.click(buyButton); expect(mockSendAnalytics).toHaveBeenCalledTimes(1); expect(window.open).not.toHaveBeenCalled(); mockUseOnchainKit.mockReturnValue({ projectId: mockProjectId, chain: mockChain, }); rerender(); fireEvent.click(buyButton); expect(mockSendAnalytics).toHaveBeenCalledTimes(2); expect(window.open).toHaveBeenCalled(); }); it('does not send duplicate analytics events on multiple clicks', () => { render(); const buyButton = screen.getByRole('button', { name: 'Buy' }); fireEvent.click(buyButton); fireEvent.click(buyButton); const sendButton = screen.getByRole('button', { name: 'Send' }); fireEvent.click(sendButton); fireEvent.click(sendButton); const swapButton = screen.getByRole('button', { name: 'Swap' }); fireEvent.click(swapButton); fireEvent.click(swapButton); expect(mockSendAnalytics).toHaveBeenCalledTimes(6); expect(mockSendAnalytics).toHaveBeenNthCalledWith( 1, WalletEvent.OptionSelected, { option: WalletOption.Buy }, ); expect(mockSendAnalytics).toHaveBeenNthCalledWith( 2, WalletEvent.OptionSelected, { option: WalletOption.Buy }, ); expect(mockSendAnalytics).toHaveBeenNthCalledWith( 3, WalletEvent.OptionSelected, { option: WalletOption.Send }, ); expect(mockSendAnalytics).toHaveBeenNthCalledWith( 4, WalletEvent.OptionSelected, { option: WalletOption.Send }, ); expect(mockSendAnalytics).toHaveBeenNthCalledWith( 5, WalletEvent.OptionSelected, { option: WalletOption.Swap }, ); expect(mockSendAnalytics).toHaveBeenNthCalledWith( 6, WalletEvent.OptionSelected, { option: WalletOption.Swap }, ); }); }); });