fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
311
website/__tests__/compliance/DependencyMap.test.tsx
Normal file
311
website/__tests__/compliance/DependencyMap.test.tsx
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* DependencyMap Component Tests
|
||||
*
|
||||
* Tests fuer die Control-Requirement Mapping Visualisierung
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import DependencyMap from '../../components/compliance/charts/DependencyMap'
|
||||
|
||||
// Mock-Daten
|
||||
const mockRequirements = [
|
||||
{
|
||||
id: 'req-1',
|
||||
article: 'Art. 32',
|
||||
title: 'Sicherheit der Verarbeitung',
|
||||
regulation_code: 'GDPR',
|
||||
},
|
||||
{
|
||||
id: 'req-2',
|
||||
article: 'Art. 9',
|
||||
title: 'Risikomanagement',
|
||||
regulation_code: 'AIACT',
|
||||
},
|
||||
{
|
||||
id: 'req-3',
|
||||
article: 'Art. 25',
|
||||
title: 'Datenschutz durch Technikgestaltung',
|
||||
regulation_code: 'GDPR',
|
||||
},
|
||||
]
|
||||
|
||||
const mockControls = [
|
||||
{
|
||||
id: 'ctrl-1',
|
||||
control_id: 'PRIV-001',
|
||||
title: 'Verarbeitungsverzeichnis',
|
||||
domain: 'priv',
|
||||
status: 'pass',
|
||||
},
|
||||
{
|
||||
id: 'ctrl-2',
|
||||
control_id: 'CRYPTO-001',
|
||||
title: 'Verschluesselung',
|
||||
domain: 'crypto',
|
||||
status: 'pass',
|
||||
},
|
||||
{
|
||||
id: 'ctrl-3',
|
||||
control_id: 'AI-001',
|
||||
title: 'KI-Risikobewertung',
|
||||
domain: 'ai',
|
||||
status: 'partial',
|
||||
},
|
||||
]
|
||||
|
||||
const mockMappings = [
|
||||
{ requirement_id: 'req-1', control_id: 'PRIV-001', coverage_level: 'full' as const },
|
||||
{ requirement_id: 'req-1', control_id: 'CRYPTO-001', coverage_level: 'partial' as const },
|
||||
{ requirement_id: 'req-2', control_id: 'AI-001', coverage_level: 'full' as const },
|
||||
{ requirement_id: 'req-3', control_id: 'PRIV-001', coverage_level: 'planned' as const },
|
||||
]
|
||||
|
||||
describe('DependencyMap', () => {
|
||||
it('should render the component with data', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
// Statistik-Header sollte sichtbar sein
|
||||
expect(screen.getByText('Abdeckung')).toBeInTheDocument()
|
||||
expect(screen.getByText('Vollstaendig')).toBeInTheDocument()
|
||||
expect(screen.getByText('Teilweise')).toBeInTheDocument()
|
||||
expect(screen.getByText('Geplant')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render empty state when no data', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={[]}
|
||||
controls={[]}
|
||||
mappings={[]}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText(/Keine Mappings vorhanden/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show correct coverage statistics', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
// 2 full, 1 partial, 1 planned
|
||||
expect(screen.getByText('2')).toBeInTheDocument() // full mappings
|
||||
expect(screen.getByText('1')).toBeInTheDocument() // partial/planned mappings
|
||||
})
|
||||
|
||||
it('should display control IDs in matrix header', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('PRIV-001')).toBeInTheDocument()
|
||||
expect(screen.getByText('CRYPTO-001')).toBeInTheDocument()
|
||||
expect(screen.getByText('AI-001')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display requirement articles', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText(/GDPR Art. 32/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/AIACT Art. 9/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support German language', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
lang="de"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Alle Verordnungen')).toBeInTheDocument()
|
||||
expect(screen.getByText('Alle Domains')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support English language', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
lang="en"
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('All Regulations')).toBeInTheDocument()
|
||||
expect(screen.getByText('All Domains')).toBeInTheDocument()
|
||||
expect(screen.getByText('Coverage')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onControlClick when control is clicked', () => {
|
||||
const onControlClick = vi.fn()
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
onControlClick={onControlClick}
|
||||
/>
|
||||
)
|
||||
|
||||
const controlHeader = screen.getByText('PRIV-001')
|
||||
fireEvent.click(controlHeader)
|
||||
|
||||
expect(onControlClick).toHaveBeenCalledWith(mockControls[0])
|
||||
})
|
||||
|
||||
it('should call onRequirementClick when requirement is clicked', () => {
|
||||
const onRequirementClick = vi.fn()
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
onRequirementClick={onRequirementClick}
|
||||
/>
|
||||
)
|
||||
|
||||
// Finde den Requirement-Text
|
||||
const reqElement = screen.getByText(/Sicherheit der Verarbeitung/i)
|
||||
fireEvent.click(reqElement.closest('div[class*="cursor-pointer"]')!)
|
||||
|
||||
expect(onRequirementClick).toHaveBeenCalledWith(mockRequirements[0])
|
||||
})
|
||||
|
||||
it('should filter by regulation', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
const regSelect = screen.getByDisplayValue('Alle Verordnungen')
|
||||
fireEvent.change(regSelect, { target: { value: 'AIACT' } })
|
||||
|
||||
// Nur AIACT Requirements sollten sichtbar sein
|
||||
expect(screen.queryByText(/GDPR Art. 32/i)).not.toBeInTheDocument()
|
||||
expect(screen.getByText(/AIACT Art. 9/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should filter by domain', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
const domainSelect = screen.getByDisplayValue('Alle Domains')
|
||||
fireEvent.change(domainSelect, { target: { value: 'priv' } })
|
||||
|
||||
// Nur Privacy Controls sollten sichtbar sein
|
||||
expect(screen.getByText('PRIV-001')).toBeInTheDocument()
|
||||
expect(screen.queryByText('CRYPTO-001')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should switch between matrix and connection view', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
// Standard ist Matrix-Ansicht
|
||||
expect(screen.getByText('Matrix')).toBeInTheDocument()
|
||||
|
||||
// Wechsle zu Verbindungs-Ansicht
|
||||
fireEvent.click(screen.getByText('Verbindungen'))
|
||||
|
||||
// Connection-View Text sollte erscheinen
|
||||
expect(screen.getByText(/Waehlen Sie ein Control/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should highlight connected items when control is selected', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
// Klicke auf einen Control
|
||||
fireEvent.click(screen.getByText('PRIV-001'))
|
||||
|
||||
// Der Control sollte highlighted sein (bg-primary-100 Klasse)
|
||||
const controlElement = screen.getByText('PRIV-001').closest('div')
|
||||
expect(controlElement?.className).toContain('bg-primary-100')
|
||||
})
|
||||
|
||||
it('should show legend with coverage levels', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Vollstaendig abgedeckt')).toBeInTheDocument()
|
||||
expect(screen.getByText('Teilweise abgedeckt')).toBeInTheDocument()
|
||||
expect(screen.getByText('Geplant')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should calculate coverage percentage correctly', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
// 3 von 3 Requirements haben mindestens ein Mapping = 100%
|
||||
expect(screen.getByText('100.0%')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show control counts in connection view', () => {
|
||||
render(
|
||||
<DependencyMap
|
||||
requirements={mockRequirements}
|
||||
controls={mockControls}
|
||||
mappings={mockMappings}
|
||||
/>
|
||||
)
|
||||
|
||||
// Wechsle zu Verbindungs-Ansicht
|
||||
fireEvent.click(screen.getByText('Verbindungen'))
|
||||
|
||||
// PRIV-001 hat 2 Verbindungen
|
||||
const privControl = screen.getAllByText('PRIV-001')[0].closest('button')
|
||||
expect(privControl?.textContent).toContain('2')
|
||||
})
|
||||
})
|
||||
173
website/__tests__/compliance/ExpiredEvidenceAlert.test.tsx
Normal file
173
website/__tests__/compliance/ExpiredEvidenceAlert.test.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Tests fuer ExpiredEvidenceAlert Komponente
|
||||
* Sprint 4: Stakeholder-Views & Rollenbasierte Features
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
// Mock next/navigation
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: jest.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock der Komponente (da wir den Source noch importieren muessen)
|
||||
import ExpiredEvidenceAlert, { ExpiredEvidence } from '@/components/compliance/ExpiredEvidenceAlert'
|
||||
|
||||
const mockExpiredEvidence: ExpiredEvidence[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'SAST Scan Report Q4/2025',
|
||||
control_id: 'SDLC-001',
|
||||
control_name: 'SAST Scanning',
|
||||
expired_at: '2025-12-31',
|
||||
days_expired: 18,
|
||||
days_until_expiry: -18,
|
||||
status: 'expired',
|
||||
artifact_type: 'scan_report'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Backup Recovery Test',
|
||||
control_id: 'OPS-002',
|
||||
control_name: 'Backup & Recovery',
|
||||
expired_at: '2026-01-25',
|
||||
days_expired: null,
|
||||
days_until_expiry: 7,
|
||||
status: 'expiring_soon',
|
||||
artifact_type: 'test_result'
|
||||
},
|
||||
]
|
||||
|
||||
describe('ExpiredEvidenceAlert', () => {
|
||||
describe('Banner Variante (default)', () => {
|
||||
it('rendert mit korrekter Ueberschrift fuer abgelaufene Evidence', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="banner" />)
|
||||
|
||||
expect(screen.getByText(/Nachweise erfordern Aufmerksamkeit/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt korrekte Anzahl abgelaufener und ablaufender Items', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="banner" />)
|
||||
|
||||
expect(screen.getByText(/1 abgelaufen/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/1 laufen bald ab/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('kann expandiert werden um Details anzuzeigen', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="banner" />)
|
||||
|
||||
const detailsButton = screen.getByText('Details')
|
||||
fireEvent.click(detailsButton)
|
||||
|
||||
expect(screen.getByText('SAST Scan Report Q4/2025')).toBeInTheDocument()
|
||||
expect(screen.getByText('Backup Recovery Test')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('kann dismissed werden', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="banner" />)
|
||||
|
||||
const dismissButton = screen.getByTitle(/Schliessen/i)
|
||||
fireEvent.click(dismissButton)
|
||||
|
||||
expect(screen.queryByText(/Nachweise erfordern Aufmerksamkeit/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt nichts an wenn keine Evidence vorhanden', () => {
|
||||
const { container } = render(<ExpiredEvidenceAlert evidenceList={[]} variant="banner" />)
|
||||
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Card Variante', () => {
|
||||
it('rendert Kompakt-Karte mit Header', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="card" />)
|
||||
|
||||
expect(screen.getByText('Nachweis-Warnungen')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt maxItems Anzahl von Items', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="card" maxItems={1} />)
|
||||
|
||||
expect(screen.getByText('SAST Scan Report Q4/2025')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Backup Recovery Test')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt "Alle anzeigen" Link wenn mehr Items als maxItems', () => {
|
||||
const manyItems = [...mockExpiredEvidence, ...mockExpiredEvidence]
|
||||
render(<ExpiredEvidenceAlert evidenceList={manyItems} variant="card" maxItems={2} />)
|
||||
|
||||
expect(screen.getByText(/Alle \d+ anzeigen/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Minimal Variante', () => {
|
||||
it('rendert nur Icon mit Badge', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="minimal" />)
|
||||
|
||||
// Badge sollte Gesamtzahl zeigen
|
||||
expect(screen.getByText('2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt korrekten Tooltip-Text', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="minimal" language="de" />)
|
||||
|
||||
const button = screen.getByRole('button')
|
||||
expect(button).toHaveAttribute('title', '1 abgelaufen, 1 laufen bald ab')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sprachumschaltung', () => {
|
||||
it('zeigt deutsche Texte bei language=de', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="banner" language="de" />)
|
||||
|
||||
expect(screen.getByText(/abgelaufen/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('Nachweise verwalten')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt englische Texte bei language=en', () => {
|
||||
render(<ExpiredEvidenceAlert evidenceList={mockExpiredEvidence} variant="banner" language="en" />)
|
||||
|
||||
expect(screen.getByText(/expired/i)).toBeInTheDocument()
|
||||
expect(screen.getByText('Manage Evidence')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Callbacks', () => {
|
||||
it('ruft onViewAll Callback auf', () => {
|
||||
const mockOnViewAll = jest.fn()
|
||||
render(
|
||||
<ExpiredEvidenceAlert
|
||||
evidenceList={mockExpiredEvidence}
|
||||
variant="banner"
|
||||
onViewAll={mockOnViewAll}
|
||||
/>
|
||||
)
|
||||
|
||||
const manageButton = screen.getByText('Nachweise verwalten')
|
||||
fireEvent.click(manageButton)
|
||||
|
||||
expect(mockOnViewAll).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('ruft onItemClick Callback auf', () => {
|
||||
const mockOnItemClick = jest.fn()
|
||||
render(
|
||||
<ExpiredEvidenceAlert
|
||||
evidenceList={mockExpiredEvidence}
|
||||
variant="card"
|
||||
onItemClick={mockOnItemClick}
|
||||
/>
|
||||
)
|
||||
|
||||
const firstItem = screen.getByText('SAST Scan Report Q4/2025')
|
||||
fireEvent.click(firstItem)
|
||||
|
||||
expect(mockOnItemClick).toHaveBeenCalledWith(mockExpiredEvidence[0])
|
||||
})
|
||||
})
|
||||
})
|
||||
234
website/__tests__/compliance/MyTasksPage.test.tsx
Normal file
234
website/__tests__/compliance/MyTasksPage.test.tsx
Normal file
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Tests fuer My-Tasks Dashboard
|
||||
* Sprint 4: Stakeholder-Views & Rollenbasierte Features
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
// Mock next/navigation
|
||||
const mockPush = jest.fn()
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockPush,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = {
|
||||
getItem: jest.fn(),
|
||||
setItem: jest.fn(),
|
||||
removeItem: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
}
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock })
|
||||
|
||||
// Import nach Mocks
|
||||
import MyTasksPage from '@/app/admin/compliance/my-tasks/page'
|
||||
|
||||
describe('MyTasksPage', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('rendert Seitentitel', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
expect(screen.getByText(/Meine Aufgaben|My Tasks/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Statistik-Karten', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// Statistik-Kategorien sollten sichtbar sein
|
||||
expect(screen.getByText(/Offen|Open/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/In Bearbeitung|In Progress/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Heute fällig|Due Today/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Überfällig|Overdue/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Task-Liste mit Mock-Daten', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// Mindestens ein Task sollte sichtbar sein (Mock-Daten)
|
||||
expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Filter-Funktionalität', () => {
|
||||
it('zeigt Status-Filter Dropdown', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const statusFilter = screen.getByRole('combobox', { name: /status/i })
|
||||
expect(statusFilter).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Typ-Filter Dropdown', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const typeFilter = screen.getByRole('combobox', { name: /typ|type/i })
|
||||
expect(typeFilter).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Prioritaet-Filter Dropdown', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const priorityFilter = screen.getByRole('combobox', { name: /priorität|priority/i })
|
||||
expect(priorityFilter).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('filtert Tasks nach Status', async () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const statusFilter = screen.getByRole('combobox', { name: /status/i })
|
||||
fireEvent.change(statusFilter, { target: { value: 'pending' } })
|
||||
|
||||
// Nach Filter-Aenderung sollten nur pending Tasks sichtbar sein
|
||||
await waitFor(() => {
|
||||
const tasks = screen.getAllByRole('listitem')
|
||||
// Alle sichtbaren Tasks sollten pending Status haben
|
||||
tasks.forEach(task => {
|
||||
expect(task).not.toHaveTextContent(/Abgeschlossen|Completed/i)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sortierung', () => {
|
||||
it('zeigt Sortier-Optionen', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const sortSelect = screen.getByRole('combobox', { name: /sortieren|sort/i })
|
||||
expect(sortSelect).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('kann nach Faelligkeit sortieren', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const sortSelect = screen.getByRole('combobox', { name: /sortieren|sort/i })
|
||||
fireEvent.change(sortSelect, { target: { value: 'due_date' } })
|
||||
|
||||
// Erste Task sollte die frueheste Faelligkeit haben
|
||||
// (Implementierung haengt von Mock-Daten ab)
|
||||
})
|
||||
|
||||
it('kann nach Prioritaet sortieren', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const sortSelect = screen.getByRole('combobox', { name: /sortieren|sort/i })
|
||||
fireEvent.change(sortSelect, { target: { value: 'priority' } })
|
||||
|
||||
// Erste Task sollte hoechste Prioritaet haben
|
||||
})
|
||||
})
|
||||
|
||||
describe('Task-Typen', () => {
|
||||
it('zeigt control_review Tasks', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// control_review Tasks sollten in der Liste sein
|
||||
expect(screen.queryByText(/Control|Kontrolle/i)).toBeDefined()
|
||||
})
|
||||
|
||||
it('zeigt evidence_upload Tasks', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// evidence_upload Tasks sollten in der Liste sein
|
||||
expect(screen.queryByText(/Evidence|Nachweis/i)).toBeDefined()
|
||||
})
|
||||
|
||||
it('zeigt signoff Tasks', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// signoff Tasks sollten in der Liste sein
|
||||
expect(screen.queryByText(/Sign-off|Freigabe/i)).toBeDefined()
|
||||
})
|
||||
|
||||
it('zeigt risk_treatment Tasks', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// risk_treatment Tasks sollten in der Liste sein
|
||||
expect(screen.queryByText(/Risiko|Risk/i)).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Task-Klick', () => {
|
||||
it('navigiert zum entsprechenden Control bei control_review', async () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// Finde einen control_review Task und klicke
|
||||
const controlTask = screen.getAllByRole('button').find(btn =>
|
||||
btn.textContent?.includes('Control') || btn.textContent?.includes('Kontrolle')
|
||||
)
|
||||
|
||||
if (controlTask) {
|
||||
fireEvent.click(controlTask)
|
||||
expect(mockPush).toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
|
||||
it('navigiert zur Evidence-Seite bei evidence_upload', async () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
const evidenceTask = screen.getAllByRole('button').find(btn =>
|
||||
btn.textContent?.includes('Evidence') || btn.textContent?.includes('Nachweis')
|
||||
)
|
||||
|
||||
if (evidenceTask) {
|
||||
fireEvent.click(evidenceTask)
|
||||
expect(mockPush).toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Prioritaets-Darstellung', () => {
|
||||
it('zeigt critical Tasks mit roter Markierung', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// Critical Tasks sollten rote Markierung haben
|
||||
const criticalBadges = document.querySelectorAll('.bg-red-500, .text-red-500, .border-red-500')
|
||||
// Mindestens ein critical Task sollte vorhanden sein (aus Mock-Daten)
|
||||
})
|
||||
|
||||
it('zeigt high Tasks mit oranger Markierung', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// High Tasks sollten orange Markierung haben
|
||||
const highBadges = document.querySelectorAll('.bg-orange-500, .text-orange-500, .border-orange-500')
|
||||
})
|
||||
|
||||
it('zeigt medium Tasks mit gelber Markierung', () => {
|
||||
render(<MyTasksPage />)
|
||||
|
||||
// Medium Tasks sollten gelbe Markierung haben
|
||||
const mediumBadges = document.querySelectorAll('.bg-yellow-500, .text-yellow-500, .border-yellow-500')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sprachumschaltung', () => {
|
||||
it('zeigt deutschen Text als Standard', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
render(<MyTasksPage />)
|
||||
|
||||
expect(screen.getByText(/Meine Aufgaben/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt englischen Text bei en-Einstellung', () => {
|
||||
localStorageMock.getItem.mockReturnValue('en')
|
||||
render(<MyTasksPage />)
|
||||
|
||||
expect(screen.getByText(/My Tasks/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Leere-State', () => {
|
||||
it('zeigt Hinweis wenn keine Tasks vorhanden', () => {
|
||||
// Mock: Keine Tasks
|
||||
// render mit leerem Task-Array
|
||||
// expect(screen.getByText(/Keine Aufgaben|No tasks/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
261
website/__tests__/compliance/RiskHeatmap.test.tsx
Normal file
261
website/__tests__/compliance/RiskHeatmap.test.tsx
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* RiskHeatmap Component Tests
|
||||
*
|
||||
* Tests fuer die erweiterte Risiko-Matrix-Visualisierung
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import RiskHeatmap, {
|
||||
calculateRiskLevel,
|
||||
MiniRiskMatrix,
|
||||
RiskDistribution,
|
||||
type Risk,
|
||||
type Control,
|
||||
} from '../../components/compliance/charts/RiskHeatmap'
|
||||
|
||||
// Mock-Daten
|
||||
const mockRisks: Risk[] = [
|
||||
{
|
||||
id: '1',
|
||||
risk_id: 'RISK-001',
|
||||
title: 'Data Breach Risk',
|
||||
description: 'Risiko eines Datenverlusts',
|
||||
category: 'data_breach',
|
||||
likelihood: 4,
|
||||
impact: 5,
|
||||
inherent_risk: 'critical',
|
||||
mitigating_controls: ['PRIV-001', 'CRYPTO-001'],
|
||||
residual_likelihood: 2,
|
||||
residual_impact: 3,
|
||||
residual_risk: 'medium',
|
||||
owner: 'Security Team',
|
||||
status: 'open',
|
||||
treatment_plan: 'Encryption implementieren',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
risk_id: 'RISK-002',
|
||||
title: 'Compliance Gap',
|
||||
category: 'compliance_gap',
|
||||
likelihood: 3,
|
||||
impact: 3,
|
||||
inherent_risk: 'medium',
|
||||
status: 'mitigated',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
risk_id: 'RISK-003',
|
||||
title: 'Vendor Risk',
|
||||
category: 'vendor_risk',
|
||||
likelihood: 2,
|
||||
impact: 2,
|
||||
inherent_risk: 'low',
|
||||
status: 'accepted',
|
||||
},
|
||||
]
|
||||
|
||||
const mockControls: Control[] = [
|
||||
{
|
||||
id: 'c1',
|
||||
control_id: 'PRIV-001',
|
||||
title: 'Datenschutz-Massnahme',
|
||||
domain: 'priv',
|
||||
status: 'implemented',
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
control_id: 'CRYPTO-001',
|
||||
title: 'Verschluesselung',
|
||||
domain: 'crypto',
|
||||
status: 'implemented',
|
||||
},
|
||||
]
|
||||
|
||||
describe('calculateRiskLevel', () => {
|
||||
it('should return low for score 1-5', () => {
|
||||
expect(calculateRiskLevel(1, 1)).toBe('low')
|
||||
expect(calculateRiskLevel(1, 5)).toBe('low')
|
||||
expect(calculateRiskLevel(5, 1)).toBe('low')
|
||||
})
|
||||
|
||||
it('should return medium for score 6-11', () => {
|
||||
expect(calculateRiskLevel(2, 3)).toBe('medium')
|
||||
expect(calculateRiskLevel(3, 3)).toBe('medium')
|
||||
expect(calculateRiskLevel(2, 5)).toBe('medium')
|
||||
})
|
||||
|
||||
it('should return high for score 12-19', () => {
|
||||
expect(calculateRiskLevel(3, 4)).toBe('high')
|
||||
expect(calculateRiskLevel(4, 4)).toBe('high')
|
||||
expect(calculateRiskLevel(3, 5)).toBe('high')
|
||||
})
|
||||
|
||||
it('should return critical for score 20-25', () => {
|
||||
expect(calculateRiskLevel(4, 5)).toBe('critical')
|
||||
expect(calculateRiskLevel(5, 4)).toBe('critical')
|
||||
expect(calculateRiskLevel(5, 5)).toBe('critical')
|
||||
})
|
||||
})
|
||||
|
||||
describe('RiskHeatmap', () => {
|
||||
it('should render the component with risks', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} />)
|
||||
|
||||
// Statistik-Header sollte sichtbar sein
|
||||
expect(screen.getByText('3')).toBeInTheDocument() // Gesamt
|
||||
expect(screen.getByText('Critical')).toBeInTheDocument()
|
||||
expect(screen.getByText('Medium')).toBeInTheDocument()
|
||||
expect(screen.getByText('Low')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render empty state when no risks', () => {
|
||||
render(<RiskHeatmap risks={[]} />)
|
||||
|
||||
expect(screen.getByText(/Keine Risiken vorhanden/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display risk badges in cells', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} />)
|
||||
|
||||
// Risk-IDs sollten als Badges angezeigt werden
|
||||
expect(screen.getByText('R001')).toBeInTheDocument()
|
||||
expect(screen.getByText('R002')).toBeInTheDocument()
|
||||
expect(screen.getByText('R003')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support German language', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} lang="de" />)
|
||||
|
||||
expect(screen.getByText('Gesamt')).toBeInTheDocument()
|
||||
expect(screen.getByText('Alle Kategorien')).toBeInTheDocument()
|
||||
expect(screen.getByText('Alle Status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support English language', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} lang="en" />)
|
||||
|
||||
expect(screen.getByText('Total')).toBeInTheDocument()
|
||||
expect(screen.getByText('All Categories')).toBeInTheDocument()
|
||||
expect(screen.getByText('All Status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onRiskClick when risk is clicked', () => {
|
||||
const onRiskClick = vi.fn()
|
||||
render(<RiskHeatmap risks={mockRisks} onRiskClick={onRiskClick} />)
|
||||
|
||||
const riskBadge = screen.getByText('R001')
|
||||
fireEvent.click(riskBadge)
|
||||
|
||||
expect(onRiskClick).toHaveBeenCalledWith(mockRisks[0])
|
||||
})
|
||||
|
||||
it('should filter by category', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} />)
|
||||
|
||||
const categorySelect = screen.getByDisplayValue('Alle Kategorien')
|
||||
fireEvent.change(categorySelect, { target: { value: 'data_breach' } })
|
||||
|
||||
// Nur RISK-001 sollte sichtbar sein
|
||||
expect(screen.getByText('R001')).toBeInTheDocument()
|
||||
expect(screen.queryByText('R002')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should filter by status', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} />)
|
||||
|
||||
const statusSelect = screen.getByDisplayValue('Alle Status')
|
||||
fireEvent.change(statusSelect, { target: { value: 'mitigated' } })
|
||||
|
||||
// Nur RISK-002 sollte sichtbar sein
|
||||
expect(screen.queryByText('R001')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('R002')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show comparison view when enabled', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} showComparison={true} />)
|
||||
|
||||
// View-Mode Buttons sollten sichtbar sein
|
||||
expect(screen.getByText('Vergleich')).toBeInTheDocument()
|
||||
expect(screen.getByText('Residual')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display linked controls for risk', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} controls={mockControls} />)
|
||||
|
||||
// Klicke auf das erste Risiko
|
||||
const riskBadge = screen.getByText('R001')
|
||||
fireEvent.click(riskBadge)
|
||||
|
||||
// Controls sollten in den Details erscheinen
|
||||
expect(screen.getByText('PRIV-001')).toBeInTheDocument()
|
||||
expect(screen.getByText('CRYPTO-001')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show risk movement summary in comparison mode', () => {
|
||||
render(<RiskHeatmap risks={mockRisks} showComparison={true} />)
|
||||
|
||||
// Wechsle in Vergleichsmodus
|
||||
fireEvent.click(screen.getByText('Vergleich'))
|
||||
|
||||
// Risikoveraenderung sollte angezeigt werden
|
||||
expect(screen.getByText(/Critical reduziert|Critical reduced/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('MiniRiskMatrix', () => {
|
||||
it('should render a compact matrix', () => {
|
||||
const { container } = render(<MiniRiskMatrix risks={mockRisks} size="sm" />)
|
||||
|
||||
// 5x5 Grid = 25 Zellen
|
||||
const cells = container.querySelectorAll('.w-6.h-6')
|
||||
expect(cells.length).toBe(25)
|
||||
})
|
||||
|
||||
it('should show risk counts in cells', () => {
|
||||
render(<MiniRiskMatrix risks={mockRisks} />)
|
||||
|
||||
// RISK-001 bei L4/I5 = 1
|
||||
// RISK-002 bei L3/I3 = 1
|
||||
// RISK-003 bei L2/I2 = 1
|
||||
const countElements = screen.getAllByText('1')
|
||||
expect(countElements.length).toBeGreaterThanOrEqual(3)
|
||||
})
|
||||
|
||||
it('should support different sizes', () => {
|
||||
const { container, rerender } = render(<MiniRiskMatrix risks={mockRisks} size="sm" />)
|
||||
expect(container.querySelector('.w-6')).toBeInTheDocument()
|
||||
|
||||
rerender(<MiniRiskMatrix risks={mockRisks} size="md" />)
|
||||
expect(container.querySelector('.w-8')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('RiskDistribution', () => {
|
||||
it('should render distribution bars', () => {
|
||||
render(<RiskDistribution risks={mockRisks} />)
|
||||
|
||||
expect(screen.getByText('critical')).toBeInTheDocument()
|
||||
expect(screen.getByText('high')).toBeInTheDocument()
|
||||
expect(screen.getByText('medium')).toBeInTheDocument()
|
||||
expect(screen.getByText('low')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show correct counts', () => {
|
||||
render(<RiskDistribution risks={mockRisks} />)
|
||||
|
||||
// 1 critical, 0 high, 1 medium, 1 low
|
||||
const counts = screen.getAllByText('1')
|
||||
expect(counts.length).toBe(3) // critical, medium, low
|
||||
|
||||
// 0 high
|
||||
expect(screen.getByText('0')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support language prop', () => {
|
||||
render(<RiskDistribution risks={mockRisks} lang="en" />)
|
||||
|
||||
// Englische Bezeichnungen werden verwendet
|
||||
expect(screen.getByText('critical')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
164
website/__tests__/compliance/RoleSelectPage.test.tsx
Normal file
164
website/__tests__/compliance/RoleSelectPage.test.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Tests fuer Role-Select Landing Page
|
||||
* Sprint 4: Stakeholder-Views & Rollenbasierte Features
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import '@testing-library/jest-dom'
|
||||
|
||||
// Mock next/navigation
|
||||
const mockPush = jest.fn()
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockPush,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = {
|
||||
getItem: jest.fn(),
|
||||
setItem: jest.fn(),
|
||||
removeItem: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
}
|
||||
Object.defineProperty(window, 'localStorage', { value: localStorageMock })
|
||||
|
||||
// Import nach Mocks
|
||||
import RoleSelectPage from '@/app/admin/compliance/role-select/page'
|
||||
|
||||
describe('RoleSelectPage', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('rendert alle 4 Rollen-Karten', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText('Executive')).toBeInTheDocument()
|
||||
expect(screen.getByText('Auditor')).toBeInTheDocument()
|
||||
expect(screen.getByText('Compliance Officer')).toBeInTheDocument()
|
||||
expect(screen.getByText(/Entwickler|Developer/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('rendert Seitentitel', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText(/Compliance Portal/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Quick-Access Links', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText(/Controls/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Evidence/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Risks|Risiken/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Rollen-Karten Klick', () => {
|
||||
it('navigiert zu Executive Dashboard bei Klick auf Executive', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
const executiveCard = screen.getByText('Executive').closest('button')
|
||||
fireEvent.click(executiveCard!)
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith('/admin/compliance?tab=executive')
|
||||
})
|
||||
|
||||
it('navigiert zu Audit Workspace bei Klick auf Auditor', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
const auditorCard = screen.getByText('Auditor').closest('button')
|
||||
fireEvent.click(auditorCard!)
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith('/admin/compliance/audit-workspace')
|
||||
})
|
||||
|
||||
it('navigiert zum Compliance Dashboard bei Klick auf Compliance Officer', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
const complianceCard = screen.getByText('Compliance Officer').closest('button')
|
||||
fireEvent.click(complianceCard!)
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith('/admin/compliance')
|
||||
})
|
||||
|
||||
it('navigiert zu technischer Ansicht bei Klick auf Developer', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
const developerCard = screen.getByText(/Entwickler|Developer/).closest('button')
|
||||
fireEvent.click(developerCard!)
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith('/admin/compliance?tab=technisch')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sprachumschaltung', () => {
|
||||
it('zeigt deutschen Text als Standard', () => {
|
||||
localStorageMock.getItem.mockReturnValue(null)
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
// Sollte standardmaessig Deutsch sein
|
||||
expect(screen.getByText(/Wählen Sie Ihre Ansicht|Waehlen Sie Ihre Ansicht/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('speichert Sprachpräferenz in localStorage', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
// Finde Language Toggle und klicke
|
||||
const langToggle = screen.getByRole('button', { name: /EN/i })
|
||||
fireEvent.click(langToggle)
|
||||
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith('compliance-language', 'en')
|
||||
})
|
||||
|
||||
it('laedt Sprachpräferenz aus localStorage', () => {
|
||||
localStorageMock.getItem.mockReturnValue('en')
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText('Choose your view')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Rollenbeschreibungen', () => {
|
||||
it('zeigt Executive Beschreibung', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText(/Ampel-Status|Traffic Light/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Top-Risiken|Top Risks/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Auditor Beschreibung', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText(/Checkliste|Checklist/i)).toBeInTheDocument()
|
||||
expect(screen.getByText(/Sign-off/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Compliance Officer Beschreibung', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText(/Workflows/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('zeigt Developer Beschreibung', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
expect(screen.getByText(/Code-Referenzen|Code Refs/i)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Praeferenz-Speicherung', () => {
|
||||
it('speichert letzte Rollenauswahl', () => {
|
||||
render(<RoleSelectPage />)
|
||||
|
||||
const executiveCard = screen.getByText('Executive').closest('button')
|
||||
fireEvent.click(executiveCard!)
|
||||
|
||||
expect(localStorageMock.setItem).toHaveBeenCalledWith('compliance-last-role', 'executive')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user