test: add hook tests for useNotifications and useReports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 21:23:15 +00:00
parent e67fe73da7
commit 36ffe43f18
2 changed files with 273 additions and 0 deletions

View file

@ -0,0 +1,123 @@
import { describe, it, expect } from 'vitest'
import { waitFor, act } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/test/mocks/server'
import { renderHookWithProviders } from '@/test/utils'
import {
mockNotification,
mockNotificationRead,
mockNotificationList,
} from '@/test/mocks/data'
import { useNotifications } from '@/hooks/useNotifications'
// ---------------------------------------------------------------------------
// useNotifications
// ---------------------------------------------------------------------------
describe('useNotifications', () => {
it('returns notifications list and unread count', async () => {
const { result } = renderHookWithProviders(() => useNotifications())
// Initially loading
expect(result.current.loading).toBe(true)
expect(result.current.notifications).toEqual([])
expect(result.current.unreadCount).toBe(0)
await waitFor(() => expect(result.current.loading).toBe(false))
expect(result.current.notifications).toEqual(mockNotificationList.items)
expect(result.current.notifications).toHaveLength(2)
expect(result.current.unreadCount).toBe(1)
expect(result.current.notifications[0].is_read).toBe(false)
expect(result.current.notifications[1].is_read).toBe(true)
})
it('handles empty notifications', async () => {
server.use(
http.get('/api/notifications', () => {
return HttpResponse.json({ items: [], unread_count: 0 })
}),
)
const { result } = renderHookWithProviders(() => useNotifications())
await waitFor(() => expect(result.current.loading).toBe(false))
expect(result.current.notifications).toEqual([])
expect(result.current.unreadCount).toBe(0)
})
it('handles loading state', async () => {
const { result } = renderHookWithProviders(() => useNotifications())
// Should start in loading state
expect(result.current.loading).toBe(true)
expect(result.current.notifications).toEqual([])
expect(result.current.unreadCount).toBe(0)
// After loading completes
await waitFor(() => expect(result.current.loading).toBe(false))
expect(result.current.notifications).toHaveLength(2)
})
it('markAsRead mutation works', async () => {
let capturedId: number | null = null
server.use(
http.put('/api/notifications/:id/read', ({ params }) => {
capturedId = Number(params.id)
return HttpResponse.json({
...mockNotification,
id: capturedId,
is_read: true,
})
}),
)
const { result } = renderHookWithProviders(() => useNotifications())
await waitFor(() => expect(result.current.loading).toBe(false))
await act(async () => {
result.current.markAsRead(1)
})
expect(capturedId).toBe(1)
})
it('markAllAsRead mutation works', async () => {
let markAllCalled = false
server.use(
http.put('/api/notifications/read-all', () => {
markAllCalled = true
return HttpResponse.json({ marked_read: 1 })
}),
)
const { result } = renderHookWithProviders(() => useNotifications())
await waitFor(() => expect(result.current.loading).toBe(false))
await act(async () => {
result.current.markAllAsRead()
})
expect(markAllCalled).toBe(true)
})
it('exposes refresh function', async () => {
const { result } = renderHookWithProviders(() => useNotifications())
await waitFor(() => expect(result.current.loading).toBe(false))
// refresh should be a function
expect(typeof result.current.refresh).toBe('function')
// calling refresh should not throw
await act(async () => {
result.current.refresh()
})
})
})

View file

@ -0,0 +1,150 @@
import { describe, it, expect } from 'vitest'
import { waitFor, act } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/test/mocks/server'
import { renderHookWithProviders } from '@/test/utils'
import { mockReportMeta } from '@/test/mocks/data'
import { useReports, useGenerateReport, useDeleteReports } from '@/hooks/useReports'
// ---------------------------------------------------------------------------
// useReports
// ---------------------------------------------------------------------------
describe('useReports', () => {
it('fetches report list (success)', async () => {
const { result } = renderHookWithProviders(() => useReports())
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual({
items: [mockReportMeta],
total: 1,
})
expect(result.current.data!.items).toHaveLength(1)
expect(result.current.data!.items[0].jahr).toBe(2026)
expect(result.current.data!.items[0].kw).toBe(6)
})
it('handles empty list', async () => {
server.use(
http.get('/api/reports/list', () => {
return HttpResponse.json({ items: [], total: 0 })
}),
)
const { result } = renderHookWithProviders(() => useReports())
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual({ items: [], total: 0 })
expect(result.current.data!.items).toHaveLength(0)
})
})
// ---------------------------------------------------------------------------
// useGenerateReport
// ---------------------------------------------------------------------------
describe('useGenerateReport', () => {
it('generates report (success)', async () => {
const newReport = {
...mockReportMeta,
id: 2,
kw: 7,
report_date: '2026-02-16',
generated_at: '2026-02-17T08:00:00Z',
}
server.use(
http.post('/api/reports/generate', () => {
return HttpResponse.json(newReport)
}),
)
const { result } = renderHookWithProviders(() => useGenerateReport())
let response: unknown
await act(async () => {
response = await result.current.mutateAsync({ jahr: 2026, kw: 7 })
})
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect((response as { id: number }).id).toBe(2)
expect((response as { kw: number }).kw).toBe(7)
expect((response as { jahr: number }).jahr).toBe(2026)
})
it('handles error', async () => {
server.use(
http.post('/api/reports/generate', () => {
return HttpResponse.json(
{ detail: 'Report generation failed' },
{ status: 500 },
)
}),
)
const { result } = renderHookWithProviders(() => useGenerateReport())
await act(async () => {
try {
await result.current.mutateAsync({ jahr: 2026, kw: 99 })
} catch {
// expected
}
})
await waitFor(() => expect(result.current.isError).toBe(true))
})
})
// ---------------------------------------------------------------------------
// useDeleteReports
// ---------------------------------------------------------------------------
describe('useDeleteReports', () => {
it('deletes reports (success)', async () => {
let capturedBody: unknown = null
server.use(
http.delete('/api/reports/delete', async ({ request }) => {
capturedBody = await request.json()
return HttpResponse.json({ deleted: 2 })
}),
)
const { result } = renderHookWithProviders(() => useDeleteReports())
await act(async () => {
await result.current.mutateAsync([1, 2])
})
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(capturedBody).toEqual([1, 2])
})
it('handles error', async () => {
server.use(
http.delete('/api/reports/delete', () => {
return HttpResponse.json(
{ detail: 'Forbidden' },
{ status: 403 },
)
}),
)
const { result } = renderHookWithProviders(() => useDeleteReports())
await act(async () => {
try {
await result.current.mutateAsync([999])
} catch {
// expected
}
})
await waitFor(() => expect(result.current.isError).toBe(true))
})
})