import { describe, it, expect, vi, beforeEach } from 'vitest' import { renderHook, act, waitFor } from '@testing-library/react' import React from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { useSavedSearches, useCreateSavedSearch } from './useSavedSearches' import type { SavedSearchDTO } from '../../api/adapters' // ── Mock apiFetch ───────────────────────────────────────────────────────────── const mockApiFetch = vi.fn() vi.mock('../../api/client', () => ({ apiFetch: (...args: unknown[]) => mockApiFetch(...args), apiFetchVoid: vi.fn().mockResolvedValue(undefined), AuthError: class AuthError extends Error { name = 'AuthError' constructor(message: string) { super(message) } }, APIError: class APIError extends Error { name = 'APIError' status: number code: string constructor(message: string, status: number, code: string) { super(message) this.status = status this.code = code } }, })) // ── Test wrapper ────────────────────────────────────────────────────────────── function makeWrapper(queryClient: QueryClient) { return function Wrapper({ children }: { children: React.ReactNode }) { return React.createElement(QueryClientProvider, { client: queryClient }, children) } } // ── Tests ───────────────────────────────────────────────────────────────────── describe('useSavedSearches + useCreateSavedSearch — cache invalidation', () => { let queryClient: QueryClient beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }) mockApiFetch.mockReset() }) it('invalidates saved-searches after createSavedSearch succeeds', async () => { const emptyResponse = { saved_searches: [] as SavedSearchDTO[] } const filledResponse = { saved_searches: [ { name: 'a', query: 'q', created_at: 0, updated_at: 0 } satisfies SavedSearchDTO, ], } // POST returns the created row const createdRow: SavedSearchDTO = { name: 'a', query: 'q', created_at: 0, updated_at: 0 } // First call → initial list fetch (empty) // Second call → re-fetch after invalidation (one row) // Third call may come from the mutation POST mockApiFetch .mockResolvedValueOnce(emptyResponse) // initial GET .mockResolvedValueOnce(createdRow) // POST create .mockResolvedValueOnce(filledResponse) // re-fetch after invalidation const wrapper = makeWrapper(queryClient) const { result: listResult } = renderHook(() => useSavedSearches(), { wrapper }) const { result: mutResult } = renderHook(() => useCreateSavedSearch(), { wrapper }) // Wait for initial fetch to settle await waitFor(() => expect(listResult.current.isSuccess).toBe(true)) expect(listResult.current.data).toEqual([]) // Call mutate act(() => { mutResult.current.mutate({ name: 'a', query: 'q' }) }) // Wait for mutation success + re-fetch await waitFor(() => expect(mutResult.current.isSuccess).toBe(true)) await waitFor(() => { expect(listResult.current.data?.length).toBe(1) }) expect(listResult.current.data?.[0].name).toBe('a') }) })