mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 18:23:42 +00:00
test: add CasesPage integration tests (list, filter, detail)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ba3f930e4d
commit
10524a471d
1 changed files with 266 additions and 0 deletions
266
frontend/src/pages/__tests__/CasesPage.test.tsx
Normal file
266
frontend/src/pages/__tests__/CasesPage.test.tsx
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue