82 tests covering download.ts and all React Query hooks. API modules are mocked with vi.mock; mutation tests spy on queryClient.invalidateQueries to verify cache invalidation. Conditional queries (null id) are verified to stay idle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
182 lines
5.9 KiB
TypeScript
182 lines
5.9 KiB
TypeScript
import { QueryClientProvider } from '@tanstack/react-query'
|
|
import { renderHook, waitFor, act } from '@testing-library/react'
|
|
import { createTestQueryClient } from '../test/utils'
|
|
import {
|
|
useRuns,
|
|
useRun,
|
|
useCreateRun,
|
|
useUpdateRun,
|
|
useDeleteRun,
|
|
useNamingCategories,
|
|
useNameSuggestions,
|
|
} from './useRuns'
|
|
|
|
vi.mock('../api/runs')
|
|
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }))
|
|
|
|
import { getRuns, getRun, createRun, updateRun, deleteRun, getNamingCategories } from '../api/runs'
|
|
import { toast } from 'sonner'
|
|
|
|
function createWrapper() {
|
|
const queryClient = createTestQueryClient()
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
)
|
|
return { queryClient, wrapper }
|
|
}
|
|
|
|
describe('useRuns', () => {
|
|
it('calls getRuns and returns data', async () => {
|
|
const runs = [{ id: 1, name: 'My Run' }]
|
|
vi.mocked(getRuns).mockResolvedValue(runs as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useRuns(), { wrapper })
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
|
|
|
expect(getRuns).toHaveBeenCalledOnce()
|
|
expect(result.current.data).toEqual(runs)
|
|
})
|
|
})
|
|
|
|
describe('useRun', () => {
|
|
it('calls getRun with the given id', async () => {
|
|
const run = { id: 3, name: 'Specific Run' }
|
|
vi.mocked(getRun).mockResolvedValue(run as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useRun(3), { wrapper })
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
|
|
|
expect(getRun).toHaveBeenCalledWith(3)
|
|
})
|
|
})
|
|
|
|
describe('useCreateRun', () => {
|
|
it('calls createRun with the provided input', async () => {
|
|
vi.mocked(createRun).mockResolvedValue({ id: 10 } as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useCreateRun(), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync({ name: 'New Run', gameId: 1, status: 'active' } as never)
|
|
})
|
|
|
|
expect(createRun).toHaveBeenCalledWith({ name: 'New Run', gameId: 1, status: 'active' })
|
|
})
|
|
|
|
it('invalidates the runs query on success', async () => {
|
|
vi.mocked(createRun).mockResolvedValue({} as never)
|
|
const { queryClient, wrapper } = createWrapper()
|
|
const spy = vi.spyOn(queryClient, 'invalidateQueries')
|
|
|
|
const { result } = renderHook(() => useCreateRun(), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync({} as never)
|
|
})
|
|
|
|
expect(spy).toHaveBeenCalledWith({ queryKey: ['runs'] })
|
|
})
|
|
})
|
|
|
|
describe('useUpdateRun', () => {
|
|
it('calls updateRun with the given id and data', async () => {
|
|
vi.mocked(updateRun).mockResolvedValue({} as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useUpdateRun(5), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync({ name: 'Updated' } as never)
|
|
})
|
|
|
|
expect(updateRun).toHaveBeenCalledWith(5, { name: 'Updated' })
|
|
})
|
|
|
|
it('invalidates both the list and individual run query on success', async () => {
|
|
vi.mocked(updateRun).mockResolvedValue({} as never)
|
|
const { queryClient, wrapper } = createWrapper()
|
|
const spy = vi.spyOn(queryClient, 'invalidateQueries')
|
|
|
|
const { result } = renderHook(() => useUpdateRun(5), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync({} as never)
|
|
})
|
|
|
|
expect(spy).toHaveBeenCalledWith({ queryKey: ['runs'] })
|
|
expect(spy).toHaveBeenCalledWith({ queryKey: ['runs', 5] })
|
|
})
|
|
|
|
it('shows a toast when status is set to completed', async () => {
|
|
vi.mocked(updateRun).mockResolvedValue({} as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useUpdateRun(1), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync({ status: 'completed' } as never)
|
|
})
|
|
|
|
expect(toast.success).toHaveBeenCalledWith('Run marked as completed!')
|
|
})
|
|
|
|
it('shows an error toast on failure', async () => {
|
|
vi.mocked(updateRun).mockRejectedValue(new Error('Network error'))
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useUpdateRun(1), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutate({} as never)
|
|
})
|
|
|
|
await waitFor(() =>
|
|
expect(toast.error).toHaveBeenCalledWith('Failed to update run: Network error')
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('useDeleteRun', () => {
|
|
it('calls deleteRun with the given id', async () => {
|
|
vi.mocked(deleteRun).mockResolvedValue(undefined as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useDeleteRun(), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync(7)
|
|
})
|
|
|
|
expect(deleteRun).toHaveBeenCalledWith(7)
|
|
})
|
|
|
|
it('invalidates the runs query on success', async () => {
|
|
vi.mocked(deleteRun).mockResolvedValue(undefined as never)
|
|
const { queryClient, wrapper } = createWrapper()
|
|
const spy = vi.spyOn(queryClient, 'invalidateQueries')
|
|
|
|
const { result } = renderHook(() => useDeleteRun(), { wrapper })
|
|
await act(async () => {
|
|
await result.current.mutateAsync(7)
|
|
})
|
|
|
|
expect(spy).toHaveBeenCalledWith({ queryKey: ['runs'] })
|
|
})
|
|
})
|
|
|
|
describe('useNamingCategories', () => {
|
|
it('calls getNamingCategories', async () => {
|
|
vi.mocked(getNamingCategories).mockResolvedValue([] as never)
|
|
const { wrapper } = createWrapper()
|
|
|
|
const { result } = renderHook(() => useNamingCategories(), { wrapper })
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
|
|
|
expect(getNamingCategories).toHaveBeenCalledOnce()
|
|
})
|
|
})
|
|
|
|
describe('useNameSuggestions', () => {
|
|
it('is disabled when runId is null', () => {
|
|
const { wrapper } = createWrapper()
|
|
const { result } = renderHook(() => useNameSuggestions(null), { wrapper })
|
|
expect(result.current.fetchStatus).toBe('idle')
|
|
})
|
|
})
|