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')
})
})