cc-haha/desktop/src/components/chat/SessionTaskBar.test.tsx
程序员阿江(Relakkes) b156be8d8d feat: make PR quality verification self-enforcing
Contributors and coding agents need one local command that both reports and enforces the quality contract. This change turns the PR gate into the shared verification entrypoint, adds path-selected local lanes, tightens coverage accounting around changed lines, and documents the repair loop in contributor and agent-facing guidance.

Constraint: Ordinary PR verification must stay non-live and runnable without provider credentials
Constraint: Coverage policy updates in this commit require maintainer approval before push/merge
Rejected: Keep quality guidance only in docs | agents need executable scripts and AGENTS.md instructions to follow the loop consistently
Confidence: high
Scope-risk: broad
Directive: Do not bypass `bun run verify` for production changes; fix failed lanes and coverage reports instead of lowering thresholds
Tested: bun run check:policy
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run verify
Not-tested: live provider baseline; no provider credentials were required for this non-live PR gate
2026-05-06 22:33:43 +08:00

114 lines
3.2 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
import '@testing-library/jest-dom'
import { SessionTaskBar } from './SessionTaskBar'
import { useCLITaskStore } from '../../stores/cliTaskStore'
vi.mock('../../api/cliTasks', () => ({
cliTasksApi: {
getTasksForList: vi.fn(),
resetTaskList: vi.fn(async () => ({ ok: true })),
},
}))
vi.mock('../../i18n', () => ({
useTranslation: () => (key: string) => {
const translations: Record<string, string> = {
'tasks.title': 'Tasks',
'tasks.dismissCompleted': 'Hide completed tasks',
}
return translations[key] ?? key
},
}))
describe('SessionTaskBar', () => {
beforeEach(() => {
useCLITaskStore.setState({
sessionId: 'session-1',
tasks: [],
expanded: false,
completedAndDismissed: false,
dismissedCompletionKey: null,
})
})
afterEach(() => {
cleanup()
useCLITaskStore.getState().clearTasks()
})
it('only shows the dismiss button once every task is completed', () => {
act(() => {
useCLITaskStore.getState().setTasksFromTodos([
{ content: 'first', status: 'completed' },
{ content: 'second', status: 'in_progress', activeForm: 'working' },
])
})
act(() => {
render(<SessionTaskBar />)
})
expect(screen.getByText('Tasks')).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Hide completed tasks' })).toBeNull()
})
it('hides the bar after dismissing a completed task set', async () => {
act(() => {
useCLITaskStore.getState().setTasksFromTodos([
{ content: 'first', status: 'completed' },
{ content: 'second', status: 'completed' },
])
})
act(() => {
render(<SessionTaskBar />)
})
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: 'Hide completed tasks' }))
await Promise.resolve()
})
expect(screen.queryByText('Tasks')).toBeNull()
expect(useCLITaskStore.getState().tasks).toEqual([])
})
it('shows the bar again for a new task cycle after a previous completed set was dismissed', async () => {
act(() => {
useCLITaskStore.getState().setTasksFromTodos([
{ content: 'first', status: 'completed' },
])
})
act(() => {
render(<SessionTaskBar />)
})
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: 'Hide completed tasks' }))
await Promise.resolve()
})
expect(screen.queryByText('Tasks')).toBeNull()
act(() => {
useCLITaskStore.getState().setTasksFromTodos([
{ content: 'next task', status: 'in_progress', activeForm: 'running next task' },
])
})
expect(screen.getByText('Tasks')).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Hide completed tasks' })).toBeNull()
act(() => {
useCLITaskStore.getState().setTasksFromTodos([
{ content: 'next task', status: 'completed' },
])
})
expect(screen.getByText('Tasks')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Hide completed tasks' })).toBeInTheDocument()
})
})