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:
148
frontend/src/hooks/useAdmin.test.tsx
Normal file
148
frontend/src/hooks/useAdmin.test.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { renderHook, waitFor, act } from '@testing-library/react'
|
||||
import { createTestQueryClient } from '../test/utils'
|
||||
import { usePokemonList, useCreateGame, useUpdateGame, useDeleteGame } from './useAdmin'
|
||||
|
||||
vi.mock('../api/admin')
|
||||
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }))
|
||||
|
||||
import * as adminApi from '../api/admin'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
function createWrapper() {
|
||||
const queryClient = createTestQueryClient()
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
return { queryClient, wrapper }
|
||||
}
|
||||
|
||||
describe('usePokemonList', () => {
|
||||
it('calls listPokemon with defaults', async () => {
|
||||
vi.mocked(adminApi.listPokemon).mockResolvedValue({ results: [], total: 0 } as never)
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
const { result } = renderHook(() => usePokemonList(), { wrapper })
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true))
|
||||
|
||||
expect(adminApi.listPokemon).toHaveBeenCalledWith(undefined, 50, 0, undefined)
|
||||
})
|
||||
|
||||
it('passes search and filter params to listPokemon', async () => {
|
||||
vi.mocked(adminApi.listPokemon).mockResolvedValue({ results: [], total: 0 } as never)
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
renderHook(() => usePokemonList('pika', 10, 20, 'electric'), { wrapper })
|
||||
await waitFor(() =>
|
||||
expect(adminApi.listPokemon).toHaveBeenCalledWith('pika', 10, 20, 'electric')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('useCreateGame', () => {
|
||||
it('calls createGame with the provided input', async () => {
|
||||
vi.mocked(adminApi.createGame).mockResolvedValue({ id: 1 } as never)
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
const { result } = renderHook(() => useCreateGame(), { wrapper })
|
||||
const input = { name: 'FireRed', slug: 'firered', generation: 3, region: 'kanto', vgId: 1 }
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(input as never)
|
||||
})
|
||||
|
||||
expect(adminApi.createGame).toHaveBeenCalledWith(input)
|
||||
})
|
||||
|
||||
it('invalidates the games query on success', async () => {
|
||||
vi.mocked(adminApi.createGame).mockResolvedValue({} as never)
|
||||
const { queryClient, wrapper } = createWrapper()
|
||||
const spy = vi.spyOn(queryClient, 'invalidateQueries')
|
||||
|
||||
const { result } = renderHook(() => useCreateGame(), { wrapper })
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({} as never)
|
||||
})
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({ queryKey: ['games'] })
|
||||
})
|
||||
|
||||
it('shows a success toast after creating a game', async () => {
|
||||
vi.mocked(adminApi.createGame).mockResolvedValue({} as never)
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
const { result } = renderHook(() => useCreateGame(), { wrapper })
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({} as never)
|
||||
})
|
||||
|
||||
expect(toast.success).toHaveBeenCalledWith('Game created')
|
||||
})
|
||||
|
||||
it('shows an error toast on failure', async () => {
|
||||
vi.mocked(adminApi.createGame).mockRejectedValue(new Error('Conflict'))
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
const { result } = renderHook(() => useCreateGame(), { wrapper })
|
||||
await act(async () => {
|
||||
result.current.mutate({} as never)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(toast.error).toHaveBeenCalledWith('Failed to create game: Conflict'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('useUpdateGame', () => {
|
||||
it('calls updateGame with id and data', async () => {
|
||||
vi.mocked(adminApi.updateGame).mockResolvedValue({} as never)
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
const { result } = renderHook(() => useUpdateGame(), { wrapper })
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 7, data: { name: 'Renamed' } } as never)
|
||||
})
|
||||
|
||||
expect(adminApi.updateGame).toHaveBeenCalledWith(7, { name: 'Renamed' })
|
||||
})
|
||||
|
||||
it('invalidates games and shows a toast on success', async () => {
|
||||
vi.mocked(adminApi.updateGame).mockResolvedValue({} as never)
|
||||
const { queryClient, wrapper } = createWrapper()
|
||||
const spy = vi.spyOn(queryClient, 'invalidateQueries')
|
||||
|
||||
const { result } = renderHook(() => useUpdateGame(), { wrapper })
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync({ id: 1, data: {} } as never)
|
||||
})
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({ queryKey: ['games'] })
|
||||
expect(toast.success).toHaveBeenCalledWith('Game updated')
|
||||
})
|
||||
})
|
||||
|
||||
describe('useDeleteGame', () => {
|
||||
it('calls deleteGame with the given id', async () => {
|
||||
vi.mocked(adminApi.deleteGame).mockResolvedValue(undefined as never)
|
||||
const { wrapper } = createWrapper()
|
||||
|
||||
const { result } = renderHook(() => useDeleteGame(), { wrapper })
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(3)
|
||||
})
|
||||
|
||||
expect(adminApi.deleteGame).toHaveBeenCalledWith(3)
|
||||
})
|
||||
|
||||
it('invalidates games and shows a toast on success', async () => {
|
||||
vi.mocked(adminApi.deleteGame).mockResolvedValue(undefined as never)
|
||||
const { queryClient, wrapper } = createWrapper()
|
||||
const spy = vi.spyOn(queryClient, 'invalidateQueries')
|
||||
|
||||
const { result } = renderHook(() => useDeleteGame(), { wrapper })
|
||||
await act(async () => {
|
||||
await result.current.mutateAsync(3)
|
||||
})
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({ queryKey: ['games'] })
|
||||
expect(toast.success).toHaveBeenCalledWith('Game deleted')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user