import { renderHook, act, waitFor } from '@testing-library/react'; import { useSchoolSuggest } from '@/hooks/useSchoolSuggest'; const realFetch = global.fetch; function mockFetch(rows: unknown[], delayMs = 0) { global.fetch = jest.fn(async (_url: unknown, init?: { signal?: AbortSignal }) => { if (delayMs) { await new Promise((resolve, reject) => { const t = setTimeout(resolve, delayMs); init?.signal?.addEventListener('abort', () => { clearTimeout(t); reject(Object.assign(new Error('aborted'), { name: 'AbortError' })); }); }); } return { ok: true, json: async () => ({ suggestions: rows }) }; }) as unknown as typeof fetch; } const ROW = { urn: 1, school_name: 'Brecknock Primary School', local_authority: 'Camden', postcode: 'NW1 1AA', phase: 'Primary', school_type: 'Community school', }; describe('useSchoolSuggest', () => { beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); global.fetch = realFetch; }); it('does not fetch below the minimum query length', () => { mockFetch([ROW]); renderHook(() => useSchoolSuggest('b', true)); act(() => { jest.advanceTimersByTime(500); }); expect(global.fetch).not.toHaveBeenCalled(); }); it('does not fetch at all when disabled', () => { // The flag being off must mean no request, not a hidden dropdown. mockFetch([ROW]); renderHook(() => useSchoolSuggest('brecknock', false)); act(() => { jest.advanceTimersByTime(500); }); expect(global.fetch).not.toHaveBeenCalled(); }); it('debounces rather than firing per keystroke', () => { mockFetch([ROW]); const { rerender } = renderHook( ({ q }) => useSchoolSuggest(q, true), { initialProps: { q: 'br' } }); rerender({ q: 'bre' }); rerender({ q: 'brec' }); act(() => { jest.advanceTimersByTime(199); }); expect(global.fetch).not.toHaveBeenCalled(); act(() => { jest.advanceTimersByTime(2); }); expect(global.fetch).toHaveBeenCalledTimes(1); }); it('opens with results once they arrive', async () => { mockFetch([ROW]); const { result } = renderHook(() => useSchoolSuggest('brecknock', true)); act(() => { jest.advanceTimersByTime(200); }); await waitFor(() => expect(result.current.suggestions).toHaveLength(1)); expect(result.current.open).toBe(true); }); it('close() hides the list without clearing the query', async () => { mockFetch([ROW]); const { result } = renderHook(() => useSchoolSuggest('brecknock', true)); act(() => { jest.advanceTimersByTime(200); }); await waitFor(() => expect(result.current.open).toBe(true)); act(() => { result.current.close(); }); expect(result.current.open).toBe(false); }); });