Files
goodbuddy/src/renderer/src/ActivityPanel.test.tsx
T
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> b3fdf96962 feat: expand secure assistant workflows
Harden runtime execution and add local knowledge, Smart Heartbeat, usage visibility, responsive product surfaces, and cross-platform packaging support.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-02 10:04:59 +08:00

208 lines
5.4 KiB
TypeScript

import {
cleanup,
fireEvent,
render,
screen,
within
} from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { TokenUsageSummary } from '../../shared/assistant-contracts'
import { ActivityPanel } from './ActivityPanel'
import {
MAX_ACTIVITY_RECORDS,
type ActivityRecord
} from './activity-store'
function makeRecord(
index: number,
status: ActivityRecord['status'] = 'completed'
): ActivityRecord {
return {
id: `activity-${index}`,
conversationId: `conversation-${index}`,
requestId: `request-${index}`,
kind: 'tool',
title: `活动 ${index}`,
detail: `详情 ${index}`,
status,
createdAt: Date.UTC(2026, 0, 1, 12, 0, index)
}
}
function makeTokenUsage(): TokenUsageSummary {
return {
totals: {
callCount: 2,
input: 125,
output: 25,
cacheRead: 40,
cacheWrite: 10,
totalTokens: 200
},
records: [
{
requestId: 'request-1',
projectId: 'project-1',
projectName: '项目甲',
conversationId: 'conversation-1',
conversationTitle: '会话甲',
runtime: 'model',
provider: 'openai',
model: 'gpt-5',
callCount: 1,
input: 100,
output: 20,
cacheRead: 40,
cacheWrite: 10,
totalTokens: 170
},
{
requestId: 'request-2',
conversationId: '',
runtime: 'model',
provider: '',
model: '',
callCount: 1,
input: 25,
output: 5,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 30
}
]
}
}
describe('ActivityPanel', () => {
afterEach(() => {
cleanup()
})
it('filters active and unsuccessful activity and opens its conversation', () => {
const onOpenConversation = vi.fn()
render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={onOpenConversation}
records={[
makeRecord(1, 'running'),
makeRecord(2, 'failed'),
makeRecord(3, 'denied'),
makeRecord(4)
]}
tokenUsage={makeTokenUsage()}
/>
)
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
expect(screen.getByText('活动 1')).toBeInTheDocument()
expect(screen.queryByText('活动 2')).not.toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '失败' }))
expect(screen.getByText('活动 2')).toBeInTheDocument()
expect(screen.getByText('活动 3')).toBeInTheDocument()
expect(screen.queryByText('活动 1')).not.toBeInTheDocument()
fireEvent.click(
screen.getAllByRole('button', { name: '打开所属对话' })[0]!
)
expect(onOpenConversation).toHaveBeenCalledWith('conversation-2')
})
it('clears activity and explains the real empty state', () => {
const onClear = vi.fn()
const { rerender } = render(
<ActivityPanel
onClear={onClear}
onOpenConversation={vi.fn()}
records={[makeRecord(1)]}
tokenUsage={makeTokenUsage()}
/>
)
fireEvent.click(screen.getByRole('button', { name: '清空记录' }))
expect(onClear).toHaveBeenCalledOnce()
rerender(
<ActivityPanel
onClear={onClear}
onOpenConversation={vi.fn()}
records={[]}
tokenUsage={makeTokenUsage()}
/>
)
expect(
screen.getByText(
'尚无活动记录。任务请求、工具调用和审批决定会显示在这里。'
)
).toBeInTheDocument()
expect(
screen.getByRole('button', { name: '清空记录' })
).toBeDisabled()
})
it('never renders more than 500 records', () => {
const records = Array.from(
{ length: MAX_ACTIVITY_RECORDS + 1 },
(_, index) => makeRecord(index)
)
render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={vi.fn()}
records={records}
tokenUsage={makeTokenUsage()}
/>
)
expect(screen.getByText('活动 499')).toBeInTheDocument()
expect(screen.queryByText('活动 500')).not.toBeInTheDocument()
})
it('shows totals without double-counting cache tokens', () => {
render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={vi.fn()}
records={[]}
tokenUsage={makeTokenUsage()}
/>
)
const stats = screen.getByLabelText('Token 用量统计')
expect(
within(stats).getByText('150')
).toBeInTheDocument()
const projectRow = screen.getByRole('row', {
name: '项目甲gpt-5 · openai 100 20 10 40 120'
})
expect(projectRow).toBeInTheDocument()
expect(
within(projectRow).queryByText('170')
).not.toBeInTheDocument()
})
it('groups token usage and displays fallback labels', () => {
render(
<ActivityPanel
onClear={vi.fn()}
onOpenConversation={vi.fn()}
records={[]}
tokenUsage={makeTokenUsage()}
/>
)
expect(screen.getByText('项目甲')).toBeInTheDocument()
expect(screen.getByText('未归属项目')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '按会话' }))
expect(screen.getByText('会话甲')).toBeInTheDocument()
expect(screen.getByText('已删除会话')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: '按模型' }))
expect(screen.getByText('gpt-5')).toBeInTheDocument()
expect(screen.getByText('未知模型')).toBeInTheDocument()
})
})