diff --git a/src/wallet/components/Wallet.test.tsx b/src/wallet/components/Wallet.test.tsx index 59c8b132bd..30fb78c03f 100644 --- a/src/wallet/components/Wallet.test.tsx +++ b/src/wallet/components/Wallet.test.tsx @@ -1,40 +1,119 @@ -import '@testing-library/jest-dom'; -import { render, screen, waitFor } from '@testing-library/react'; -import { useAccount, useConnect, useDisconnect } from 'wagmi'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ConnectWallet } from './ConnectWallet'; import { Wallet } from './Wallet'; import { WalletDropdown } from './WalletDropdown'; +import { useWalletContext } from './WalletProvider'; -vi.mock('wagmi', () => ({ - useAccount: vi.fn(), - useConnect: vi.fn(), - useDisconnect: vi.fn(), +vi.mock('./WalletProvider', () => ({ + useWalletContext: vi.fn(), + WalletProvider: ({ children }) => <>{children}</>, +})); + +vi.mock('./ConnectWallet', () => ({ + ConnectWallet: () => <div data-testid="connect-wallet">Connect Wallet</div>, +})); + +vi.mock('./WalletDropdown', () => ({ + WalletDropdown: () => ( + <div data-testid="wallet-dropdown">Wallet Dropdown</div> + ), })); describe('Wallet Component', () => { + let mockSetIsOpen: ReturnType<typeof vi.fn>; + beforeEach(() => { - vi.clearAllMocks(); - (useAccount as vi.Mock).mockReturnValue({ status: 'disconnected' }); - (useConnect as vi.Mock).mockReturnValue({ - connectors: [{ name: 'injected' }], - connect: vi.fn(), + mockSetIsOpen = vi.fn(); + (useWalletContext as ReturnType<typeof vi.fn>).mockReturnValue({ + isOpen: false, + setIsOpen: mockSetIsOpen, }); - (useDisconnect as vi.Mock).mockReturnValue({ disconnect: vi.fn() }); }); - it('should render the Wallet component with ConnectWallet', async () => { + it('should render the Wallet component with ConnectWallet', () => { render( <Wallet> <ConnectWallet /> - <WalletDropdown> - <div /> - </WalletDropdown> + <WalletDropdown /> </Wallet>, ); - await waitFor(() => { - expect( - screen.getByTestId('ockConnectWallet_Container'), - ).toBeInTheDocument(); + + expect(screen.getByTestId('connect-wallet')).toBeDefined(); + expect(screen.queryByTestId('wallet-dropdown')).toBeNull(); + }); + + it('should close the wallet when clicking outside', () => { + (useWalletContext as ReturnType<typeof vi.fn>).mockReturnValue({ + isOpen: true, + setIsOpen: mockSetIsOpen, }); + + render( + <Wallet> + <ConnectWallet /> + <WalletDropdown /> + </Wallet>, + ); + + expect(screen.getByTestId('wallet-dropdown')).toBeDefined(); + + fireEvent.click(document.body); + + expect(mockSetIsOpen).toHaveBeenCalledWith(false); + }); + + it('should not close the wallet when clicking inside', () => { + (useWalletContext as ReturnType<typeof vi.fn>).mockReturnValue({ + isOpen: true, + setIsOpen: mockSetIsOpen, + }); + + render( + <Wallet> + <ConnectWallet /> + <WalletDropdown /> + </Wallet>, + ); + + const walletDropdown = screen.getByTestId('wallet-dropdown'); + expect(walletDropdown).toBeDefined(); + + fireEvent.click(walletDropdown); + + expect(mockSetIsOpen).not.toHaveBeenCalled(); + }); + + it('should not trigger click handler when wallet is closed', () => { + render( + <Wallet> + <ConnectWallet /> + <WalletDropdown /> + </Wallet>, + ); + + expect(screen.queryByTestId('wallet-dropdown')).toBeNull(); + + fireEvent.click(document.body); + + expect(mockSetIsOpen).not.toHaveBeenCalled(); + }); + + it('should remove event listener on unmount', () => { + const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener'); + + const { unmount } = render( + <Wallet> + <ConnectWallet /> + <WalletDropdown /> + </Wallet>, + ); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith( + 'click', + expect.any(Function), + ); }); }); diff --git a/src/wallet/components/Wallet.tsx b/src/wallet/components/Wallet.tsx index 27829db84a..35b48c5089 100644 --- a/src/wallet/components/Wallet.tsx +++ b/src/wallet/components/Wallet.tsx @@ -1,46 +1,52 @@ -import { Children, useMemo, useRef, useEffect } from 'react'; +import { Children, useEffect, useMemo, useRef } from 'react'; import type { WalletReact } from '../types'; import { ConnectWallet } from './ConnectWallet'; import { WalletDropdown } from './WalletDropdown'; import { WalletProvider, useWalletContext } from './WalletProvider'; -function WalletContent({ children }: WalletReact) { +const WalletContent = ({ children }: WalletReact) => { const { isOpen, setIsOpen } = useWalletContext(); - const containerRef = useRef<HTMLDivElement>(null); + const walletContainerRef = useRef<HTMLDivElement>(null); const { connect, dropdown } = useMemo(() => { const childrenArray = Children.toArray(children); return { + // @ts-ignore connect: childrenArray.filter(({ type }) => type === ConnectWallet), + // @ts-ignore dropdown: childrenArray.filter(({ type }) => type === WalletDropdown), }; }, [children]); + // Handle clicking outside the wallet component to close the dropdown. useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (containerRef.current && !containerRef.current.contains(event.target as Node) && isOpen) { + const handleClickOutsideComponent = (event: MouseEvent) => { + if ( + walletContainerRef.current && + !walletContainerRef.current.contains(event.target as Node) && + isOpen + ) { setIsOpen(false); } }; - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; + document.addEventListener('click', handleClickOutsideComponent); + return () => + document.removeEventListener('click', handleClickOutsideComponent); }, [isOpen, setIsOpen]); return ( - <div ref={containerRef} className="relative w-fit shrink-0"> + <div ref={walletContainerRef} className="relative w-fit shrink-0"> {connect} {isOpen && dropdown} </div> ); -} +}; -export function Wallet({ children }: WalletReact) { +export const Wallet = ({ children }: WalletReact) => { return ( <WalletProvider> <WalletContent>{children}</WalletContent> </WalletProvider> ); -} \ No newline at end of file +};