test: add page tests for AdminUsers, Reports, Login

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

View file

@ -0,0 +1,94 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { screen, waitFor } 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 { AdminUsersPage } from '@/pages/AdminUsersPage'
describe('AdminUsersPage', () => {
beforeEach(() => {
localStorage.setItem('access_token', 'test-token')
})
it('renders user list with user data', async () => {
renderWithProviders(<AdminUsersPage />, { initialRoute: '/admin/users' })
await waitFor(() => {
// mockUserResponse has username 'admin_user', email 'admin@dak.de', role 'admin'
expect(screen.getByText('admin_user')).toBeInTheDocument()
})
expect(screen.getByText('admin@dak.de')).toBeInTheDocument()
expect(screen.getByText('Admin')).toBeInTheDocument()
// "Aktiv" appears both as table header and badge; check for at least 2
const aktivElements = screen.getAllByText('Aktiv')
expect(aktivElements.length).toBeGreaterThanOrEqual(2)
})
it('shows "Neuen Benutzer erstellen" button and opens dialog', async () => {
const user = userEvent.setup()
renderWithProviders(<AdminUsersPage />, { initialRoute: '/admin/users' })
await waitFor(() => {
expect(screen.getByText('Neuen Benutzer erstellen')).toBeInTheDocument()
})
// Click the create button to open the dialog
await user.click(screen.getByText('Neuen Benutzer erstellen'))
// Dialog should open with form fields
await waitFor(() => {
expect(screen.getByLabelText('Benutzername')).toBeInTheDocument()
})
expect(screen.getByLabelText('E-Mail')).toBeInTheDocument()
expect(screen.getByLabelText('Passwort')).toBeInTheDocument()
})
it('handles empty user list', async () => {
server.use(
http.get('/api/admin/users', () => {
return HttpResponse.json([])
}),
)
renderWithProviders(<AdminUsersPage />, { initialRoute: '/admin/users' })
await waitFor(() => {
expect(screen.getByText('Keine Benutzer gefunden.')).toBeInTheDocument()
})
})
it('shows loading state', () => {
server.use(
http.get('/api/admin/users', async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
return HttpResponse.json([])
}),
)
renderWithProviders(<AdminUsersPage />, { initialRoute: '/admin/users' })
// Heading is always visible
expect(screen.getByText('Benutzer')).toBeInTheDocument()
// During loading, user data should not be visible yet
expect(screen.queryByText('admin_user')).not.toBeInTheDocument()
expect(screen.queryByText('Keine Benutzer gefunden.')).not.toBeInTheDocument()
})
it('shows error state on API failure', async () => {
server.use(
http.get('/api/admin/users', () => {
return new HttpResponse(null, { status: 500 })
}),
)
renderWithProviders(<AdminUsersPage />, { initialRoute: '/admin/users' })
// When the query fails, users defaults to [] so it should show empty state
await waitFor(() => {
expect(screen.getByText('Keine Benutzer gefunden.')).toBeInTheDocument()
})
})
})

View file

@ -0,0 +1,94 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { screen, waitFor } 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 { LoginPage } from '@/pages/LoginPage'
// Mock window.matchMedia for useTheme hook
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
describe('LoginPage', () => {
beforeEach(() => {
// LoginPage is for unauthenticated users — do NOT set access_token
localStorage.removeItem('access_token')
localStorage.removeItem('refresh_token')
// Override /api/auth/me to return 401 for unauthenticated state
server.use(
http.get('/api/auth/me', () => {
return new HttpResponse(null, { status: 401 })
}),
)
})
it('renders email and password input fields', () => {
renderWithProviders(<LoginPage />, { initialRoute: '/login' })
expect(screen.getByLabelText('E-Mail')).toBeInTheDocument()
expect(screen.getByLabelText('Passwort')).toBeInTheDocument()
})
it('renders submit button', () => {
renderWithProviders(<LoginPage />, { initialRoute: '/login' })
expect(screen.getByRole('button', { name: 'Anmelden' })).toBeInTheDocument()
})
it('shows error message on failed login', async () => {
const user = userEvent.setup()
// Override login endpoint to return 401
server.use(
http.post('/api/auth/login', () => {
return HttpResponse.json(
{ detail: 'Invalid credentials' },
{ status: 401 },
)
}),
)
renderWithProviders(<LoginPage />, { initialRoute: '/login' })
// Fill in valid form data
await user.type(screen.getByLabelText('E-Mail'), 'wrong@dak.de')
await user.type(screen.getByLabelText('Passwort'), 'wrongpassword')
// Submit the form
await user.click(screen.getByRole('button', { name: 'Anmelden' }))
// Should show error message for 401
await waitFor(() => {
expect(screen.getByText('Ungueltige Anmeldedaten')).toBeInTheDocument()
})
})
it('shows validation errors for empty form submission', async () => {
const user = userEvent.setup()
renderWithProviders(<LoginPage />, { initialRoute: '/login' })
// Click submit without filling in any fields
await user.click(screen.getByRole('button', { name: 'Anmelden' }))
// Zod validation errors should appear
await waitFor(() => {
expect(screen.getByText('Bitte geben Sie eine gueltige E-Mail-Adresse ein')).toBeInTheDocument()
})
expect(screen.getByText('Passwort ist erforderlich')).toBeInTheDocument()
})
})

View file

@ -0,0 +1,86 @@
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 { ReportsPage } from '@/pages/ReportsPage'
describe('ReportsPage', () => {
beforeEach(() => {
localStorage.setItem('access_token', 'test-token')
})
it('renders report list with report metadata', async () => {
renderWithProviders(<ReportsPage />, { initialRoute: '/reports' })
await waitFor(() => {
// mockReportMeta has jahr: 2026, kw: 6
expect(screen.getByText('2026')).toBeInTheDocument()
})
expect(screen.getByText('KW 6')).toBeInTheDocument()
// Total count should show in the header "Bisherige Berichte (1)"
expect(screen.getByText('Bisherige Berichte (1)')).toBeInTheDocument()
})
it('shows "Bericht generieren" button and form fields', async () => {
renderWithProviders(<ReportsPage />, { initialRoute: '/reports' })
await waitFor(() => {
// "Bericht generieren" appears as card title and button text; use button role
expect(screen.getByRole('button', { name: /Bericht generieren/ })).toBeInTheDocument()
})
// Admin user sees the generation form with Jahr and KW inputs
expect(screen.getByLabelText('Jahr')).toBeInTheDocument()
expect(screen.getByLabelText('Kalenderwoche')).toBeInTheDocument()
})
it('handles empty report list', async () => {
server.use(
http.get('/api/reports/list', () => {
return HttpResponse.json({ items: [], total: 0 })
}),
)
renderWithProviders(<ReportsPage />, { initialRoute: '/reports' })
await waitFor(() => {
expect(screen.getByText('Keine Berichte vorhanden.')).toBeInTheDocument()
})
})
it('shows loading state', () => {
server.use(
http.get('/api/reports/list', async () => {
await new Promise((resolve) => setTimeout(resolve, 500))
return HttpResponse.json({ items: [], total: 0 })
}),
)
renderWithProviders(<ReportsPage />, { initialRoute: '/reports' })
// The heading is always visible
expect(screen.getByText('Berichte')).toBeInTheDocument()
// During loading, the empty state message or report data should not be visible
expect(screen.queryByText('Keine Berichte vorhanden.')).not.toBeInTheDocument()
expect(screen.queryByText('KW 6')).not.toBeInTheDocument()
})
it('shows error on API failure', async () => {
server.use(
http.get('/api/reports/list', () => {
return new HttpResponse(null, { status: 500 })
}),
)
renderWithProviders(<ReportsPage />, { initialRoute: '/reports' })
// When query fails, reports defaults to [] so empty state appears
await waitFor(() => {
expect(screen.getByText('Keine Berichte vorhanden.')).toBeInTheDocument()
})
})
})