import { renderHook, waitFor } from '@testing-library/react'; import { base, baseSepolia, mainnet, optimism } from 'viem/chains'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getNewReactQueryTestProvider } from './getNewReactQueryTestProvider'; import { useAvatars } from './useAvatars'; import { getAvatars } from '@/identity/utils/getAvatars'; vi.mock('@/identity/utils/getAvatars', () => ({ getAvatars: vi.fn(), })); const mockUseQuery = vi.fn(); vi.mock('@tanstack/react-query', async () => { const actual = await vi.importActual('@tanstack/react-query'); // eslint-disable-next-line @typescript-eslint/no-unused-vars type UseQueryType = (options: { queryKey: unknown[]; queryFn: () => Promise; [key: string]: unknown; }) => unknown; return { ...actual, useQuery: (options: { queryKey: unknown[]; queryFn: () => Promise; [key: string]: unknown; }) => { mockUseQuery(options); return (actual.useQuery as UseQueryType)(options); }, }; }); describe('useAvatars', () => { beforeEach(() => { vi.clearAllMocks(); mockUseQuery.mockClear(); }); it('returns the correct ENS avatars and loading state', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; const testEnsAvatars = ['avatarUrl1', 'avatarUrl2']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testEnsAvatars); expect(result.current.isLoading).toBe(false); }); expect(vi.mocked(getAvatars)).toHaveBeenCalledWith({ ensNames: testEnsNames, chain: mainnet, }); }); it('returns the loading state true while still fetching ENS avatars', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; // Don't resolve the promise immediately vi.mocked(getAvatars).mockImplementation(() => new Promise(() => {})); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }), { wrapper: getNewReactQueryTestProvider(), }, ); expect(result.current.data).toBe(undefined); expect(result.current.isLoading).toBe(true); }); it('returns correct base mainnet avatars', async () => { const testEnsNames = ['shrek.base.eth', 'donkey.base.eth']; const testEnsAvatars = ['shrekface', 'donkeyface']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames, chain: base as unknown as typeof mainnet, }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testEnsAvatars); expect(result.current.isLoading).toBe(false); }); expect(vi.mocked(getAvatars)).toHaveBeenCalledWith({ ensNames: testEnsNames, chain: base, }); }); it('returns correct base sepolia avatars', async () => { const testEnsNames = ['shrek.basetest.eth', 'donkey.basetest.eth']; const testEnsAvatars = ['shrektestface', 'donkeytestface']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames, chain: baseSepolia as unknown as typeof mainnet, }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testEnsAvatars); expect(result.current.isLoading).toBe(false); }); expect(vi.mocked(getAvatars)).toHaveBeenCalledWith({ ensNames: testEnsNames, chain: baseSepolia, }); }); it('returns error for unsupported chain', async () => { const testEnsNames = ['shrek.basetest.eth', 'donkey.basetest.eth']; vi.mocked(getAvatars).mockRejectedValue( 'ChainId not supported, avatar resolution is only supported on Ethereum and Base.', ); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames, chain: optimism as unknown as typeof mainnet, }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toBe(undefined); expect(result.current.isLoading).toBe(false); expect(result.current.isError).toBe(true); expect(result.current.error).toBe( 'ChainId not supported, avatar resolution is only supported on Ethereum and Base.', ); }); }); it('respects the enabled option in queryOptions', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; const testEnsAvatars = ['avatarUrl1', 'avatarUrl2']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }, { enabled: false }), { wrapper: getNewReactQueryTestProvider(), }, ); expect(result.current.isLoading).toBe(false); expect(result.current.isFetched).toBe(false); expect(vi.mocked(getAvatars)).not.toHaveBeenCalled(); }); it('uses the default query options when no queryOptions are provided', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; const testEnsAvatars = ['avatarUrl1', 'avatarUrl2']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); renderHook(() => useAvatars({ ensNames: testEnsNames }), { wrapper: getNewReactQueryTestProvider(), }); await waitFor(() => { expect(vi.mocked(getAvatars)).toHaveBeenCalled(); }); }); it('merges custom queryOptions with default options', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; const testEnsAvatars = ['avatarUrl1', 'avatarUrl2']; const customStaleTime = 60000; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }, { staleTime: customStaleTime }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testEnsAvatars); }); expect(vi.mocked(getAvatars)).toHaveBeenCalled(); }); it('handles empty ensNames array', async () => { const { result } = renderHook(() => useAvatars({ ensNames: [] }), { wrapper: getNewReactQueryTestProvider(), }); expect(result.current.isLoading).toBe(false); expect(result.current.isFetched).toBe(false); expect(vi.mocked(getAvatars)).not.toHaveBeenCalled(); }); it('creates a stable query key based on ensNames', async () => { const testEnsNames1 = ['test1.ens', 'test2.ens']; const testEnsNames2 = ['test1.ens', 'test3.ens']; const testEnsAvatars = ['avatarUrl1', 'avatarUrl2']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { rerender } = renderHook( ({ ensNames }) => useAvatars({ ensNames }), { wrapper: getNewReactQueryTestProvider(), initialProps: { ensNames: testEnsNames1 }, }, ); await waitFor(() => { expect(vi.mocked(getAvatars)).toHaveBeenCalled(); }); vi.mocked(getAvatars).mockClear(); rerender({ ensNames: testEnsNames2 }); await waitFor(() => { expect(vi.mocked(getAvatars)).toHaveBeenCalled(); }); }); it('handles partial failures in avatar resolution', async () => { const testEnsNames = ['success1.eth', 'fail.eth', 'success2.eth']; const partialResults = ['avatar1-url', null, 'avatar2-url']; vi.mocked(getAvatars).mockResolvedValue(partialResults); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(partialResults); expect(result.current.isLoading).toBe(false); expect(result.current.isError).toBe(false); }); expect(vi.mocked(getAvatars)).toHaveBeenCalledWith({ ensNames: testEnsNames, chain: mainnet, }); }); it('disables the query when ensNames array is empty', async () => { vi.mocked(getAvatars).mockImplementation(() => { throw new Error('This should not be called'); }); const { result } = renderHook(() => useAvatars({ ensNames: [] }), { wrapper: getNewReactQueryTestProvider(), }); await new Promise((resolve) => setTimeout(resolve, 100)); expect(vi.mocked(getAvatars)).not.toHaveBeenCalled(); expect(result.current.isLoading).toBe(false); expect(result.current.fetchStatus).toBe('idle'); }); it('enables the query when enabled=true is explicitly set and ensNames is valid', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; const testEnsAvatars = ['avatarUrl1', 'avatarUrl2']; vi.mocked(getAvatars).mockResolvedValue(testEnsAvatars); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }, { enabled: true }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testEnsAvatars); }); expect(vi.mocked(getAvatars)).toHaveBeenCalled(); }); it('respects enabled=false even with valid ensNames', async () => { const testEnsNames = ['test1.ens', 'test2.ens']; vi.mocked(getAvatars).mockImplementation(() => { throw new Error('This should not be called'); }); const { result } = renderHook( () => useAvatars({ ensNames: testEnsNames }, { enabled: false }), { wrapper: getNewReactQueryTestProvider(), }, ); await new Promise((resolve) => setTimeout(resolve, 100)); expect(vi.mocked(getAvatars)).not.toHaveBeenCalled(); expect(result.current.isLoading).toBe(false); expect(result.current.fetchStatus).toBe('idle'); }); });