Files
nuzlocke-tracker/frontend/src/hooks/useAdmin.test.tsx
Julian Tabel 0d2f419c6a 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>
2026-02-21 13:47:55 +01:00

149 lines
5.2 KiB
TypeScript

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