import { useIsMounted } from '@/internal/hooks/useIsMounted';
import { QueryClientProvider } from '@tanstack/react-query';
import { QueryClient } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react';
import { act } from 'react';
import { parseEther } from 'viem';
import { type Chain, base, baseSepolia } from 'viem/chains';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import {
http,
WagmiProvider,
createConfig,
mock,
useAccount,
useConfig,
} from 'wagmi';
import { getBalance, readContract } from 'wagmi/actions';
import { ETH_BY_CHAIN } from '../constants';
import type { Appchain, BridgeableToken } from '../types';
import { AppchainBridge } from './AppchainBridge';
import { useAppchainBridgeContext } from './AppchainBridgeProvider';
const queryClient = new QueryClient();
const mockConfig = createConfig({
chains: [baseSepolia],
connectors: [
mock({
accounts: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'],
}),
],
transports: {
[baseSepolia.id]: http(),
},
});
const mockChain = {
id: 1,
name: 'Ethereum',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
} as Chain;
const mockAppchain = {
chain: {
id: 8453,
name: 'Base',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18,
},
},
} as Appchain;
const mockToken = {
name: 'ETH',
address: '',
symbol: 'ETH',
decimals: 18,
image:
'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
chainId: base.id,
remoteToken: ETH_BY_CHAIN[base.id].address,
} as BridgeableToken;
const mockBridgeParams = {
token: {
address: '0x123',
remoteToken: '0x456',
decimals: 18,
chainId: 1,
image: '',
name: 'Mock Token',
symbol: 'MOCK',
},
amount: '1',
recipient: '0x789',
amountUSD: '100',
} as const;
const wrapper = ({ children }: { children: React.ReactNode }) => (
{children}
);
vi.mock('wagmi', async (importOriginal) => {
return {
...(await importOriginal()),
useAccount: vi.fn(),
useConfig: vi.fn(),
};
});
vi.mock('wagmi/actions', () => ({
getBalance: vi.fn(),
readContract: vi.fn(),
}));
vi.mock('@/internal/hooks/useIsMounted', () => ({
useIsMounted: vi.fn(() => true),
}));
vi.mock('./AppchainBridgeProvider', async () => ({
useAppchainBridgeContext: vi.fn(),
AppchainBridgeProvider: ({ children }: { children: React.ReactNode }) => (
<>{children}>
),
}));
describe('AppchainBridge Component', () => {
beforeEach(() => {
(useAccount as Mock).mockReturnValue({
address: '0x123',
});
(useConfig as Mock).mockReturnValue({});
(getBalance as ReturnType).mockResolvedValue({
value: parseEther('1'),
decimals: 18,
});
(readContract as ReturnType).mockResolvedValue(
parseEther('1'),
);
(useAppchainBridgeContext as Mock).mockReturnValue({
withdrawStatus: 'init',
isWithdrawModalOpen: false,
from: mockChain,
to: mockAppchain,
bridgeParams: mockBridgeParams,
bridgeableTokens: [mockToken],
});
});
it('renders custom children when provided', async () => {
const customChildren = Custom Children
;
await act(async () => {
render(
{customChildren}
,
{ wrapper },
);
});
expect(screen.getByText('Custom Children')).toBeInTheDocument();
expect(
screen.queryByTestId('ockAppchainBridge_DefaultContent'),
).not.toBeInTheDocument();
});
it('renders default content when children are not provided', async () => {
await act(async () => {
render(, {
wrapper,
});
});
expect(
screen.getByTestId('ockAppchainBridge_DefaultContent'),
).toBeInTheDocument();
});
it('renders with custom title', async () => {
await act(async () => {
render(
,
{ wrapper },
);
});
expect(screen.getByText('Custom Bridge')).toBeInTheDocument();
});
it('applies custom className', async () => {
await act(async () => {
render(
,
{ wrapper },
);
});
expect(screen.getByTestId('ockAppchainBridge_Container')).toHaveClass(
'custom-class',
);
});
it('opens withdrawal modal when isWithdrawModalOpen is true', async () => {
(useAppchainBridgeContext as Mock).mockReturnValue({
isWithdrawModalOpen: true,
});
await act(async () => {
render(, {
wrapper,
});
});
expect(screen.getByText('Confirming transaction')).toBeInTheDocument();
});
it('renders address input when isAddressModalOpen is true', async () => {
(useAppchainBridgeContext as Mock).mockReturnValue({
isAddressModalOpen: true,
});
await act(async () => {
render(, {
wrapper,
});
});
expect(screen.getByTestId('ockAppchainBridge_Address')).toBeInTheDocument();
});
it('renders success modal when isSuccessModalOpen is true', async () => {
(useAppchainBridgeContext as Mock).mockReturnValue({
isSuccessModalOpen: true,
});
await act(async () => {
render(, {
wrapper,
});
});
expect(screen.getByTestId('ockAppchainBridge_Success')).toBeInTheDocument();
});
it('renders resume transaction modal when isResumeTransactionModalOpen is true', async () => {
(useAppchainBridgeContext as Mock).mockReturnValue({
isResumeTransactionModalOpen: true,
});
await act(async () => {
render(, {
wrapper,
});
});
expect(
screen.getByTestId('ockAppchainBridge_ResumeTransaction'),
).toBeInTheDocument();
});
it('should not render when not mounted', () => {
(useIsMounted as Mock).mockReturnValueOnce(false);
const { container } = render(
,
{ wrapper },
);
expect(container).toBeEmptyDOMElement();
});
});