import { QueryClient } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; import { http, createConfig } from 'wagmi'; import type { CreateConnectorFn } from 'wagmi'; import { DefaultOnchainKitProviders } from './DefaultOnchainKitProviders'; import { useProviderDependencies } from './internal/hooks/useProviderDependencies'; import { useOnchainKit } from './useOnchainKit'; import type { CreateWagmiConfigParams } from './core/types'; // Mock the coinbase wallet connector const mockCoinbaseWallet = vi.fn(); // Mock the base account connector const mockBaseAccount = vi.fn(); // Mock the farcaster frame connector const mockFarcasterMiniApp = vi.fn(); // Mock for the createWagmiConfig const mockCreateWagmiConfig = vi.fn(); vi.mock('./core/createWagmiConfig', () => ({ createWagmiConfig: (params: CreateWagmiConfigParams) => { mockCreateWagmiConfig(params); return { mock: 'config', params }; }, })); const queryClient = new QueryClient(); const wagmiConfig = createConfig({ chains: [ { id: 1, name: 'Mock Chain', nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: ['http://localhost'] } }, }, ], connectors: [], transports: { [1]: http() }, }); vi.mock('wagmi', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, WagmiProvider: vi.fn(({ children, config }) => (
{children}
)), }; }); vi.mock('@tanstack/react-query', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, QueryClientProvider: vi.fn(({ children }) => (
{children}
)), }; }); vi.mock('./internal/hooks/useProviderDependencies', () => ({ useProviderDependencies: vi.fn(() => ({ providedWagmiConfig: vi.fn(), providedQueryClient: vi.fn(), })), })); vi.mock('./useOnchainKit', () => ({ useOnchainKit: vi.fn(() => ({ apiKey: 'mock-api-key', config: { appearance: { name: 'Mock App', logo: 'https://example.com/logo.png', }, wallet: { preference: 'all', }, }, })), })); // Mock MiniKitContext const mockMiniKitContext = vi.fn(); vi.mock('@/minikit/MiniKitProvider', () => ({ MiniKitContext: { _currentValue: null }, })); // Mock useContext to control MiniKit context vi.mock('react', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useContext: vi.fn((context) => { // Check if this is the MiniKitContext by checking its shape if ( context && typeof context === 'object' && '_currentValue' in context ) { return mockMiniKitContext(); } return actual.useContext(context); }), }; }); // Create mock connector function that satisfies the CreateConnectorFn interface const createMockConnector = (id: string): CreateConnectorFn => { return () => ({ id, name: `Mock ${id}`, type: 'mock', icon: undefined, rdns: undefined, supportsSimulation: false, connect: async () => ({ accounts: [], chainId: 1, }), disconnect: async () => {}, getAccounts: async () => [], getChainId: async () => 1, getProvider: async () => ({}), isAuthorized: async () => false, onAccountsChanged: () => {}, onChainChanged: () => {}, onDisconnect: () => {}, }); }; // Mock wagmi/connectors vi.mock('wagmi/connectors', () => { return { coinbaseWallet: (params: { preference?: string; appName?: string; appLogoUrl?: string; }) => { mockCoinbaseWallet(params); // Return a connector function that satisfies the CreateConnectorFn interface return createMockConnector('coinbaseWalletSDK'); }, baseAccount: (params: { appName?: string; appLogoUrl?: string }) => { mockBaseAccount(params); // Return a connector function that satisfies the CreateConnectorFn interface return createMockConnector('baseAccount'); }, }; }); // Mock @farcaster/miniapp-wagmi-connector vi.mock('@farcaster/miniapp-wagmi-connector', () => ({ farcasterMiniApp: () => { mockFarcasterMiniApp(); return createMockConnector('farcasterMiniApp'); }, })); describe('DefaultOnchainKitProviders', () => { beforeEach(() => { (useProviderDependencies as Mock).mockReturnValue({ providedWagmiConfig: null, providedQueryClient: null, }); mockMiniKitContext.mockReturnValue({ context: null }); mockCoinbaseWallet.mockClear(); mockBaseAccount.mockClear(); mockFarcasterMiniApp.mockClear(); vi.clearAllMocks(); }); it('should wrap children in default providers', () => { render(
Test Child
, ); expect(screen.getByText('Test Child')).toBeInTheDocument(); expect(screen.queryAllByTestId('wagmi-provider')).toHaveLength(1); expect(screen.queryAllByTestId('query-client-provider')).toHaveLength(1); }); it('should not render duplicate WagmiProvider when a wagmi provider already exists', () => { (useProviderDependencies as Mock).mockReturnValue({ providedWagmiConfig: wagmiConfig, providedQueryClient: null, }); render(
Test Child
, ); expect(screen.getByText('Test Child')).toBeInTheDocument(); expect(screen.queryAllByTestId('wagmi-provider')).toHaveLength(0); expect(screen.queryAllByTestId('query-client-provider')).toHaveLength(1); }); it('should not render duplicate QueryClientProvider when a query client already exists', () => { (useProviderDependencies as Mock).mockReturnValue({ providedWagmiConfig: null, providedQueryClient: queryClient, }); render(
Test Child
, ); expect(screen.getByText('Test Child')).toBeInTheDocument(); expect(screen.queryAllByTestId('wagmi-provider')).toHaveLength(1); expect(screen.queryAllByTestId('query-client-provider')).toHaveLength(0); }); it('should not render any default providers when both providers already exist', () => { (useProviderDependencies as Mock).mockReturnValue({ providedWagmiConfig: wagmiConfig, providedQueryClient: queryClient, }); render(
Test Child
, ); expect(screen.getByText('Test Child')).toBeInTheDocument(); expect(screen.queryAllByTestId('wagmi-provider')).toHaveLength(0); expect(screen.queryAllByTestId('query-client-provider')).toHaveLength(0); }); it('should use baseAccount connector when MiniKit context is not available', () => { mockMiniKitContext.mockReturnValue({ context: null }); render(
Test Child
, ); // Verify baseAccount was called expect(mockBaseAccount).toHaveBeenCalledWith( expect.objectContaining({ appName: 'Mock App', appLogoUrl: 'https://example.com/logo.png', }), ); // Verify farcasterFrame was not called expect(mockFarcasterMiniApp).not.toHaveBeenCalled(); // Verify coinbaseWallet was not called expect(mockCoinbaseWallet).not.toHaveBeenCalled(); }); it('should use farcasterFrame connector when MiniKit context is available', () => { mockMiniKitContext.mockReturnValue({ context: { isFrame: true } }); render(
Test Child
, ); // Verify farcasterFrame was called expect(mockFarcasterMiniApp).toHaveBeenCalled(); // Verify baseAccount was not called expect(mockBaseAccount).not.toHaveBeenCalled(); // Verify coinbaseWallet was not called expect(mockCoinbaseWallet).not.toHaveBeenCalled(); }); it('should pass app details to the baseAccount connector', () => { // Mock useOnchainKit to return app details (useOnchainKit as Mock).mockReturnValue({ apiKey: 'mock-api-key', config: { appearance: { name: 'Mock App', logo: 'https://example.com/logo.png', }, wallet: { preference: 'smartWalletOnly', }, }, }); render(
Test Child
, ); // Verify baseAccount was called with the app details expect(mockBaseAccount).toHaveBeenCalledWith( expect.objectContaining({ appName: 'Mock App', appLogoUrl: 'https://example.com/logo.png', }), ); }); it('should pass app details when no preference is specified', () => { // Mock useOnchainKit to return config without preference (useOnchainKit as Mock).mockReturnValue({ apiKey: 'mock-api-key', config: { appearance: { name: 'Mock App', logo: 'https://example.com/logo.png', }, wallet: {}, }, }); render(
Test Child
, ); // Verify baseAccount was called with app details expect(mockBaseAccount).toHaveBeenCalledWith( expect.objectContaining({ appName: 'Mock App', appLogoUrl: 'https://example.com/logo.png', }), ); }); it('should pass app details with eoaOnly preference', () => { // Mock useOnchainKit to return eoaOnly preference (useOnchainKit as Mock).mockReturnValue({ apiKey: 'mock-api-key', config: { appearance: { name: 'Mock App', logo: 'https://example.com/logo.png', }, wallet: { preference: 'eoaOnly', }, }, }); render(
Test Child
, ); // Verify baseAccount was called with app details expect(mockBaseAccount).toHaveBeenCalledWith( expect.objectContaining({ appName: 'Mock App', appLogoUrl: 'https://example.com/logo.png', }), ); }); it('should handle undefined appearance values', () => { // Mock useOnchainKit to return undefined appearance values (useOnchainKit as Mock).mockReturnValue({ apiKey: null, config: { wallet: { preference: 'all', }, }, }); render(
Test Child
, ); // Verify baseAccount was called with undefined app name and logo expect(mockBaseAccount).toHaveBeenCalledWith( expect.objectContaining({ appName: undefined, appLogoUrl: undefined, }), ); }); it('should handle missing config entirely', () => { // Mock useOnchainKit to return no config (useOnchainKit as Mock).mockReturnValue({ apiKey: 'test-api-key', }); render(
Test Child
, ); expect(screen.getByText('Test Child')).toBeInTheDocument(); expect(mockBaseAccount).toHaveBeenCalledWith( expect.objectContaining({ appName: undefined, appLogoUrl: undefined, }), ); }); });