mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 16:03:41 +00:00
test: add page tests for AdminUsers, Reports, Login
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e7f7817ebb
commit
ba3f930e4d
3 changed files with 274 additions and 0 deletions
94
frontend/src/pages/__tests__/AdminUsersPage.test.tsx
Normal file
94
frontend/src/pages/__tests__/AdminUsersPage.test.tsx
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
94
frontend/src/pages/__tests__/LoginPage.test.tsx
Normal file
94
frontend/src/pages/__tests__/LoginPage.test.tsx
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
86
frontend/src/pages/__tests__/ReportsPage.test.tsx
Normal file
86
frontend/src/pages/__tests__/ReportsPage.test.tsx
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue