test: add page tests for Dashboard, Disclosures, AdminAudit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 21:27:05 +00:00
parent 36ffe43f18
commit e7f7817ebb
3 changed files with 302 additions and 0 deletions

View file

@ -0,0 +1,80 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { screen, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/test/mocks/server'
import { renderWithProviders } from '@/test/utils'
import { AdminAuditPage } from '@/pages/AdminAuditPage'
describe('AdminAuditPage', () => {
beforeEach(() => {
localStorage.setItem('access_token', 'test-token')
})
it('renders audit log entries', async () => {
renderWithProviders(<AdminAuditPage />)
await waitFor(() => {
// mockAuditLogEntry has action 'case.update'
expect(screen.getByText('case.update')).toBeInTheDocument()
})
// Should show entity info: entity_type 'case' + entity_id '1'
expect(screen.getByText('case #1')).toBeInTheDocument()
// Should show user_id = 1
expect(screen.getByText('1')).toBeInTheDocument()
// Should show the IP address
expect(screen.getByText('10.10.181.104')).toBeInTheDocument()
})
it('shows filter controls', async () => {
renderWithProviders(<AdminAuditPage />)
// Filter section title
expect(screen.getByText('Filter')).toBeInTheDocument()
// Filter input labels
expect(screen.getByLabelText('Benutzer-ID')).toBeInTheDocument()
expect(screen.getByLabelText('Aktion')).toBeInTheDocument()
expect(screen.getByLabelText('Datum von')).toBeInTheDocument()
expect(screen.getByLabelText('Datum bis')).toBeInTheDocument()
// Filter buttons
expect(screen.getByText('Filtern')).toBeInTheDocument()
expect(screen.getByText('Zurücksetzen')).toBeInTheDocument()
})
it('handles empty audit log list', async () => {
server.use(
http.get('/api/admin/audit-log', () => {
return HttpResponse.json([])
}),
)
renderWithProviders(<AdminAuditPage />)
await waitFor(() => {
expect(screen.getByText('Keine Audit-Einträge gefunden.')).toBeInTheDocument()
})
})
it('shows loading state', () => {
// Delay the API response to observe loading state
server.use(
http.get('/api/admin/audit-log', async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
return HttpResponse.json([])
}),
)
renderWithProviders(<AdminAuditPage />)
// The heading is always visible
expect(screen.getByText('Audit-Log')).toBeInTheDocument()
// During loading, neither the empty message nor table data should appear
expect(screen.queryByText('Keine Audit-Einträge gefunden.')).not.toBeInTheDocument()
expect(screen.queryByText('case.update')).not.toBeInTheDocument()
})
})

View file

@ -0,0 +1,148 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { screen, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/test/mocks/server'
import { renderWithProviders } from '@/test/utils'
import { DashboardPage } from '@/pages/DashboardPage'
// Recharts ResponsiveContainer needs ResizeObserver as a proper constructor
class ResizeObserverStub {
observe() {}
unobserve() {}
disconnect() {}
}
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
// Mock recharts to avoid SVG rendering issues in jsdom
// We render a simplified version that outputs the data as text
vi.mock('recharts', async () => {
const actual = await vi.importActual<typeof import('recharts')>('recharts')
return {
...actual,
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
<div data-testid="responsive-container">{children}</div>
),
PieChart: ({ children }: { children: React.ReactNode }) => (
<div data-testid="pie-chart">{children}</div>
),
BarChart: ({ children }: { children: React.ReactNode }) => (
<div data-testid="bar-chart">{children}</div>
),
Pie: ({ data }: { data?: Array<{ name: string; value: number }> }) => (
<div data-testid="pie">
{data?.map((entry) => (
<span key={entry.name}>{entry.name}</span>
))}
</div>
),
Bar: () => <div data-testid="bar" />,
Cell: () => <div />,
XAxis: () => <div />,
YAxis: () => <div />,
CartesianGrid: () => <div />,
Tooltip: () => <div />,
Legend: () => <div />,
}
})
describe('DashboardPage', () => {
beforeEach(() => {
localStorage.setItem('access_token', 'test-token')
})
it('renders KPI cards with correct values once loaded', async () => {
renderWithProviders(<DashboardPage />)
await waitFor(() => {
expect(screen.getByText('Fälle gesamt')).toBeInTheDocument()
})
// Check all four KPI card titles
expect(screen.getByText('Offene ICD')).toBeInTheDocument()
expect(screen.getByText('Offene Codierung')).toBeInTheDocument()
expect(screen.getByText('Gutachten gesamt')).toBeInTheDocument()
// Check KPI values from mockDashboardKPIs (142, 18, 7, 89)
expect(screen.getByText('142')).toBeInTheDocument()
expect(screen.getByText('18')).toBeInTheDocument()
expect(screen.getByText('7')).toBeInTheDocument()
expect(screen.getByText('89')).toBeInTheDocument()
})
it('shows year selector', async () => {
renderWithProviders(<DashboardPage />)
// The Select trigger should show the current year
const currentYear = new Date().getFullYear()
await waitFor(() => {
expect(screen.getByText(String(currentYear))).toBeInTheDocument()
})
// The heading should be present
expect(screen.getByText('Dashboard')).toBeInTheDocument()
})
it('shows loading skeleton initially', () => {
// Delay the API response so we can observe the loading state
server.use(
http.get('/api/reports/dashboard', async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
return HttpResponse.json({ kpis: {}, weekly: [] })
}),
)
renderWithProviders(<DashboardPage />)
// During loading, the KPI titles should not be visible yet
expect(screen.queryByText('Fälle gesamt')).not.toBeInTheDocument()
// The heading is always visible
expect(screen.getByText('Dashboard')).toBeInTheDocument()
})
it('filters 0-value Fallgruppen from pie chart data', async () => {
// The mock data has: onko: 65, ortho: 42, kardio: 20, neuro: 15
// FALLGRUPPEN_LABELS maps onko->Onkologie, kardio->Kardiologie
// ortho and neuro are not in FALLGRUPPEN_LABELS so they use the raw key
// All values are > 0, so all should appear in the pie chart
// sd, intensiv, galle are NOT in mock data, so they should not appear
renderWithProviders(<DashboardPage />)
await waitFor(() => {
expect(screen.getByText('Fälle gesamt')).toBeInTheDocument()
})
// The chart section title should appear
expect(screen.getByText('Fallgruppen')).toBeInTheDocument()
// Onkologie and Kardiologie should appear (mapped from FALLGRUPPEN_LABELS)
expect(screen.getByText('Onkologie')).toBeInTheDocument()
expect(screen.getByText('Kardiologie')).toBeInTheDocument()
// ortho and neuro use raw keys since they are not in FALLGRUPPEN_LABELS
expect(screen.getByText('ortho')).toBeInTheDocument()
expect(screen.getByText('neuro')).toBeInTheDocument()
// Schilddrüse should NOT appear (sd is not in mock data)
expect(screen.queryByText('Schilddrüse')).not.toBeInTheDocument()
// Intensivmedizin should NOT appear (intensiv is not in mock data)
expect(screen.queryByText('Intensivmedizin')).not.toBeInTheDocument()
// Gallenblase should NOT appear (galle is not in mock data)
expect(screen.queryByText('Gallenblase')).not.toBeInTheDocument()
})
it('handles error state when API returns 500', async () => {
server.use(
http.get('/api/reports/dashboard', () => {
return new HttpResponse(null, { status: 500 })
}),
)
renderWithProviders(<DashboardPage />)
// When data is null and loading is false, it shows the fallback message
await waitFor(() => {
expect(screen.getByText('Keine Daten verfügbar.')).toBeInTheDocument()
})
})
})

View file

@ -0,0 +1,74 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { screen, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/test/mocks/server'
import { renderWithProviders } from '@/test/utils'
import { DisclosuresPage } from '@/pages/DisclosuresPage'
describe('DisclosuresPage', () => {
beforeEach(() => {
localStorage.setItem('access_token', 'test-token')
})
it('renders disclosure requests list', async () => {
renderWithProviders(<DisclosuresPage />)
await waitFor(() => {
// The mock handler returns [mockDisclosureRequest] with fall_id '2026-06-onko-A123456789'
expect(screen.getByText('Fall 2026-06-onko-A123456789')).toBeInTheDocument()
})
// Should show requester username
expect(screen.getByText('dak_user')).toBeInTheDocument()
// Should show the reason
expect(screen.getByText('Benötige vollständige Patientendaten für Rückruf.')).toBeInTheDocument()
// Should show the status badge
expect(screen.getByText('pending')).toBeInTheDocument()
})
it('shows approve and reject buttons', async () => {
renderWithProviders(<DisclosuresPage />)
await waitFor(() => {
expect(screen.getByText('Genehmigen')).toBeInTheDocument()
})
expect(screen.getByText('Ablehnen')).toBeInTheDocument()
})
it('handles empty disclosure list', async () => {
server.use(
http.get('/api/admin/disclosure-requests', () => {
return HttpResponse.json([])
}),
)
renderWithProviders(<DisclosuresPage />)
await waitFor(() => {
expect(screen.getByText('Keine offenen Anfragen.')).toBeInTheDocument()
})
})
it('shows loading state', () => {
// Delay the API response to observe loading state
server.use(
http.get('/api/admin/disclosure-requests', async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
return HttpResponse.json([])
}),
)
renderWithProviders(<DisclosuresPage />)
// The heading is always visible
expect(screen.getByText('Freigabe-Anfragen')).toBeInTheDocument()
// During loading, the "Keine offenen Anfragen." message should not be visible yet
expect(screen.queryByText('Keine offenen Anfragen.')).not.toBeInTheDocument()
// Nor should any disclosure item be visible
expect(screen.queryByText('Genehmigen')).not.toBeInTheDocument()
})
})