import { setOnchainKitConfig } from '@/core/OnchainKitConfig';
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { act } from 'react';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import { useAnalytics } from '../../core/analytics/hooks/useAnalytics';
import { quoteResponseDataMock } from '../mocks';
import type { FundCardProviderProps } from '../types';
import { FundCardAmountInput } from './FundCardAmountInput';
import { FundCardProvider, useFundContext } from './FundCardProvider';
import { truncateDecimalPlaces } from '@/internal/utils/truncateDecimalPlaces';
class ResizeObserverMock {
observe() {}
unobserve() {}
disconnect() {}
}
vi.mock('../../core/analytics/hooks/useAnalytics', () => ({
useAnalytics: vi.fn(() => ({
sendAnalytics: vi.fn(),
})),
}));
vi.mock('../utils/fetchOnrampQuote', () => ({
fetchOnrampQuote: vi.fn().mockResolvedValue(quoteResponseDataMock),
}));
describe('FundCardAmountInput', () => {
beforeEach(() => {
global.ResizeObserver = ResizeObserverMock;
setOnchainKitConfig({ apiKey: '123456789' });
vi.clearAllMocks();
});
const TestComponent = () => {
const {
fundAmountFiat,
fundAmountCrypto,
exchangeRate,
exchangeRateLoading,
} = useFundContext();
return (
{fundAmountFiat}
{fundAmountCrypto}
{exchangeRate}
{exchangeRateLoading ? 'loading' : 'not-loading'}
);
};
const renderWithProvider = async (
initialProps: Partial = {},
) => {
return await act(async () => {
return render(
,
);
});
};
it('renders correctly with fiat input type', async () => {
await renderWithProvider();
expect(screen.getByTestId('ockTextInput_Input')).toBeInTheDocument();
expect(screen.getByTestId('ockCurrencySpan')).toHaveTextContent('USD');
});
it('renders correctly with crypto input type', async () => {
await renderWithProvider({ inputType: 'crypto' });
expect(screen.getByTestId('ockCurrencySpan')).toHaveTextContent('ETH');
});
it('handles fiat input change', async () => {
await renderWithProvider({ inputType: 'fiat' });
const input = screen.getByTestId('ockTextInput_Input');
const testFiatValue = 10;
await act(async () => {
fireEvent.change(input, { target: { value: testFiatValue } });
});
await waitFor(() => {
const valueFiat = screen.getByTestId('test-value-fiat');
const valueCrypto = screen.getByTestId('test-value-crypto');
expect(valueFiat.textContent).toBe(String(testFiatValue));
expect(valueCrypto.textContent).toBe(
truncateDecimalPlaces(
String(
(testFiatValue *
Number(quoteResponseDataMock.purchaseAmount.value)) /
Number(quoteResponseDataMock.paymentSubtotal.value),
),
8,
),
);
});
});
it('handles crypto input change', async () => {
await renderWithProvider({ inputType: 'crypto' });
await waitFor(() => {
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '1' } });
const valueCrypto = screen.getByTestId('test-value-crypto');
expect(valueCrypto.textContent).toBe('1');
});
});
it('does not allow non-numeric input', async () => {
await renderWithProvider();
await waitFor(() => {
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: 'ABC' } });
const valueFiat = screen.getByTestId('test-value-fiat');
expect(valueFiat.textContent).toBe('');
});
});
it('applies custom className', async () => {
await act(async () => {
render(
,
);
});
const container = screen.getByTestId('ockAmountInputContainer');
expect(container).toHaveClass('custom-class');
});
it('handles truncation of crypto decimals', async () => {
await renderWithProvider({ inputType: 'crypto' });
await waitFor(() => {
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '0.123456789' } });
const valueCrypto = screen.getByTestId('test-value-crypto');
expect(valueCrypto.textContent).toBe('0.12345678');
});
});
it('handles truncation of fiat decimals', async () => {
await act(async () => {
renderWithProvider({ inputType: 'fiat' });
});
const input = screen.getByTestId('ockTextInput_Input');
await act(async () => {
fireEvent.change(input, { target: { value: '1000.123456789' } });
});
await waitFor(() => {
const valueFiat = screen.getByTestId('test-value-fiat');
expect(valueFiat.textContent).toBe('1000.12');
});
});
it('handles zero and empty values in crypto mode', async () => {
act(() => {
render(
,
);
});
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
const input = screen.getByTestId('ockTextInput_Input');
const valueFiat = screen.getByTestId('test-value-fiat');
const valueCrypto = screen.getByTestId('test-value-crypto');
fireEvent.change(input, { target: { value: '0' } });
expect(valueCrypto.textContent).toBe('0');
expect(valueFiat.textContent).toBe('');
fireEvent.change(input, { target: { value: '' } });
expect(valueCrypto.textContent).toBe('');
expect(valueFiat.textContent).toBe('');
});
});
it('handles zero and empty values in fiat mode', async () => {
act(() => {
render(
,
);
});
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '0' } });
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
const valueFiat = screen.getByTestId('test-value-fiat');
const valueCrypto = screen.getByTestId('test-value-crypto');
expect(valueCrypto.textContent).toBe('');
expect(valueFiat.textContent).toBe('0');
});
});
it('handles non zero values in fiat mode', async () => {
act(() => {
render(
,
);
});
await waitFor(() => {
expect(screen.getByTestId('loading-state').textContent).toBe(
'not-loading',
);
const input = screen.getByTestId('ockTextInput_Input');
fireEvent.change(input, { target: { value: '400' } });
const valueFiat = screen.getByTestId('test-value-fiat');
const valueCrypto = screen.getByTestId('test-value-crypto');
expect(valueCrypto.textContent).toBe('0.33333333');
expect(valueFiat.textContent).toBe('400');
});
});
it('sets empty string for crypto when calculated value is zero', async () => {
global.fetch = vi.fn(() =>
Promise.resolve({
json: () =>
Promise.resolve({
payment_total: { value: '100.00', currency: 'USD' },
payment_subtotal: { value: '0', currency: 'USD' },
purchase_amount: { value: '0', currency: 'ETH' },
coinbase_fee: { value: '0', currency: 'USD' },
network_fee: { value: '0', currency: 'USD' },
quote_id: 'quote-id-123',
}),
}),
) as Mock;
act(() => {
render(
,
);
});
await waitFor(() => {
const input = screen.getByTestId('ockTextInput_Input');
const valueFiat = screen.getByTestId('test-value-fiat');
const valueCrypto = screen.getByTestId('test-value-crypto');
fireEvent.change(input, { target: { value: '1' } });
expect(valueFiat.textContent).toBe('1');
expect(valueCrypto.textContent).toBe('');
const exchangeRate = screen.getByTestId('test-value-exchange-rate');
expect(exchangeRate.textContent).toBe('0');
});
});
it('sends analytics event when fiat amount changes', async () => {
const mockSendAnalytics = vi.fn();
vi.mocked(useAnalytics).mockImplementation(() => ({
sendAnalytics: mockSendAnalytics,
}));
await renderWithProvider({ inputType: 'fiat' });
const input = screen.getByTestId('ockTextInput_Input');
await act(async () => {
fireEvent.change(input, { target: { value: '100' } });
});
expect(mockSendAnalytics).toHaveBeenCalledWith('fundAmountChanged', {
amount: 100,
currency: 'USD',
});
});
it('does not send analytics event for invalid input', async () => {
const mockSendAnalytics = vi.fn();
vi.mocked(useAnalytics).mockImplementation(() => ({
sendAnalytics: mockSendAnalytics,
}));
await renderWithProvider({ inputType: 'fiat' });
const input = screen.getByTestId('ockTextInput_Input');
await act(async () => {
fireEvent.change(input, { target: { value: 'abc' } });
});
expect(mockSendAnalytics).not.toHaveBeenCalled();
});
it('sends analytics event when amount changes to zero', async () => {
const mockSendAnalytics = vi.fn();
vi.mocked(useAnalytics).mockImplementation(() => ({
sendAnalytics: mockSendAnalytics,
}));
await renderWithProvider({ inputType: 'fiat' });
const input = screen.getByTestId('ockTextInput_Input');
await act(async () => {
fireEvent.change(input, { target: { value: '100' } });
expect(mockSendAnalytics).toHaveBeenCalledWith('fundAmountChanged', {
amount: 100,
currency: 'USD',
});
fireEvent.change(input, { target: { value: '0' } });
expect(mockSendAnalytics).toHaveBeenCalledWith('fundAmountChanged', {
amount: 0,
currency: 'USD',
});
});
});
it('sends analytics event with correct currency', async () => {
const mockSendAnalytics = vi.fn();
vi.mocked(useAnalytics).mockImplementation(() => ({
sendAnalytics: mockSendAnalytics,
}));
await renderWithProvider({
inputType: 'fiat',
currency: 'EUR',
});
const input = screen.getByTestId('ockTextInput_Input');
await act(async () => {
fireEvent.change(input, { target: { value: '50' } });
});
expect(mockSendAnalytics).toHaveBeenCalledWith('fundAmountChanged', {
amount: 50,
currency: 'EUR',
});
});
});