e8ea179228
Adds case management to the Compliance Advisor widget. - topic threads: cases group into threads; the left menu shows each thread's first question as the Thema with expandable follow-ups. Send = follow-up to the active thread (carries the thread's prior Q&A as history for contextual answers); "+" starts a new topic. - delete: a trash action per question (menu + stacked view). - copy: single Q&A (question + answer + evidence + footnotes) or a whole thread, as Markdown to the clipboard (pure formatters in copy.ts). - fullscreen: compact -> panel -> fullscreen view. - route.ts consumes an optional bounded `history` so follow-ups are contextual for both the widget and the workspace consumer. Tests: copy formatter unit tests + Playwright specs (threads/new-topic, delete, fullscreen, copy affordance). No deploy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
69 lines
2.6 KiB
TypeScript
69 lines
2.6 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, fireEvent } from '@testing-library/react'
|
|
import { CaseView } from './CaseView'
|
|
import type { AdvisorCase } from './useAdvisorCase'
|
|
import type { AdvisorResponse } from '@/lib/sdk/advisor/contract'
|
|
|
|
const clarify: AdvisorResponse = {
|
|
mode: 'clarify',
|
|
question: 'Was ist PDCA?',
|
|
clarity: {
|
|
is_underspecified: true,
|
|
concentration: 0.38,
|
|
suggested_contexts: [
|
|
{ id: 'datenschutz', label: 'Datenschutz' },
|
|
{ id: 'qm', label: 'Qualitätsmanagement' },
|
|
],
|
|
},
|
|
general_answer: 'PDCA steht für **Plan-Do-Check-Act**.',
|
|
answer: null,
|
|
evidence: [],
|
|
citations: [],
|
|
visual_evidence: [],
|
|
footnotes: [],
|
|
}
|
|
|
|
const answer: AdvisorResponse = {
|
|
mode: 'answer',
|
|
question: 'PDCA im Datenschutz?',
|
|
clarity: { is_underspecified: false, dominant_context: 'datenschutz', concentration: 0.88 },
|
|
answer: 'Der DSM-Zyklus [1] beschreibt den Ablauf.',
|
|
evidence: [
|
|
{ evidence_id: 'e1', document: 'DSK Sdm B41', section: 'Art. 5', paragraph: 'Abs. 2', snippet: 'x' },
|
|
],
|
|
citations: [
|
|
{ citation_id: 'c1', evidence_id: 'e1', document: 'DSK Sdm B41', section: 'Art. 5', paragraph: 'Abs. 2' },
|
|
],
|
|
visual_evidence: [
|
|
{ visual_id: 'v1', visual_type: 'flowchart', caption: 'PDCA-Zyklus', document: 'DSK SDM', vision_summary: 's' },
|
|
],
|
|
footnotes: [],
|
|
}
|
|
|
|
function mk(response: AdvisorResponse): AdvisorCase {
|
|
return { id: 'case1', threadId: 'thread1', question: response.question, response, selectedContext: null, status: 'done' }
|
|
}
|
|
|
|
describe('CaseView — clarify mode', () => {
|
|
it('renders the L1 general answer + context chips and fires onSelectContext', () => {
|
|
const onSel = vi.fn()
|
|
const { container, getByText } = render(
|
|
<CaseView c={mk(clarify)} busy={false} onSelectContext={onSel} />,
|
|
)
|
|
expect(container.textContent).toContain('Plan-Do-Check-Act')
|
|
expect(container.textContent).toContain('Allgemeine Definition')
|
|
fireEvent.click(getByText('Datenschutz'))
|
|
expect(onSel).toHaveBeenCalledWith('datenschutz')
|
|
})
|
|
})
|
|
|
|
describe('CaseView — answer mode', () => {
|
|
it('renders answer with a clickable [n] citation, grouped evidence (friendly name), and visual', () => {
|
|
const { container } = render(<CaseView c={mk(answer)} busy={false} onSelectContext={() => {}} />)
|
|
expect(container.textContent).toContain('DSM-Zyklus')
|
|
expect(container.querySelector('button[title="Beleg 1 anzeigen"]')).not.toBeNull()
|
|
expect(container.textContent).toContain('DSK Standard-Datenschutzmodell')
|
|
expect(container.textContent).toContain('PDCA-Zyklus')
|
|
})
|
|
})
|