mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 18:23:42 +00:00
test: add hook tests for useNotifications and useReports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e67fe73da7
commit
36ffe43f18
2 changed files with 273 additions and 0 deletions
123
frontend/src/hooks/__tests__/useNotifications.test.ts
Normal file
123
frontend/src/hooks/__tests__/useNotifications.test.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
150
frontend/src/hooks/__tests__/useReports.test.ts
Normal file
150
frontend/src/hooks/__tests__/useReports.test.ts
Normal 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))
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue