import { openPopup } from '@/internal/utils/openPopup';
import { pressable } from '@/styles/theme';
import '@testing-library/jest-dom';
import { useAnalytics } from '@/core/analytics/hooks/useAnalytics';
import { FundEvent } from '@/core/analytics/types';
import { fireEvent, render, screen } from '@testing-library/react';
import {
type Mock,
afterEach,
beforeEach,
describe,
expect,
it,
vi,
} from 'vitest';
import { useAccount } from 'wagmi';
import { useGetFundingUrl } from '../hooks/useGetFundingUrl';
import { quoteResponseDataMock } from '../mocks';
import { FundButtonRenderParams } from '../types';
import { getFundingPopupSize } from '../utils/getFundingPopupSize';
import { FundButton } from './FundButton';
vi.mock('@/fund/hooks/useGetFundingUrl', () => ({
useGetFundingUrl: vi.fn(),
}));
vi.mock('@/fund/utils/getFundingPopupSize', () => ({
getFundingPopupSize: vi.fn(),
}));
vi.mock('@/internal/utils/openPopup', () => ({
openPopup: vi.fn(),
}));
vi.mock('../../wallet/components/ConnectWallet', () => ({
ConnectWallet: () => (
Connect
),
}));
vi.mock('wagmi', () => ({
useAccount: vi.fn(),
useConnect: () => ({
connectors: [],
connect: vi.fn(),
pendingConnector: null,
isLoading: false,
error: null,
}),
}));
vi.mock('@/core/analytics/hooks/useAnalytics', () => ({
useAnalytics: vi.fn(),
}));
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
function customRender({ onClick, isDisabled }: FundButtonRenderParams) {
return (
);
}
global.fetch = vi.fn(() =>
Promise.resolve({
json: () => Promise.resolve(quoteResponseDataMock),
}),
) as Mock;
describe('FundButton', () => {
const mockSendAnalytics = vi.fn();
beforeEach(() => {
(useAccount as Mock).mockReturnValue({
address: '0x123',
});
(useAnalytics as Mock).mockReturnValue({
sendAnalytics: mockSendAnalytics,
});
});
afterEach(() => {
vi.clearAllMocks();
});
it('renders the fund button with the fundingUrl prop when it is defined', () => {
const fundingUrl = 'https://props.funding.url';
const { height, width } = { height: 200, width: 100 };
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
render();
const buttonElement = screen.getByRole('button');
expect(screen.getByText('Fund')).toBeInTheDocument();
fireEvent.click(buttonElement);
expect(getFundingPopupSize as Mock).toHaveBeenCalledWith('md', fundingUrl);
expect(openPopup as Mock).toHaveBeenCalledWith({
url: fundingUrl,
height,
width,
target: undefined,
});
});
it('renders the fund button with the default fundingUrl when the fundingUrl prop is undefined', () => {
const fundingUrl = 'https://default.funding.url';
const { height, width } = { height: 200, width: 100 };
(useGetFundingUrl as Mock).mockReturnValue(fundingUrl);
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
render();
expect(useGetFundingUrl).toHaveBeenCalled();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(getFundingPopupSize as Mock).toHaveBeenCalledWith('md', fundingUrl);
expect(openPopup as Mock).toHaveBeenCalledWith({
url: fundingUrl,
height,
width,
target: undefined,
});
});
it('renders calls window.open when the openIn prop is set to tab', () => {
const onClickMock = vi.fn();
const fundingUrl = 'https://props.funding.url';
const { height, width } = { height: 200, width: 100 };
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
render(
,
);
const button = screen.getByTestId('ockFundButton');
fireEvent.click(button);
expect(onClickMock).toHaveBeenCalled();
expect(openSpy).toHaveBeenCalledWith(fundingUrl, '_blank');
});
it('displays a spinner when in loading state', () => {
render();
expect(screen.getByTestId('ockSpinner')).toBeInTheDocument();
});
it('displays success text when in success state', () => {
render();
expect(screen.getByTestId('ockFundButtonTextContent')).toHaveTextContent(
'Success',
);
});
it('displays error text when in error state', () => {
render();
expect(screen.getByTestId('ockFundButtonTextContent')).toHaveTextContent(
'Something went wrong',
);
});
it('adds disabled class when the button is disabled', () => {
render();
expect(screen.getByRole('button')).toHaveClass(pressable.disabled);
});
it('calls onPopupClose when the popup window is closed', () => {
vi.useFakeTimers();
const fundingUrl = 'https://props.funding.url';
const onPopupClose = vi.fn();
const { height, width } = { height: 200, width: 100 };
const mockPopupWindow = {
closed: false,
close: vi.fn(),
};
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
(openPopup as Mock).mockReturnValue(mockPopupWindow);
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
// Simulate closing the popup
mockPopupWindow.closed = true;
vi.runOnlyPendingTimers();
expect(onPopupClose).toHaveBeenCalled();
});
it('calls onClick when the fund button is clicked', () => {
const fundingUrl = 'https://props.funding.url';
const onClick = vi.fn();
const { height, width } = { height: 200, width: 100 };
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(onClick).toHaveBeenCalled();
expect(getFundingPopupSize as Mock).toHaveBeenCalledWith('md', fundingUrl);
expect(openPopup as Mock).toHaveBeenCalledWith({
url: fundingUrl,
height,
width,
target: undefined,
});
});
it('renders custom implementation when render prop is passed', () => {
render();
expect(screen.getByText('click')).toBeInTheDocument();
});
it('shows ConnectWallet when no wallet is connected', () => {
(useAccount as Mock).mockReturnValue({
address: undefined,
});
render();
expect(
screen.queryByTestId('ockConnectWallet_Container'),
).toBeInTheDocument();
expect(screen.queryByTestId('ockFundButton')).not.toBeInTheDocument();
});
it('shows Fund button when wallet is connected', () => {
render();
expect(screen.queryByTestId('ockFundButton')).toBeInTheDocument();
expect(
screen.queryByTestId('ockConnectWallet_Container'),
).not.toBeInTheDocument();
});
it('passes sessionToken to useGetFundingUrl hook', () => {
const sessionToken = 'test-session-token';
const fundingUrl = 'https://default.funding.url';
const { height, width } = { height: 200, width: 100 };
(useGetFundingUrl as Mock).mockReturnValue(fundingUrl);
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
render();
expect(useGetFundingUrl).toHaveBeenCalledWith({
fiatCurrency: 'USD',
originComponentName: 'FundButton',
sessionToken: sessionToken,
});
});
describe('analytics', () => {
it('sends FundInitiated analytics when fund button is clicked', () => {
const fundingUrl = 'https://props.funding.url';
const { height, width } = { height: 200, width: 100 };
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
(openPopup as Mock).mockReturnValue({ closed: false });
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(mockSendAnalytics).toHaveBeenCalledWith(FundEvent.FundInitiated, {
currency: 'EUR',
});
});
it('sends FundFailure analytics when popup fails to open', () => {
const fundingUrl = 'https://props.funding.url';
const { height, width } = { height: 200, width: 100 };
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
(openPopup as Mock).mockReturnValue(null);
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(mockSendAnalytics).toHaveBeenCalledWith(FundEvent.FundFailure, {
error: 'Failed to open funding popup',
metadata: { currency: 'USD' },
});
});
it('uses USD as default fiatCurrency for analytics', () => {
const fundingUrl = 'https://props.funding.url';
const { height, width } = { height: 200, width: 100 };
(getFundingPopupSize as Mock).mockReturnValue({ height, width });
(openPopup as Mock).mockReturnValue({ closed: false });
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(mockSendAnalytics).toHaveBeenCalledWith(FundEvent.FundInitiated, {
currency: 'USD',
});
});
it('does not send analytics when button is disabled', () => {
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(mockSendAnalytics).not.toHaveBeenCalled();
});
it('does not send analytics when no funding URL is available', () => {
(useGetFundingUrl as Mock).mockReturnValue(undefined);
render();
const buttonElement = screen.getByRole('button');
fireEvent.click(buttonElement);
expect(mockSendAnalytics).not.toHaveBeenCalled();
});
it('sends analytics with empty string when address is undefined', () => {
(useAccount as Mock).mockReturnValue({
address: undefined,
});
const fundingUrl = 'https://props.funding.url';
(getFundingPopupSize as Mock).mockReturnValue({
height: 200,
width: 100,
});
(openPopup as Mock).mockReturnValue({ closed: false });
render();
const { sendAnalytics } = useAnalytics();
sendAnalytics(FundEvent.FundInitiated, {
currency: 'USD',
});
expect(mockSendAnalytics).toHaveBeenCalledWith(FundEvent.FundInitiated, {
currency: 'USD',
});
});
});
});