import { setOnchainKitConfig } from '@/core/OnchainKitConfig';
import { openPopup } from '@/internal/utils/openPopup';
import '@testing-library/jest-dom';
import { act } from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import { useAccount } from 'wagmi';
import { useFundCardFundingUrl } from '../hooks/useFundCardFundingUrl';
import { optionsResponseDataMock, quoteResponseDataMock } from '../mocks';
import type { PresetAmountInputs } from '../types';
import { fetchOnrampOptions } from '../utils/fetchOnrampOptions';
import { fetchOnrampQuote } from '../utils/fetchOnrampQuote';
import { getFundingPopupSize } from '../utils/getFundingPopupSize';
import { FundCard } from './FundCard';
import { FundCardProvider, useFundContext } from './FundCardProvider';
const mockUpdateInputWidth = vi.fn();
vi.mock('../../internal/hooks/useInputResize', () => ({
useInputResize: () => mockUpdateInputWidth,
}));
vi.mock('../hooks/useGetFundingUrl', () => ({
useGetFundingUrl: vi.fn(),
}));
vi.mock('../hooks/useFundCardFundingUrl', () => ({
useFundCardFundingUrl: vi.fn(),
}));
vi.mock('@/core/hooks/useOnchainKit', () => ({
useOnchainKit: () => ({
apiKey: 'mock-api-key',
sessionId: 'mock-session-id',
config: {},
}),
}));
vi.mock('@/internal/utils/openPopup', () => ({
openPopup: vi.fn(),
}));
vi.mock('../utils/getFundingPopupSize', () => ({
getFundingPopupSize: vi.fn(),
}));
vi.mock('../hooks/useFundCardSetupOnrampEventListeners');
vi.mock('../utils/fetchOnrampQuote');
vi.mock('../utils/fetchOnrampOptions');
vi.mock('wagmi', () => ({
useAccount: vi.fn(),
useConnect: vi.fn(),
}));
vi.mock('../../wallet/components/ConnectWallet', () => ({
ConnectWallet: ({ className }: { className?: string }) => (
Connect Wallet
),
}));
// Test component to access context values
const TestComponent = () => {
const {
fundAmountFiat,
fundAmountCrypto,
exchangeRate,
exchangeRateLoading,
setFundAmountFiat,
setSelectedInputType,
} = useFundContext();
return (
{fundAmountFiat}
{fundAmountCrypto}
{exchangeRate}
{exchangeRateLoading ? 'loading' : 'not-loading'}
);
};
const renderComponent = async (presetAmountInputs?: PresetAmountInputs) => {
await act(async () => {
render(
,
);
});
};
describe('FundCard', () => {
beforeEach(() => {
vi.clearAllMocks();
setOnchainKitConfig({ apiKey: 'mock-api-key' });
mockUpdateInputWidth.mockClear();
(getFundingPopupSize as Mock).mockImplementation(() => ({
height: 200,
width: 100,
}));
(useFundCardFundingUrl as Mock).mockReturnValue('mock-funding-url');
(fetchOnrampQuote as Mock).mockResolvedValue(quoteResponseDataMock);
(fetchOnrampOptions as Mock).mockResolvedValue(optionsResponseDataMock);
(useAccount as Mock).mockReturnValue({
address: '0x123',
});
});
it('renders without crashing', async () => {
await renderComponent();
expect(screen.getByTestId('ockFundCardHeader')).toBeInTheDocument();
expect(screen.getByTestId('ockFundButtonTextContent')).toBeInTheDocument();
});
it('displays the correct header text', async () => {
await renderComponent();
expect(screen.getByTestId('ockFundCardHeader')).toHaveTextContent(
'Buy BTC',
);
});
it('displays the correct button text', async () => {
await renderComponent();
expect(screen.getByTestId('ockFundButtonTextContent')).toHaveTextContent(
'Buy',
);
});
it('handles input changes for fiat amount', async () => {
await renderComponent();
const input = screen.getByTestId('ockTextInput_Input') as HTMLInputElement;
await act(async () => {
fireEvent.change(input, { target: { value: '100' } });
});
expect(input.value).toBe('100');
});
it('switches input type from fiat to crypto', async () => {
await renderComponent();
await waitFor(() => {
const switchButton = screen.getByTestId('ockAmountTypeSwitch');
fireEvent.click(switchButton);
});
expect(screen.getByTestId('ockCurrencySpan')).toHaveTextContent('BTC');
});
it('disables the submit button when fund amount is zero and type is fiat', async () => {
await renderComponent();
const setFiatAmountButton = screen.getByTestId('set-fiat-amount');
fireEvent.click(setFiatAmountButton);
const button = screen.getByTestId('ockFundButton');
expect(button).toBeDisabled();
});
it('disables the submit button when fund amount is zero and input type is crypto', async () => {
await renderComponent();
const setCryptoInputTypeButton = screen.getByTestId(
'set-crypto-input-type',
);
fireEvent.click(setCryptoInputTypeButton);
const button = screen.getByTestId('ockFundButton');
expect(button).toBeDisabled();
});
it('enables the submit button when fund amount is greater than zero and type is fiat', async () => {
await renderComponent();
const setFiatAmountButton = screen.getByTestId('set-fiat-amount');
fireEvent.click(setFiatAmountButton);
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '1000' } });
const button = screen.getByTestId('ockFundButton');
expect(button).not.toBeDisabled();
});
});
it('enables the submit button when fund amount is greater than zero and type is crypto', async () => {
await renderComponent();
const setCryptoInputTypeButton = screen.getByTestId(
'set-crypto-input-type',
);
fireEvent.click(setCryptoInputTypeButton);
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '1000' } });
const button = screen.getByTestId('ockFundButton');
expect(button).not.toBeDisabled();
});
});
it('shows loading state when submitting', async () => {
await renderComponent();
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '1000' } });
const button = screen.getByTestId('ockFundButton');
expect(screen.queryByTestId('ockSpinner')).not.toBeInTheDocument();
act(() => {
fireEvent.click(button);
});
expect(screen.getByTestId('ockSpinner')).toBeInTheDocument();
});
});
it('sets submit button state to default on popup close', async () => {
(openPopup as Mock).mockImplementation(() => ({ closed: true }));
await renderComponent();
const button = screen.getByTestId('ockFundButton');
const submitButton = screen.getByTestId('ockFundButton');
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
// Simulate entering a valid amount
const input = screen.getByTestId(
'ockTextInput_Input',
) as HTMLInputElement;
fireEvent.change(input, { target: { value: '100' } });
fireEvent.click(button);
// Assert that the submit button state is set to 'default'
expect(submitButton).not.toBeDisabled();
});
});
it('renders custom children instead of default children', async () => {
await act(async () => {
render(
Custom Content
,
);
});
expect(screen.getByTestId('custom-child')).toBeInTheDocument();
expect(screen.queryByTestId('ockFundCardHeader')).not.toBeInTheDocument();
});
it('handles preset amount input click correctly', async () => {
const presetAmountInputs: PresetAmountInputs = ['12345', '20', '30'];
await renderComponent(presetAmountInputs);
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
// Click the preset amount input
const presetAmountInput = screen.getByText('$12,345');
// Verify the input value was updated
expect(presetAmountInput).toBeInTheDocument();
});
});
it('passes sessionToken to FundCardProvider', async () => {
const sessionToken = 'test-session-token';
await act(async () => {
render(
,
);
});
// Verify the component renders correctly with sessionToken
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
});
});
it('handles sessionToken correctly in FundCardProvider context', async () => {
const sessionToken = 'test-session-token';
const TestSessionComponent = () => {
const context = useFundContext();
return {context.sessionToken}
;
};
await act(async () => {
render(
,
);
});
expect(screen.getByTestId('session-token').textContent).toBe(sessionToken);
});
});