|
|
@@ -5,13 +5,16 @@ import { cleanup, fireEvent, render } from '@testing-library/react'
|
|
|
import type { RunningToolCall, ToolResultNode } from '@deepseek-ai/dsh-client-ui-chat/client'
|
|
|
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
|
|
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
|
|
-import { classifyTool, resultText, toolRowModel } from '../src/client/tool/models/tool-call-model.ts'
|
|
|
+import {
|
|
|
+ classifyTool, formatToolBody, resultText, toolRowModel,
|
|
|
+} from '../src/client/tool/models/tool-call-model.ts'
|
|
|
import { ToolRow } from '../src/client/tool/components/ToolRow.tsx'
|
|
|
import { GenericToolCard, type GenericToolCardProps } from '../src/client/tool/toolviews/GenericToolCard.tsx'
|
|
|
import { zh } from '@deepseek-ai/dsh-client-ui-conversation/src/client/locales.ts'
|
|
|
|
|
|
afterEach(() => {
|
|
|
cleanup()
|
|
|
+ vi.restoreAllMocks()
|
|
|
})
|
|
|
|
|
|
const t: GenericToolCardProps['t'] = makeTranslate(zh, commonZh)
|
|
|
@@ -163,14 +166,17 @@ describe('tool-call-model', () => {
|
|
|
})
|
|
|
|
|
|
it('body pretty-prints JSON args, keeps raw non-JSON, null when empty', () => {
|
|
|
- expect(toolRowModel('bash', running({ argsRaw: '{"a":1}' })).body).toBe('{\n "a": 1\n}')
|
|
|
- expect(toolRowModel('bash', running({ argsRaw: 'raw' })).body).toBe('raw')
|
|
|
- expect(toolRowModel('bash', running({ argsRaw: '' })).body).toBeNull()
|
|
|
- expect(toolRowModel('bash', result({ call: null })).body).toBeNull()
|
|
|
+ expect(formatToolBody('bash', toolRowModel('bash', running({ argsRaw: '{"a":1}' })).bodyRaw ?? ''))
|
|
|
+ .toBe('{\n "a": 1\n}')
|
|
|
+ expect(formatToolBody('bash', toolRowModel('bash', running({ argsRaw: 'raw' })).bodyRaw ?? ''))
|
|
|
+ .toBe('raw')
|
|
|
+ expect(toolRowModel('bash', running({ argsRaw: '' })).bodyRaw).toBeNull()
|
|
|
+ expect(toolRowModel('bash', result({ call: null })).bodyRaw).toBeNull()
|
|
|
})
|
|
|
|
|
|
it('a code row with an empty program falls back to the args JSON envelope', () => {
|
|
|
- expect(toolRowModel('run_code', running({ name: 'run_code', argsRaw: '{"code":""}' })).body)
|
|
|
+ const model = toolRowModel('run_code', running({ name: 'run_code', argsRaw: '{"code":""}' }))
|
|
|
+ expect(formatToolBody(model.variant, model.bodyRaw ?? ''))
|
|
|
.toBe('{\n "code": ""\n}')
|
|
|
})
|
|
|
|
|
|
@@ -228,7 +234,7 @@ describe('ToolRow', () => {
|
|
|
const rowProps = {
|
|
|
t,
|
|
|
variant: 'bash' as const, icon: <i data-testid="tool-icon" />, title: 'Bash',
|
|
|
- summary: 'List files', body: '{\n "a": 1\n}', state: 'ok' as const,
|
|
|
+ summary: 'List files', bodyRaw: '{"a":1}', state: 'ok' as const,
|
|
|
}
|
|
|
|
|
|
it('renders leading icon, title and summary while collapsed', () => {
|
|
|
@@ -252,6 +258,18 @@ describe('ToolRow', () => {
|
|
|
expect(view.getByText('List files')).toBeTruthy()
|
|
|
})
|
|
|
|
|
|
+ it('formats the argument body only while expanding it', () => {
|
|
|
+ const stringify = vi.spyOn(JSON, 'stringify')
|
|
|
+ const view = render(<ToolRow {...rowProps} />)
|
|
|
+ expect(stringify).not.toHaveBeenCalled()
|
|
|
+
|
|
|
+ fireEvent.click(view.getByRole('button'))
|
|
|
+ expect(stringify).toHaveBeenCalledTimes(1)
|
|
|
+
|
|
|
+ fireEvent.click(view.getByRole('button'))
|
|
|
+ expect(stringify).toHaveBeenCalledTimes(1)
|
|
|
+ })
|
|
|
+
|
|
|
it('running keeps the icon (row sweep carries the signal); error swaps in a StateDot', () => {
|
|
|
const runningView = render(<ToolRow {...rowProps} state="running" />)
|
|
|
expect(runningView.queryByTestId('tool-icon')).not.toBeNull()
|
|
|
@@ -264,7 +282,7 @@ describe('ToolRow', () => {
|
|
|
})
|
|
|
|
|
|
it('non-expandable rows render a passive leading slot and no row button', () => {
|
|
|
- const view = render(<ToolRow {...rowProps} body={null} />)
|
|
|
+ const view = render(<ToolRow {...rowProps} bodyRaw={null} />)
|
|
|
expect(view.queryByRole('button')).toBeNull()
|
|
|
expect(view.container.querySelector('[aria-expanded]')).toBeNull()
|
|
|
expect(view.queryByTestId('tool-icon')).not.toBeNull()
|
|
|
@@ -391,7 +409,7 @@ describe('ToolRow', () => {
|
|
|
expect(inputOnly.getByText('输入')).toBeTruthy()
|
|
|
expect(inputOnly.queryByText('输出')).toBeNull()
|
|
|
cleanup()
|
|
|
- const outputOnly = render(<ToolRow {...rowProps} body={null} output="only out" />)
|
|
|
+ const outputOnly = render(<ToolRow {...rowProps} bodyRaw={null} output="only out" />)
|
|
|
fireEvent.click(outputOnly.getByRole('button'))
|
|
|
expect(outputOnly.queryByText('输入')).toBeNull()
|
|
|
expect(outputOnly.getByText('输出')).toBeTruthy()
|