Add unit tests for frontend utilities and hooks
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>
This commit is contained in:
181
frontend/src/hooks/useRuns.test.tsx
Normal file
181
frontend/src/hooks/useRuns.test.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user