import { renderHook, waitFor } from '@testing-library/react'; import type { Address } from 'viem'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getAddresses } from '../utils/getAddresses'; import { getNewReactQueryTestProvider } from './getNewReactQueryTestProvider'; import { useAddresses } from './useAddresses'; vi.mock('../utils/getAddresses'); 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('useAddresses', () => { const testNames = ['test.eth', 'test2.eth', 'shrek.base.eth']; const testAddresses = [ '0x1234567890123456789012345678901234567890', '0x2345678901234567890123456789012345678901', '0x3456789012345678901234567890123456789012', ] as Address[]; beforeEach(() => { vi.clearAllMocks(); vi.resetAllMocks(); mockUseQuery.mockClear(); }); it('returns the correct addresses and loading state', async () => { vi.mocked(getAddresses).mockResolvedValue(testAddresses); const { result } = renderHook(() => useAddresses({ names: testNames }), { wrapper: getNewReactQueryTestProvider(), }); expect(result.current.isPending).toBe(true); expect(result.current.data).toBe(undefined); await waitFor(() => { expect(result.current.data).toEqual(testAddresses); expect(result.current.isPending).toBe(false); }); expect(getAddresses).toHaveBeenCalledWith({ names: testNames, }); }); it('returns the correct addresses for different name inputs', async () => { vi.mocked(getAddresses).mockResolvedValue(testAddresses); const { result } = renderHook( () => useAddresses({ names: testNames, }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testAddresses); expect(result.current.isPending).toBe(false); }); expect(getAddresses).toHaveBeenCalledWith({ names: testNames, }); }); it('returns error when name resolution fails', async () => { const errorMessage = 'Error resolving names'; vi.mocked(getAddresses).mockRejectedValue(errorMessage); const { result } = renderHook( () => useAddresses({ names: testNames, }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toBe(undefined); expect(result.current.isPending).toBe(false); expect(result.current.isError).toBe(true); expect(result.current.error).toBe(errorMessage); }); }); it('is disabled when names array is empty', async () => { vi.mocked(getAddresses).mockImplementation(() => { throw new Error('This should not be called'); }); const { result } = renderHook(() => useAddresses({ names: [] }), { wrapper: getNewReactQueryTestProvider(), }); await new Promise((resolve) => setTimeout(resolve, 100)); expect(getAddresses).not.toHaveBeenCalled(); expect(result.current.isError).toBe(false); }); it('uses the default query options when no queryOptions are provided', async () => { vi.mocked(getAddresses).mockResolvedValue(testAddresses); renderHook(() => useAddresses({ names: testNames }), { wrapper: getNewReactQueryTestProvider(), }); await waitFor(() => { expect(getAddresses).toHaveBeenCalled(); }); const options = mockUseQuery.mock.calls[0][0]; expect(options).toHaveProperty('queryKey'); expect(options.queryKey).toEqual(['useAddresses', testNames.join(',')]); }); it('merges custom queryOptions with default options', async () => { const gcTime = 120000; vi.mocked(getAddresses).mockResolvedValue(testAddresses); renderHook(() => useAddresses({ names: testNames }, { gcTime }), { wrapper: getNewReactQueryTestProvider(), }); expect(mockUseQuery).toHaveBeenCalled(); const options = mockUseQuery.mock.calls[0][0]; expect(options).toHaveProperty('gcTime', gcTime); }); it('creates a stable query key based on names', async () => { vi.mocked(getAddresses).mockResolvedValue(testAddresses); const { result, rerender } = renderHook( (props: { names: string[] }) => useAddresses({ names: props.names }), { wrapper: getNewReactQueryTestProvider(), initialProps: { names: testNames }, }, ); await waitFor(() => { expect(result.current.data).toEqual(testAddresses); }); vi.clearAllMocks(); rerender({ names: [...testNames] }); expect(getAddresses).not.toHaveBeenCalled(); }); it('handles partial failures in address resolution', async () => { const partialResults = [testAddresses[0], null, testAddresses[2]]; vi.mocked(getAddresses).mockResolvedValue(partialResults); const { result } = renderHook(() => useAddresses({ names: testNames }), { wrapper: getNewReactQueryTestProvider(), }); await waitFor(() => { expect(result.current.data).toEqual(partialResults); expect(result.current.isPending).toBe(false); expect(result.current.isError).toBe(false); }); expect(getAddresses).toHaveBeenCalledWith({ names: testNames, }); }); it('respects enabled=false in queryOptions even with valid names', async () => { vi.mocked(getAddresses).mockImplementation(() => { throw new Error('This should not be called'); }); const { result } = renderHook( () => useAddresses({ names: testNames }, { enabled: false }), { wrapper: getNewReactQueryTestProvider(), }, ); await new Promise((resolve) => setTimeout(resolve, 100)); expect(getAddresses).not.toHaveBeenCalled(); expect(result.current.isLoading).toBe(false); expect(result.current.fetchStatus).toBe('idle'); }); it('enables the query when enabled=true and names are valid', async () => { vi.mocked(getAddresses).mockResolvedValue(testAddresses); const { result } = renderHook( () => useAddresses({ names: testNames }, { enabled: true }), { wrapper: getNewReactQueryTestProvider(), }, ); await waitFor(() => { expect(result.current.data).toEqual(testAddresses); expect(result.current.isLoading).toBe(false); }); expect(getAddresses).toHaveBeenCalledWith({ names: testNames, }); }); it('handles empty results from getAddresses', async () => { vi.mocked(getAddresses).mockResolvedValue([]); const { result } = renderHook(() => useAddresses({ names: testNames }), { wrapper: getNewReactQueryTestProvider(), }); await waitFor(() => { expect(result.current.data).toEqual([]); expect(result.current.isPending).toBe(false); expect(result.current.isError).toBe(false); }); }); it('does not run query when names prop changes to empty array', async () => { vi.mocked(getAddresses).mockResolvedValue(testAddresses); const { result, rerender } = renderHook( (props: { names: string[] }) => useAddresses({ names: props.names }), { wrapper: getNewReactQueryTestProvider(), initialProps: { names: testNames }, }, ); await waitFor(() => { expect(result.current.data).toEqual(testAddresses); }); vi.clearAllMocks(); rerender({ names: [] }); await new Promise((resolve) => setTimeout(resolve, 100)); expect(getAddresses).not.toHaveBeenCalled(); }); });