import { act } from 'react'; import { render, RenderResult, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import { useNFTLifecycleContext } from '@/nft/components/NFTLifecycleProvider'; import { useNFTContext } from '@/nft/components/NFTProvider'; import { NFTMintButton } from '@/nft/components/mint/NFTMintButton'; import { useOnchainKit } from '@/useOnchainKit'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { base } from 'viem/chains'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { http, WagmiProvider, createConfig, useAccount, useChainId, } from 'wagmi'; import { mock } from 'wagmi/connectors'; vi.mock('@/nft/components/NFTProvider'); vi.mock('@/nft/components/NFTLifecycleProvider'); vi.mock('@/useOnchainKit'); vi.mock('wagmi', async (importOriginal) => { return { ...(await importOriginal()), useAccount: vi.fn(), useChainId: vi.fn(), }; }); vi.mock('@/internal/components/Spinner', () => ({ Spinner: () =>
Spinner
, })); vi.mock('@/transaction', async (importOriginal) => { return { ...(await importOriginal()), TransactionLifecycleStatus: vi.fn(), TransactionButton: ({ text, disabled, }: { text: string; disabled: boolean; }) => ( ), Transaction: ({ onStatus, children, capabilities, }: { onStatus: (status: { statusName: string }) => void; children: React.ReactNode; capabilities: { paymasterService: { url: string } }; }) => ( <>
{capabilities?.paymasterService?.url}
{children} ), TransactionSponsor: vi.fn(), TransactionStatus: vi.fn(), TransactionStatusAction: vi.fn(), TransactionStatusLabel: vi.fn(), }; }); vi.mock('@/wallet', () => ({ ConnectWallet: () =>
ConnectWallet
, })); const queryClient = new QueryClient(); const accountConfig = createConfig({ chains: [base], connectors: [ mock({ accounts: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'], }), ], transports: { [base.id]: http(), }, }); const TestProviders = ({ children }: { children: React.ReactNode }) => { return ( {children} ); }; describe('NFTMintButton', () => { let mockUpdateLifecycleStatus: Mock; beforeEach(() => { (useNFTContext as Mock).mockReturnValue({ contractAddress: '0x123', name: 'name', tokenId: '1', quantity: 1, isEligibleToMint: true, buildMintTransaction: vi .fn() .mockResolvedValue({ to: '0x123', data: '0x123', value: BigInt(2) }), }); mockUpdateLifecycleStatus = vi.fn(); (useNFTLifecycleContext as Mock).mockReturnValue({ updateLifecycleStatus: mockUpdateLifecycleStatus, }); (useAccount as Mock).mockReturnValue({ address: '0xabc' }); (useChainId as Mock).mockReturnValue(1); (useOnchainKit as Mock).mockReturnValue({ config: undefined, }); }); it('should render the mint button with default label', async () => { const { findByText } = render( , ); expect(await findByText('Mint')).toBeInTheDocument(); }); it('should render the mint button with custom label', async () => { const { findByText } = render( , ); expect(await findByText('Custom Mint')).toBeInTheDocument(); }); it('should call updateLifecycleStatus with transactionPending status when transactionStatus is transactionPending', async () => { await act(async () => { render( , ); }); await act(async () => { screen.getByText('TransactionPending').click(); }); expect(mockUpdateLifecycleStatus).toHaveBeenCalledWith({ statusName: 'transactionPending', }); }); it('should call updateLifecycleStatus with transaction status when transactionStatus is transactionLegacyExecuted', async () => { await act(async () => { render( , ); }); await act(async () => { screen.getByText('TransactionLegacyExecuted').click(); }); expect(mockUpdateLifecycleStatus).toHaveBeenCalledWith({ statusName: 'transactionLegacyExecuted', }); }); it('should call updateLifecycleStatus with transaction status when transactionStatus is success', async () => { await act(async () => { render( , ); }); await act(async () => { screen.getByText('Success').click(); }); expect(mockUpdateLifecycleStatus).toHaveBeenCalledWith({ statusName: 'success', }); }); it('should call updateLifecycleStatus with transaction status when transactionStatus is error', async () => { await act(async () => { render( , ); }); await act(async () => { screen.getByText('Error').click(); }); expect(mockUpdateLifecycleStatus).toHaveBeenCalledWith({ statusName: 'error', }); }); it('should render ConnectWallet when address is not available', () => { (useAccount as Mock).mockReturnValue({ address: null }); const { getByText } = render( , ); expect(getByText('ConnectWallet')).toBeInTheDocument(); }); it('should not render when buildMintTransaction is undefined', () => { (useNFTContext as Mock).mockReturnValue({ contractAddress: '0x123', tokenId: '1', quantity: 1, isEligibleToMint: true, }); const { container } = render( , ); expect(container.firstChild).toBeNull(); }); it('should disable button if disabled props is true', async () => { const { findByText } = render( , ); expect(await findByText('Mint')).toBeDisabled(); }); it('should show minting not available when isEligibleToMint is false', () => { (useNFTContext as Mock).mockReturnValue({ contractAddress: '0x123', tokenId: '1', quantity: 1, isEligibleToMint: false, buildMintTransaction: vi .fn() .mockResolvedValue({ to: '0x123', data: '0x123', value: BigInt(2) }), }); const { getByText } = render( , ); expect(getByText('Minting not available')).toBeInTheDocument(); }); it('calls buildMintTransaction when quantity changes', async () => { const buildMintTransactionMock = vi.fn().mockResolvedValue([]); (useNFTContext as Mock).mockReturnValueOnce({ contractAddress: '0x123', tokenId: '1', name: 'name', isEligibleToMint: true, buildMintTransaction: buildMintTransactionMock, quantity: 1, }); let rerender: RenderResult['rerender']; await act(async () => { const { rerender: rerenderFn } = render( , ); rerender = rerenderFn; }); expect(buildMintTransactionMock).toHaveBeenCalledWith( expect.objectContaining({ contractAddress: '0x123', tokenId: '1', takerAddress: '0xabc', quantity: 1, }), ); (useNFTContext as Mock).mockReturnValueOnce({ contractAddress: '0x123', tokenId: '1', name: 'name', isEligibleToMint: true, buildMintTransaction: buildMintTransactionMock, quantity: 2, }); await act(async () => { rerender( , ); }); expect(buildMintTransactionMock).toHaveBeenCalledWith( expect.objectContaining({ contractAddress: '0x123', tokenId: '1', takerAddress: '0xabc', quantity: 2, }), ); }); it('calls updateLifecycleStatus on buildMintTransaction error', async () => { const buildMintTransactionMock = vi.fn().mockRejectedValue('error'); (useNFTContext as Mock).mockReturnValueOnce({ contractAddress: '0x123', tokenId: '1', name: 'name', isEligibleToMint: true, buildMintTransaction: buildMintTransactionMock, quantity: 1, }); await act(async () => { render( , ); }); expect(mockUpdateLifecycleStatus).toHaveBeenCalledWith({ statusName: 'error', statusData: expect.objectContaining({ message: 'error' }), }); }); });