锁定 Phase 4 读取接缝收敛后的纯渲染与关键交互契约;五个组件 此前从未被 vitest 覆盖,现全量补齐(25 个测试)。 - DecisionProcess(6):加载/空态/详情展开/上下文 JSON 折叠、 轮询门控(executing 轮询、completed 停轮询)、补充日志触发。 - EvalOverview(6):planning / pending_approval / executing 三种状态的渲染分支;plan 维度/虚拟用户/时间分布/完成标准; 会话进度与过期文案。 - EvalReport(4):报告加载/空态、会话折叠、SessionMessages 懒加载、Markdown 导出(downloadBlob 接缝)。 - ConfigSnapshots(4):列表与类型标签、详情展开、快照对比 两选一校验、空态与差异渲染。 - TaskQueueMonitor(5):统计芯片、状态过滤、刷新按钮、 轮询定时器(vi.useFakeTimers + advanceTimersByTimeAsync)、 空态。 测试沿用 sessionReducer 先例:vi.mock 工厂注入 readSlot / API 方法,按 reducer 形状构造 fixture;antd 双汉字按钮 (详情/导出/查看)使用 /\s*/ 正则匹配 autoInsertSpace 空格。
140 lines
4.8 KiB
TypeScript
140 lines
4.8 KiB
TypeScript
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||
import type { IntelligentEval, IntelligentEvalReport } from '../../api'
|
||
import EvalReport from './EvalReport'
|
||
|
||
const downloadReportMarkdown = vi.fn()
|
||
const listMessages = vi.fn()
|
||
|
||
vi.mock('../../api', () => ({
|
||
intelligentEvalsApi: {
|
||
downloadReportMarkdown: (...args: unknown[]) => downloadReportMarkdown(...args),
|
||
listMessages: (...args: unknown[]) => listMessages(...args),
|
||
},
|
||
}))
|
||
|
||
function makeReport(overrides: Partial<IntelligentEvalReport> = {}): IntelligentEvalReport {
|
||
return {
|
||
summary: '整体服务态度良好,价保场景存在流程遗漏',
|
||
scores: { overall: 4.2, dimensions: { 服务态度: 4.5, 专业度: 3.2 } },
|
||
findings: [
|
||
{
|
||
issue: '价保流程未主动告知凭证',
|
||
severity: 'high',
|
||
dimension: '专业度',
|
||
evidence: [{ session_id: 'sess-aaaaaaaa', turn_index: 2, user_said: '需要什么凭证', assistant_replied: '您好' }],
|
||
suggestion: '补充 SOP 提示',
|
||
related_sop: 'SOP-12',
|
||
},
|
||
{
|
||
issue: '回复偏慢',
|
||
severity: 'medium',
|
||
dimension: '响应时效',
|
||
evidence: [],
|
||
},
|
||
],
|
||
highlights: [{ description: '语气友好', dimension: '服务态度' }],
|
||
priority_recommendations: ['优先整改价保流程'],
|
||
...overrides,
|
||
}
|
||
}
|
||
|
||
function makeEval(overrides: Partial<IntelligentEval> = {}): IntelligentEval {
|
||
return {
|
||
id: 'eval-1',
|
||
name: '24h 评估',
|
||
target_id: 't-1',
|
||
status: 'completed',
|
||
goal: 'goal',
|
||
seeds: {},
|
||
intent: '',
|
||
role_description: '',
|
||
plan: null,
|
||
plan_feedback: null,
|
||
time_window_hours: 24,
|
||
report: makeReport(),
|
||
created_at: null,
|
||
updated_at: null,
|
||
started_at: null,
|
||
completed_at: '2026-01-02T00:00:00Z',
|
||
session_count: 1,
|
||
completed_sessions: 1,
|
||
sessions: [
|
||
{
|
||
id: 's-1',
|
||
eval_id: 'eval-1',
|
||
target_id: 't-1',
|
||
persona: { name: '老客户' },
|
||
goal: '完成退货',
|
||
dimension: '售后',
|
||
status: 'completed',
|
||
verdict: null,
|
||
turn_count: 5,
|
||
created_at: '2026-01-01T08:10:00Z',
|
||
closed_at: '2026-01-01T08:25:00Z',
|
||
},
|
||
],
|
||
...overrides,
|
||
}
|
||
}
|
||
|
||
beforeEach(() => {
|
||
vi.clearAllMocks()
|
||
})
|
||
|
||
afterEach(() => {
|
||
cleanup()
|
||
})
|
||
|
||
describe('EvalReport', () => {
|
||
it('shows empty states when there is no report and no sessions', () => {
|
||
render(<EvalReport ev={makeEval({ report: null, sessions: [] })} />)
|
||
|
||
expect(screen.getByText('暂无报告')).toBeInTheDocument()
|
||
expect(screen.getByText('暂无会话')).toBeInTheDocument()
|
||
})
|
||
|
||
it('renders summary cards, dimension scores, findings, highlights and recommendations', () => {
|
||
render(<EvalReport ev={makeEval()} />)
|
||
|
||
expect(screen.getByText('4.2')).toBeInTheDocument()
|
||
expect(screen.getByText('问题发现(2)')).toBeInTheDocument()
|
||
expect(screen.getAllByText('服务态度').length).toBeGreaterThanOrEqual(1)
|
||
expect(screen.getByText('4.5')).toBeInTheDocument()
|
||
expect(screen.getByText('3.2')).toBeInTheDocument()
|
||
expect(screen.getByText('整体服务态度良好,价保场景存在流程遗漏')).toBeInTheDocument()
|
||
expect(screen.getByText('价保流程未主动告知凭证')).toBeInTheDocument()
|
||
expect(screen.getByText('回复偏慢')).toBeInTheDocument()
|
||
expect(screen.getByText('语气友好')).toBeInTheDocument()
|
||
expect(screen.getByText('优先整改价保流程')).toBeInTheDocument()
|
||
expect(document.body.textContent).toContain('高 1 · 中 1 · 低 0')
|
||
})
|
||
|
||
it('expanding a session panel loads its messages', async () => {
|
||
listMessages.mockResolvedValue({
|
||
data: {
|
||
messages: [
|
||
{ id: 'm-1', session_id: 's-1', role: 'user', content: '我想退货', latency_ms: null, created_at: null },
|
||
{ id: 'm-2', session_id: 's-1', role: 'assistant', content: '好的,请提供订单号', latency_ms: 320, created_at: null },
|
||
],
|
||
},
|
||
})
|
||
render(<EvalReport ev={makeEval()} />)
|
||
|
||
expect(listMessages).not.toHaveBeenCalled()
|
||
fireEvent.click(screen.getByText('老客户'))
|
||
await waitFor(() => expect(listMessages).toHaveBeenCalledWith('eval-1', 's-1'))
|
||
expect(await screen.findByText('我想退货')).toBeInTheDocument()
|
||
expect(screen.getByText('好的,请提供订单号')).toBeInTheDocument()
|
||
expect(screen.getByText('320ms')).toBeInTheDocument()
|
||
})
|
||
|
||
it('exports the markdown report', async () => {
|
||
downloadReportMarkdown.mockResolvedValue(undefined)
|
||
render(<EvalReport ev={makeEval()} />)
|
||
|
||
fireEvent.click(screen.getByText(/导出 Markdown/))
|
||
await waitFor(() => expect(downloadReportMarkdown).toHaveBeenCalledWith('eval-1'))
|
||
})
|
||
})
|