// @vitest-environment jsdom import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it } from 'vitest' import type { ScanReport } from './api' import { FindingsView } from './components/FindingsView' const report: ScanReport = { id: 'scan-1', repository_path: '/scan/repopulse', repository_name: 'RepoPulse', source: { origin: 'gitea', provider: 'gitea', repository_id: '64', repository_full_name: 'Jens/RepoPulse', branch: 'main', requested_revision: 'revision-1', actual_revision: 'revision-1', trigger: 'scheduled', }, policy: { auto_scan: true, min_health_score: 70, max_blockers: 0, require_tests: true, require_ci: true, suppressions: [], }, scanned_at: '2026-09-12T20:00:00Z', duration_ms: 1200, metrics: { files: 2, source_files: 2, lines_of_code: 20, test_files: 1, todo: 0, fixme: 0, hack: 0, xxx: 0, dependency_manifests: 1, declared_dependencies: 1, large_files: 0, large_source_files: 0, }, languages: { TypeScript: 20 }, category_scores: { 'Security basics': 20, Maintainability: 80 }, health_score: 70, release_readiness: 'BLOCKED', findings: [ { code: 'security.secret-assignment', category: 'Security basics', severity: 'blocker', message: 'Potential credential assignment detected; value intentionally redacted', path: 'backend/app/main.py', line: 245, deduction: 20, fingerprint: 'fingerprint-1', suppressed: false, }, { code: 'maintainability.long-function', category: 'Maintainability', severity: 'warning', message: 'Function is longer than the configured threshold', path: 'backend/app/signals.py', line: 111, deduction: 4, fingerprint: 'fingerprint-2', suppressed: false, }, ], tests: { frameworks: ['pytest'], coverage_artifacts: [], ci_workflows: [], test_source_ratio: null }, git: { available: true, commit_count: 1, contributor_count: 1, latest_commit_at: null, tracked_files: 2, renamed_files: [] }, dependencies: { manifests: ['package.json'], lockfiles: ['package-lock.json'], declared_dependencies: 1, unbounded_dependencies: 0, unpinned_dependencies: 0, duplicate_dependencies: [] }, architecture: { nodes: [], edges: [], clusters: {}, truncated: false, total_nodes: 0, total_edges: 0 }, hotspots: [], release_gate: null, } afterEach(() => cleanup()) describe('FindingsView', () => { it('keeps findings scanable while exposing expandable rule details', () => { const { container } = render() const firstFinding = container.querySelector('details.finding') as HTMLDetailsElement expect(firstFinding.open).toBe(false) expect(screen.getByText('Potential credential assignment detected; value intentionally redacted')).toBeTruthy() fireEvent.click(screen.getByText('Potential credential assignment detected; value intentionally redacted')) expect(firstFinding.open).toBe(true) expect(screen.getByText('−20 points')).toBeTruthy() expect(screen.getByText('backend/app/main.py:245')).toBeTruthy() expect(screen.getByText('security.secret-assignment')).toBeTruthy() }) it('filters by severity and reports an actionable empty state', () => { render() const severity = screen.getByRole('combobox', { name: 'Filter findings by severity' }) const query = screen.getByRole('textbox', { name: 'Filter findings' }) fireEvent.change(severity, { target: { value: 'warning' } }) expect(screen.getByText(/Showing 1 of 2/)).toBeTruthy() fireEvent.change(query, { target: { value: 'does-not-exist' } }) expect(screen.getByText('No findings match the current filters.')).toBeTruthy() }) })