import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { type Mock, afterEach, beforeEach, describe, expect, it, vi, } from 'vitest'; import { useAnalytics } from '../../core/analytics/hooks/useAnalytics'; import { CheckoutEvent } from '../../core/analytics/types'; import { useIcon } from '../../internal/hooks/useIcon'; import { CHECKOUT_LIFECYCLE_STATUS } from '../constants'; import { CheckoutButton } from './CheckoutButton'; import { useCheckoutContext } from './CheckoutProvider'; vi.mock('./CheckoutProvider', () => ({ useCheckoutContext: vi.fn(), })); vi.mock('../../internal/hooks/useIcon', () => ({ useIcon: vi.fn(), })); vi.mock('../../core/analytics/hooks/useAnalytics', () => ({ useAnalytics: vi.fn(), })); describe('CheckoutButton', () => { let mockOnSubmit: Mock; let mockSendAnalytics: Mock; beforeEach(() => { mockOnSubmit = vi.fn(); mockSendAnalytics = vi.fn(); (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: 'ready' }, onSubmit: mockOnSubmit, }); (useIcon as Mock).mockReturnValue('Icon'); (useAnalytics as Mock).mockReturnValue({ sendAnalytics: mockSendAnalytics, }); }); afterEach(() => { vi.resetAllMocks(); }); it('renders with default text', () => { render(); expect(screen.getByText('Pay')).toBeInTheDocument(); }); it('renders with custom text', () => { render(); expect(screen.getByText('Custom Pay')).toBeInTheDocument(); }); it('renders with Coinbase branding when coinbaseBranded is true', () => { (useIcon as Mock).mockReturnValue('Coinbase Pay Icon'); render(); expect(screen.getByText('Pay')).toBeInTheDocument(); }); it('disables button when disabled prop is true', () => { render(); expect(screen.getByRole('button')).toBeDisabled(); }); it('disables button when lifecycle status is PENDING', () => { (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.PENDING }, onSubmit: mockOnSubmit, }); render(); expect(screen.getByRole('button')).toBeDisabled(); }); it('disables button when lifecycle status is FETCHING_DATA', () => { (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.FETCHING_DATA }, onSubmit: mockOnSubmit, }); render(); expect(screen.getByRole('button')).toBeDisabled(); }); it('shows spinner when lifecycle status is PENDING', () => { (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.PENDING }, onSubmit: mockOnSubmit, }); render(); expect(screen.getByRole('button')).not.toHaveTextContent('Pay'); // Note: We can't easily test for the Spinner component directly, // but we can verify the button text is not displayed }); it('changes button text to "View payment details" when transaction is successful', () => { (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.SUCCESS }, onSubmit: mockOnSubmit, }); render(); expect(screen.getByText('View payment details')).toBeInTheDocument(); }); it('calls onSubmit when button is clicked', () => { render(); fireEvent.click(screen.getByRole('button')); expect(mockOnSubmit).toHaveBeenCalledTimes(1); }); it('tracks checkout success analytics when transaction is successful', async () => { const transactionHash = '0xabc123'; const chargeId = 'charge-123'; (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.SUCCESS, statusData: { transactionReceipts: [{ transactionHash }], chargeId, }, }, onSubmit: mockOnSubmit, }); render(); await waitFor(() => { expect(mockSendAnalytics).toHaveBeenCalledWith( CheckoutEvent.CheckoutSuccess, { chargeHandlerId: chargeId, transactionHash, }, ); }); }); it('does not track analytics when transaction hash or charge ID is missing', () => { (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.SUCCESS, statusData: { transactionReceipts: [{ transactionHash: null }], chargeId: 'charge-123', }, }, onSubmit: mockOnSubmit, }); render(); expect(mockSendAnalytics).not.toHaveBeenCalled(); }); it('does not track analytics when status is not SUCCESS', () => { (useCheckoutContext as Mock).mockReturnValue({ lifecycleStatus: { statusName: CHECKOUT_LIFECYCLE_STATUS.READY }, onSubmit: mockOnSubmit, }); render(); expect(mockSendAnalytics).not.toHaveBeenCalled(); }); });