diff --git a/frontend/src/pages/Findings.tsx b/frontend/src/pages/Findings.tsx index fe65c327..96d79e68 100644 --- a/frontend/src/pages/Findings.tsx +++ b/frontend/src/pages/Findings.tsx @@ -468,7 +468,21 @@ export default function Findings() { setCopiedFindingId(null) } } + async function copyFindingId(findingId: string) { + try { + await navigator.clipboard.writeText(findingId) + setCopiedFindingId(findingId) + + window.setTimeout(() => { + setCopiedFindingId((current) => + current === findingId ? null : current + ) + }, 1600) + } catch { + setCopiedFindingId(null) + } +} // ─── Keyboard navigation ──────────────────────────────────────────────────── const listRef = useRef(null) @@ -940,9 +954,20 @@ export default function Findings() {
-

Selected Finding

-

{selectedFinding.title}

-
+

+ Selected Finding +

+ +

+ {selectedFinding.title} +

+ +
+ + ID: {selectedFinding.id} + +
+
@@ -1133,6 +1158,13 @@ export default function Findings() { > {copiedFindingId === selectedFinding.id ? 'Copied' : 'Copy Brief'} +
diff --git a/frontend/testing/unit/pages/Findings.test.tsx b/frontend/testing/unit/pages/Findings.test.tsx index 5a23d8c8..28df3d6f 100644 --- a/frontend/testing/unit/pages/Findings.test.tsx +++ b/frontend/testing/unit/pages/Findings.test.tsx @@ -231,6 +231,42 @@ describe('Findings — virtualized list', () => { }) }) + it('copies finding id and shows success feedback', async () => { + const findings = [makeFinding({ id: 'f1', title: 'ID Copy Test' })] + vi.mocked(getFindings).mockResolvedValue({ findings }) + Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: { writeText: vi.fn().mockResolvedValue(undefined) }, + }) + + render() + await waitFor(() => expect(screen.queryByText('Synchronizing findings feed...')).not.toBeInTheDocument()) + + const copyButton = screen.getByRole('button', { name: /Copy ID/i }) + await userEvent.click(copyButton) + + await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith('f1')) + expect(copyButton).toHaveTextContent('Copied') + }) + + it('keeps copy state idle when finding id copy fails', async () => { + const findings = [makeFinding({ id: 'f2', title: 'ID Copy Failure Test' })] + vi.mocked(getFindings).mockResolvedValue({ findings }) + Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: { writeText: vi.fn().mockRejectedValue(new Error('clipboard unavailable')) }, + }) + + render() + await waitFor(() => expect(screen.queryByText('Synchronizing findings feed...')).not.toBeInTheDocument()) + + const copyButton = screen.getByRole('button', { name: /Copy ID/i }) + await userEvent.click(copyButton) + + await waitFor(() => expect(navigator.clipboard.writeText).toHaveBeenCalledWith('f2')) + expect(copyButton).toHaveTextContent('Copy ID') + }) + it('persists review state to localStorage', async () => { const findings = [makeFinding({ id: 'f1', title: 'Persist Test', severity: 'high' })] vi.mocked(getFindings).mockResolvedValue({ findings })