test: add CasesPage integration tests (list, filter, detail)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 21:36:12 +00:00
parent ba3f930e4d
commit 10524a471d

View file

@ -0,0 +1,266 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { http, HttpResponse } from 'msw'
import { server } from '@/test/mocks/server'
import { renderWithProviders } from '@/test/utils'
import { CasesPage } from '@/pages/CasesPage'
import { mockCase, mockCaseNoIcd, mockEmptyCaseList } from '@/test/mocks/data'
describe('CasesPage', () => {
beforeEach(() => {
localStorage.setItem('access_token', 'test-token')
})
// ==================== List rendering ====================
it('renders the page heading', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText('Fälle')).toBeInTheDocument()
})
})
it('renders case list table with fall_id, datum, and fallgruppe columns', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
// Wait for the table to load with data
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Check table headers
expect(screen.getByText('Fall-ID')).toBeInTheDocument()
expect(screen.getByText('Datum')).toBeInTheDocument()
expect(screen.getByText('Fallgruppe')).toBeInTheDocument()
expect(screen.getByText('KVNR')).toBeInTheDocument()
expect(screen.getByText('ICD')).toBeInTheDocument()
// "Gutachten" appears both as table header and as a status badge, so use getAllByText
expect(screen.getAllByText('Gutachten').length).toBeGreaterThanOrEqual(1)
expect(screen.getByText('Status')).toBeInTheDocument()
// Admin-only columns should be visible (default mockUser is admin)
expect(screen.getByText('Nachname')).toBeInTheDocument()
expect(screen.getByText('Vorname')).toBeInTheDocument()
})
it('shows correct number of cases from mock data', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
// Wait for data to load
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Both mock cases should be rendered
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
expect(screen.getByText(mockCaseNoIcd.fall_id!)).toBeInTheDocument()
// Total count shown in pagination area
expect(screen.getByText('2 Fälle insgesamt')).toBeInTheDocument()
})
it('renders case data correctly in table rows', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Check first case data
expect(screen.getByText('Onkologie')).toBeInTheDocument()
expect(screen.getByText(mockCase.kvnr!)).toBeInTheDocument()
expect(screen.getByText(mockCase.icd!)).toBeInTheDocument()
expect(screen.getByText(mockCase.nachname!)).toBeInTheDocument()
// Second case should also be present
expect(screen.getByText(mockCaseNoIcd.fall_id!)).toBeInTheDocument()
expect(screen.getByText(mockCaseNoIcd.kvnr!)).toBeInTheDocument()
})
it('handles empty list and shows empty message', async () => {
server.use(
http.get('/api/cases/', () => {
return HttpResponse.json(mockEmptyCaseList)
}),
)
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText('Keine Fälle gefunden.')).toBeInTheDocument()
})
})
it('shows loading state with skeleton placeholders', () => {
// Delay the API response to observe loading state
server.use(
http.get('/api/cases/', async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
return HttpResponse.json(mockEmptyCaseList)
}),
)
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
// During loading, the heading is always visible
expect(screen.getByText('Fälle')).toBeInTheDocument()
// Table data should not be visible yet
expect(screen.queryByText('Fall-ID')).not.toBeInTheDocument()
})
// ==================== Filtering ====================
it('renders search input', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Search input with admin placeholder
const searchInput = screen.getByPlaceholderText('Suche nach Name, Fall-ID, KVNR...')
expect(searchInput).toBeInTheDocument()
})
it('allows typing into the search input', async () => {
const user = userEvent.setup()
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
const searchInput = screen.getByPlaceholderText('Suche nach Name, Fall-ID, KVNR...')
await user.type(searchInput, 'onko')
expect(searchInput).toHaveValue('onko')
})
it('renders year and fallgruppe filter selects', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Year select trigger shows "Alle Jahre" by default
expect(screen.getByText('Alle Jahre')).toBeInTheDocument()
// Fallgruppe select trigger shows "Alle Fallgruppen" by default
expect(screen.getByText('Alle Fallgruppen')).toBeInTheDocument()
// ICD filter shows "Alle" by default
// There are multiple "Alle" texts, so we verify the ICD filter trigger exists
const alleTriggers = screen.getAllByText('Alle')
expect(alleTriggers.length).toBeGreaterThanOrEqual(1)
})
// ==================== Detail view ====================
it('opens detail sheet when clicking a table row', async () => {
const user = userEvent.setup()
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
// Wait for data to load
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Click on the first case's fall_id cell (which is inside a table row)
await user.click(screen.getByText(mockCase.fall_id!))
// The Sheet (Radix Dialog) should open with case details
await waitFor(() => {
const dialog = screen.getByRole('dialog')
expect(dialog).toBeInTheDocument()
})
// SheetTitle shows the fall_id
const dialog = screen.getByRole('dialog')
expect(within(dialog).getByText(`Fall ${mockCase.fall_id}`)).toBeInTheDocument()
})
it('shows case detail information in the sheet', async () => {
const user = userEvent.setup()
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Click on the first case
await user.click(screen.getByText(mockCase.fall_id!))
// Wait for sheet to open
await waitFor(() => {
expect(screen.getByRole('dialog')).toBeInTheDocument()
})
const dialog = screen.getByRole('dialog')
// SheetDescription shows fallgruppe and KW/year
expect(within(dialog).getByText(/Onkologie/)).toBeInTheDocument()
// Read-only metadata fields
expect(within(dialog).getByText('Fall-ID')).toBeInTheDocument()
expect(within(dialog).getByText('CRM-Ticket')).toBeInTheDocument()
// Editable section headers from fieldConfig
expect(within(dialog).getByText('Persönliche Daten')).toBeInTheDocument()
expect(within(dialog).getByText('Kontakt')).toBeInTheDocument()
expect(within(dialog).getByText('Falldetails')).toBeInTheDocument()
// ICD section
expect(within(dialog).getByText('ICD-Code')).toBeInTheDocument()
// Edit button should be present
expect(within(dialog).getByText('Bearbeiten')).toBeInTheDocument()
})
// ==================== Error handling ====================
it('shows empty state when API returns 500', async () => {
server.use(
http.get('/api/cases/', () => {
return new HttpResponse(null, { status: 500 })
}),
)
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
// When the query fails, data is null, so the empty message is shown
await waitFor(() => {
expect(screen.getByText('Keine Fälle gefunden.')).toBeInTheDocument()
})
})
// ==================== Pagination ====================
it('shows pagination controls', async () => {
renderWithProviders(<CasesPage />, { initialRoute: '/cases' })
await waitFor(() => {
expect(screen.getByText(mockCase.fall_id!)).toBeInTheDocument()
})
// Pagination text
expect(screen.getByText('Seite 1 von 1')).toBeInTheDocument()
expect(screen.getByText('2 Fälle insgesamt')).toBeInTheDocument()
})
// ==================== pendingIcdOnly mode ====================
it('renders ICD-Eingabe heading when pendingIcdOnly is true', async () => {
renderWithProviders(<CasesPage pendingIcdOnly />, { initialRoute: '/cases/pending-icd' })
await waitFor(() => {
expect(screen.getByText('ICD-Eingabe')).toBeInTheDocument()
})
// The filter bar should NOT be rendered in pendingIcdOnly mode
expect(screen.queryByPlaceholderText('Suche nach Name, Fall-ID, KVNR...')).not.toBeInTheDocument()
expect(screen.queryByText('Alle Fallgruppen')).not.toBeInTheDocument()
})
})