cc-haha/desktop/src/components/chat/chatBlocks.test.tsx
程序员阿江(Relakkes) 993b96cd39 Stabilize the desktop transcript so long agent sessions stay readable
The desktop app now keeps the composer stable while turns are active,
reduces low-signal tool noise in the transcript, restores project context
under the composer after session creation, and relies on the CLI's own
permission requests instead of injecting broader desktop-side Bash asks.

This also brings in the supporting desktop app source tree and the server
routes/session metadata needed for git info, filesystem browsing, session
resume, slash commands, and SDK-backed permission bridging so the UI can
operate as a coherent feature instead of a partial patch.

Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs
Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts
Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model
Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history
Confidence: medium
Scope-risk: broad
Reversibility: messy
Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual visual regression against the exact screenshots in a live desktop session
Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)
2026-04-06 20:37:44 +08:00

103 lines
3.2 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { ThinkingBlock } from './ThinkingBlock'
import { ToolCallBlock } from './ToolCallBlock'
import { PermissionDialog } from './PermissionDialog'
import { useChatStore } from '../../stores/chatStore'
describe('chat blocks', () => {
beforeEach(() => {
useChatStore.setState({
chatState: 'idle',
messages: [],
streamingText: '',
streamingToolInput: '',
activeToolUseId: null,
activeToolName: null,
activeThinkingId: null,
pendingPermission: null,
elapsedSeconds: 0,
})
})
it('keeps thinking collapsed by default', () => {
const { container } = render(<ThinkingBlock content="this is a long internal reasoning trace" isActive />)
expect(screen.getByText(/Thinking/)).toBeTruthy()
expect(container.textContent).toContain('this is a long internal reasoning trace')
expect(container.querySelector('.thinking-cursor')).toBeNull()
})
it('does not animate inactive historical thinking blocks', () => {
const { container } = render(<ThinkingBlock content="old reasoning" isActive={false} />)
expect(container.querySelector('.thinking-inline-cursor')).toBeNull()
})
it('shows tool previews only after expanding the tool block', () => {
const { container } = render(
<ToolCallBlock
toolName="Read"
input={{ file_path: '/tmp/example.ts', limit: 20 }}
result={{ content: 'const answer = 42\nconsole.log(answer)', isError: false }}
/>,
)
expect(container.textContent).toContain('Read')
expect(container.textContent).not.toContain('const answer = 42')
fireEvent.click(screen.getByRole('button'))
expect(container.textContent).not.toContain('Tool Input')
expect(container.textContent).not.toContain('const answer = 42')
})
it('does not surface bash stdout in the transcript preview', () => {
const { container } = render(
<ToolCallBlock
toolName="Bash"
input={{ command: 'ls -la', description: 'List files' }}
result={{ content: 'file-a\nfile-b\nfile-c', isError: false }}
/>,
)
expect(container.textContent).toContain('Bash')
expect(container.textContent).not.toContain('file-a')
fireEvent.click(screen.getByRole('button'))
expect(container.textContent).toContain('ls -la')
expect(container.textContent).not.toContain('file-a')
})
it('shows a diff preview for edit permission requests', () => {
useChatStore.setState({
pendingPermission: {
requestId: 'perm-1',
toolName: 'Edit',
input: {
file_path: '/tmp/example.ts',
old_string: 'const count = 1',
new_string: 'const count = 2',
},
},
})
const { container } = render(
<PermissionDialog
requestId="perm-1"
toolName="Edit"
input={{
file_path: '/tmp/example.ts',
old_string: 'const count = 1',
new_string: 'const count = 2',
}}
/>,
)
expect(container.textContent).toContain('/tmp/example.ts')
expect(container.textContent).toContain('const count = 1')
expect(container.textContent).toContain('const count = 2')
})
})