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('');
(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('');
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();
});
});