sparkdesign 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +7 -5
  2. package/cli/dist/index.js +0 -0
  3. package/cli/dist/utils/tokens.js +103 -17
  4. package/cli/registry/basic/button.test.tsx +333 -0
  5. package/cli/registry/chat/{question-part.tsx → ask-user-part.tsx} +4 -4
  6. package/cli/registry/chat/{browser-use-part.tsx → browser-action-part.tsx} +6 -6
  7. package/cli/registry/chat/{suggestion-part.tsx → hint-banner.tsx} +4 -4
  8. package/cli/registry/chat/markdown.test.tsx +387 -0
  9. package/cli/registry/chat/{reasoning-step.tsx → reasoning-step/compound.tsx} +163 -185
  10. package/cli/registry/chat/reasoning-step/context.tsx +114 -0
  11. package/cli/registry/chat/reasoning-step/index.tsx +45 -0
  12. package/cli/registry/chat/reasoning-step/types.ts +109 -0
  13. package/cli/registry/chat/response/compound.tsx +210 -0
  14. package/cli/registry/chat/{response.tsx → response/context.tsx} +65 -136
  15. package/cli/registry/chat/response/index.tsx +87 -0
  16. package/cli/registry/chat/response/types.ts +123 -0
  17. package/cli/registry/chat/thinking-indicator.test.tsx +244 -0
  18. package/cli/registry/chat/tool-invocation-card.test.tsx +346 -0
  19. package/cli/registry/chat/{request.tsx → user-message.tsx} +3 -3
  20. package/cli/registry/chat/user-question/compound.tsx +324 -0
  21. package/cli/registry/chat/user-question/context.tsx +456 -0
  22. package/cli/registry/chat/user-question/index.tsx +71 -316
  23. package/cli/registry/chat/user-question/useUserQuestionKeyboard.ts +5 -6
  24. package/cli/registry/tokens/index.css +31 -0
  25. package/cli/registry/tokens/scale/computed.css +103 -0
  26. package/cli/registry/tokens/scale/config.css +110 -0
  27. package/cli/registry/tokens/scale/index.css +30 -0
  28. package/cli/registry/tokens/scale/presets/compact.css +30 -0
  29. package/cli/registry/tokens/scale/presets/dense.css +64 -0
  30. package/cli/registry/tokens/scale/presets/sharp.css +40 -0
  31. package/cli/registry/tokens/scale/presets/soft.css +16 -0
  32. package/cli/registry/tokens/scale.css +12 -298
  33. package/cli/registry/tokens/scrollbar-utility.css +35 -0
  34. package/cli/registry/tokens/themes/dark-parchment.css +132 -0
  35. package/cli/registry/tokens/themes/dark-qoder.css +132 -0
  36. package/cli/registry/tokens/themes/light-parchment.css +123 -0
  37. package/cli/registry/tokens/themes/light-qoder.css +131 -0
  38. package/dist/qoder-design.css +1 -1
  39. package/dist/registry/chat/ask-user-part.d.ts +24 -0
  40. package/dist/registry/chat/browser-action-part.d.ts +28 -0
  41. package/dist/registry/chat/{suggestion-part.d.ts → hint-banner.d.ts} +4 -4
  42. package/dist/registry/chat/reasoning-step/compound.d.ts +17 -0
  43. package/dist/registry/chat/reasoning-step/context.d.ts +10 -0
  44. package/dist/registry/chat/reasoning-step/index.d.ts +14 -0
  45. package/dist/registry/chat/reasoning-step/types.d.ts +95 -0
  46. package/dist/registry/chat/response/compound.d.ts +25 -0
  47. package/dist/registry/chat/response/context.d.ts +9 -0
  48. package/dist/registry/chat/response/index.d.ts +15 -0
  49. package/dist/registry/chat/response/types.d.ts +99 -0
  50. package/dist/registry/chat/user-message.d.ts +6 -0
  51. package/dist/registry/chat/user-question/compound.d.ts +37 -0
  52. package/dist/registry/chat/user-question/context.d.ts +55 -0
  53. package/dist/registry/chat/user-question/index.d.ts +13 -5
  54. package/dist/registry/chat/user-question/useUserQuestionKeyboard.d.ts +2 -3
  55. package/dist/scale.css +9 -303
  56. package/dist/spark-design.cjs.js +62 -62
  57. package/dist/spark-design.es.js +3992 -3826
  58. package/dist/src/components/chat/AskUserPart/index.d.ts +6 -0
  59. package/dist/src/components/chat/BrowserActionPart/index.d.ts +7 -0
  60. package/dist/src/components/chat/HintBanner/index.d.ts +6 -0
  61. package/dist/src/components/chat/ReasoningStep/index.d.ts +11 -5
  62. package/dist/src/components/chat/Response/index.d.ts +16 -6
  63. package/dist/src/components/chat/UserMessage/index.d.ts +7 -0
  64. package/dist/src/components/chat/UserQuestion/index.d.ts +18 -4
  65. package/dist/src/components/index.d.ts +63 -63
  66. package/dist/theme.css +13 -800
  67. package/package.json +27 -3
  68. package/dist/registry/chat/browser-use-part.d.ts +0 -28
  69. package/dist/registry/chat/question-part.d.ts +0 -24
  70. package/dist/registry/chat/reasoning-step.d.ts +0 -35
  71. package/dist/registry/chat/request.d.ts +0 -6
  72. package/dist/registry/chat/response.d.ts +0 -28
  73. package/dist/src/components/chat/BrowserUsePart/index.d.ts +0 -7
  74. package/dist/src/components/chat/QuestionPart/index.d.ts +0 -6
  75. package/dist/src/components/chat/Request/index.d.ts +0 -7
  76. package/dist/src/components/chat/SuggestionPart/index.d.ts +0 -6
  77. /package/dist/src/components/{foundation → basic}/AlertDialog/index.d.ts +0 -0
  78. /package/dist/src/components/{foundation → basic}/Avatar/index.d.ts +0 -0
  79. /package/dist/src/components/{foundation → basic}/Button/index.d.ts +0 -0
  80. /package/dist/src/components/{foundation → basic}/Collapse/index.d.ts +0 -0
  81. /package/dist/src/components/{foundation → basic}/Collapsible/index.d.ts +0 -0
  82. /package/dist/src/components/{foundation → basic}/CollapsibleSection/index.d.ts +0 -0
  83. /package/dist/src/components/{foundation → basic}/DropdownMenu/index.d.ts +0 -0
  84. /package/dist/src/components/{foundation → basic}/EllipsisText/index.d.ts +0 -0
  85. /package/dist/src/components/{foundation → basic}/IconButton/index.d.ts +0 -0
  86. /package/dist/src/components/{foundation → basic}/Kbd/index.d.ts +0 -0
  87. /package/dist/src/components/{foundation → basic}/OptionList/index.d.ts +0 -0
  88. /package/dist/src/components/{foundation → basic}/Pagination/index.d.ts +0 -0
  89. /package/dist/src/components/{foundation → basic}/Progress/index.d.ts +0 -0
  90. /package/dist/src/components/{foundation → basic}/RadioGroup/index.d.ts +0 -0
  91. /package/dist/src/components/{foundation → basic}/Resizable/index.d.ts +0 -0
  92. /package/dist/src/components/{foundation → basic}/Scrollbar/index.d.ts +0 -0
  93. /package/dist/src/components/{foundation → basic}/Select/index.d.ts +0 -0
  94. /package/dist/src/components/{foundation → basic}/Skeleton/index.d.ts +0 -0
  95. /package/dist/src/components/{foundation → basic}/Slider/index.d.ts +0 -0
  96. /package/dist/src/components/{foundation → basic}/Spinner/index.d.ts +0 -0
  97. /package/dist/src/components/{foundation → basic}/Switch/index.d.ts +0 -0
  98. /package/dist/src/components/{foundation → basic}/Table/index.d.ts +0 -0
  99. /package/dist/src/components/{foundation → basic}/Tabs/index.d.ts +0 -0
  100. /package/dist/src/components/{foundation → basic}/Tag/index.d.ts +0 -0
  101. /package/dist/src/components/{foundation → basic}/Toast/index.d.ts +0 -0
  102. /package/dist/src/components/{foundation → basic}/Toggle/index.d.ts +0 -0
  103. /package/dist/src/components/{foundation → basic}/Tooltip/index.d.ts +0 -0
  104. /package/dist/src/components/{foundation → basic}/Typography/index.d.ts +0 -0
@@ -0,0 +1,244 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { ThinkingIndicator } from './thinking-indicator'
4
+
5
+ describe('ThinkingIndicator', () => {
6
+ // ============================================================
7
+ // 渲染测试
8
+ // ============================================================
9
+ describe('渲染', () => {
10
+ it('应正确渲染组件', () => {
11
+ const { container } = render(<ThinkingIndicator />)
12
+ expect(container.firstChild).toBeInTheDocument()
13
+ })
14
+
15
+ it('应设置正确的 displayName', () => {
16
+ expect(ThinkingIndicator.displayName).toBe('ThinkingIndicator')
17
+ })
18
+ })
19
+
20
+ // ============================================================
21
+ // 文案测试
22
+ // ============================================================
23
+ describe('text 文案', () => {
24
+ it('应显示默认文案 "Qoder is Thinking..."', () => {
25
+ render(<ThinkingIndicator />)
26
+ expect(screen.getByText('Qoder is Thinking...')).toBeInTheDocument()
27
+ })
28
+
29
+ it('应显示自定义文案', () => {
30
+ render(<ThinkingIndicator text="正在思考..." />)
31
+ expect(screen.getByText('正在思考...')).toBeInTheDocument()
32
+ })
33
+
34
+ it('空文案应正常渲染', () => {
35
+ const { container } = render(<ThinkingIndicator text="" />)
36
+ expect(container.firstChild).toBeInTheDocument()
37
+ })
38
+
39
+ it('长文案应正常渲染', () => {
40
+ const longText = 'AI is analyzing your complex multi-step request with deep reasoning...'
41
+ render(<ThinkingIndicator text={longText} />)
42
+ expect(screen.getByText(longText)).toBeInTheDocument()
43
+ })
44
+ })
45
+
46
+ // ============================================================
47
+ // Loader Slot 测试
48
+ // ============================================================
49
+ describe('loader slot', () => {
50
+ it('无 loader prop 时应渲染默认三点动画', () => {
51
+ const { container } = render(<ThinkingIndicator />)
52
+ expect(container.querySelector('.thinking-dots')).toBeInTheDocument()
53
+ })
54
+
55
+ it('默认动画应包含三个点', () => {
56
+ const { container } = render(<ThinkingIndicator />)
57
+ const dots = container.querySelectorAll('.thinking-dots span')
58
+ expect(dots).toHaveLength(3)
59
+ })
60
+
61
+ it('有 loader prop 时应渲染自定义 loader', () => {
62
+ render(
63
+ <ThinkingIndicator
64
+ loader={<div data-testid="custom-loader">Custom Loader</div>}
65
+ />
66
+ )
67
+ expect(screen.getByTestId('custom-loader')).toBeInTheDocument()
68
+ })
69
+
70
+ it('自定义 loader 时不应渲染默认动画', () => {
71
+ const { container } = render(
72
+ <ThinkingIndicator
73
+ loader={<div data-testid="custom-loader">Custom</div>}
74
+ />
75
+ )
76
+ expect(container.querySelector('.thinking-dots')).not.toBeInTheDocument()
77
+ })
78
+
79
+ it('loader 可以是复杂 React 节点', () => {
80
+ render(
81
+ <ThinkingIndicator
82
+ loader={
83
+ <div data-testid="complex-loader">
84
+ <span>Loading</span>
85
+ <span>...</span>
86
+ </div>
87
+ }
88
+ />
89
+ )
90
+ expect(screen.getByTestId('complex-loader')).toBeInTheDocument()
91
+ })
92
+ })
93
+
94
+ // ============================================================
95
+ // Shimmer 效果测试
96
+ // ============================================================
97
+ describe('shimmer 效果', () => {
98
+ it('默认应启用 shimmer 效果', () => {
99
+ render(<ThinkingIndicator text="Test" />)
100
+ // shimmer 使用 motion.span,在测试环境被 mock 为普通 span
101
+ expect(screen.getByText('Test')).toBeInTheDocument()
102
+ })
103
+
104
+ it('shimmer=true 时文本应有渐变样式类', () => {
105
+ render(<ThinkingIndicator shimmer text="Shimmer Text" />)
106
+ const textElement = screen.getByText('Shimmer Text')
107
+ expect(textElement).toHaveClass('bg-clip-text')
108
+ expect(textElement).toHaveClass('text-transparent')
109
+ })
110
+
111
+ it('shimmer=false 时应显示静态文本', () => {
112
+ render(<ThinkingIndicator shimmer={false} text="Static Text" />)
113
+ const textElement = screen.getByText('Static Text')
114
+ expect(textElement).toHaveClass('text-text-secondary')
115
+ expect(textElement).not.toHaveClass('text-transparent')
116
+ })
117
+ })
118
+
119
+ // ============================================================
120
+ // Shimmer 配置测试
121
+ // ============================================================
122
+ describe('shimmer 配置', () => {
123
+ it('应接受自定义 shimmerDuration', () => {
124
+ // shimmerDuration 用于动画,测试环境动画被 mock,仅验证 prop 接受
125
+ const { container } = render(
126
+ <ThinkingIndicator shimmerDuration={5} text="Custom Duration" />
127
+ )
128
+ expect(container.firstChild).toBeInTheDocument()
129
+ })
130
+
131
+ it('应接受自定义 shimmerSpread', () => {
132
+ const { container } = render(
133
+ <ThinkingIndicator shimmerSpread={4} text="Custom Spread" />
134
+ )
135
+ expect(container.firstChild).toBeInTheDocument()
136
+ })
137
+ })
138
+
139
+ // ============================================================
140
+ // textClassName 测试
141
+ // ============================================================
142
+ describe('textClassName', () => {
143
+ it('应将 textClassName 应用到文本元素', () => {
144
+ render(
145
+ <ThinkingIndicator text="Styled Text" textClassName="custom-text-class" />
146
+ )
147
+ const textElement = screen.getByText('Styled Text')
148
+ expect(textElement).toHaveClass('custom-text-class')
149
+ })
150
+
151
+ it('textClassName 应与默认类名共存', () => {
152
+ render(
153
+ <ThinkingIndicator
154
+ shimmer={false}
155
+ text="Mixed Classes"
156
+ textClassName="my-class"
157
+ />
158
+ )
159
+ const textElement = screen.getByText('Mixed Classes')
160
+ expect(textElement).toHaveClass('my-class')
161
+ expect(textElement).toHaveClass('text-sm')
162
+ })
163
+ })
164
+
165
+ // ============================================================
166
+ // className 覆盖测试
167
+ // ============================================================
168
+ describe('className', () => {
169
+ it('应合并自定义 className 到根元素', () => {
170
+ const { container } = render(
171
+ <ThinkingIndicator className="custom-root-class" />
172
+ )
173
+ expect(container.firstChild).toHaveClass('custom-root-class')
174
+ })
175
+
176
+ it('自定义 className 应与默认布局类共存', () => {
177
+ const { container } = render(
178
+ <ThinkingIndicator className="my-indicator" />
179
+ )
180
+ expect(container.firstChild).toHaveClass('my-indicator')
181
+ expect(container.firstChild).toHaveClass('inline-flex')
182
+ expect(container.firstChild).toHaveClass('items-center')
183
+ })
184
+ })
185
+
186
+ // ============================================================
187
+ // 原生属性透传测试
188
+ // ============================================================
189
+ describe('原生属性透传', () => {
190
+ it('应透传 data-* 属性', () => {
191
+ render(<ThinkingIndicator data-testid="thinking" />)
192
+ expect(screen.getByTestId('thinking')).toBeInTheDocument()
193
+ })
194
+
195
+ it('应透传 id 属性', () => {
196
+ const { container } = render(<ThinkingIndicator id="my-indicator" />)
197
+ expect(container.querySelector('#my-indicator')).toBeInTheDocument()
198
+ })
199
+
200
+ it('应透传 aria-* 属性', () => {
201
+ const { container } = render(
202
+ <ThinkingIndicator aria-label="AI is thinking" />
203
+ )
204
+ expect(container.firstChild).toHaveAttribute('aria-label', 'AI is thinking')
205
+ })
206
+ })
207
+
208
+ // ============================================================
209
+ // 组合场景测试
210
+ // ============================================================
211
+ describe('组合场景', () => {
212
+ it('应支持所有 props 组合', () => {
213
+ render(
214
+ <ThinkingIndicator
215
+ text="完整配置测试"
216
+ shimmer={true}
217
+ shimmerDuration={3}
218
+ shimmerSpread={2}
219
+ textClassName="text-class"
220
+ className="root-class"
221
+ data-testid="full-config"
222
+ loader={<span data-testid="custom-spinner">⟳</span>}
223
+ />
224
+ )
225
+
226
+ const root = screen.getByTestId('full-config')
227
+ expect(root).toHaveClass('root-class')
228
+ expect(screen.getByText('完整配置测试')).toHaveClass('text-class')
229
+ expect(screen.getByTestId('custom-spinner')).toBeInTheDocument()
230
+ })
231
+
232
+ it('shimmer=false 时 loader 仍可自定义', () => {
233
+ render(
234
+ <ThinkingIndicator
235
+ shimmer={false}
236
+ text="No Shimmer"
237
+ loader={<span data-testid="static-loader">●</span>}
238
+ />
239
+ )
240
+ expect(screen.getByTestId('static-loader')).toBeInTheDocument()
241
+ expect(screen.getByText('No Shimmer')).toHaveClass('text-text-secondary')
242
+ })
243
+ })
244
+ })
@@ -0,0 +1,346 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { ToolInvocationCard } from './tool-invocation-card'
4
+
5
+ describe('ToolInvocationCard', () => {
6
+ // ============================================================
7
+ // 基础渲染测试
8
+ // ============================================================
9
+ describe('基础渲染', () => {
10
+ it('应正确渲染工具名称', () => {
11
+ render(<ToolInvocationCard toolName="read_file" status="success" />)
12
+ expect(screen.getByText('read_file')).toBeInTheDocument()
13
+ })
14
+
15
+ it('应设置正确的 displayName', () => {
16
+ expect(ToolInvocationCard.displayName).toBe('ToolInvocationCard')
17
+ })
18
+
19
+ it('应渲染参数文本', () => {
20
+ render(
21
+ <ToolInvocationCard
22
+ toolName="search"
23
+ params="query: hello world"
24
+ status="success"
25
+ />
26
+ )
27
+ expect(screen.getByText('query: hello world')).toBeInTheDocument()
28
+ })
29
+
30
+ it('无参数时不应渲染参数区域', () => {
31
+ const { container } = render(
32
+ <ToolInvocationCard toolName="list_dir" status="success" />
33
+ )
34
+ // 只有工具名和状态文本
35
+ expect(container.textContent).toContain('list_dir')
36
+ })
37
+ })
38
+
39
+ // ============================================================
40
+ // 状态图标测试
41
+ // ============================================================
42
+ describe('状态图标', () => {
43
+ it('running 状态应显示时间图标', () => {
44
+ const { container } = render(
45
+ <ToolInvocationCard toolName="test" status="running" />
46
+ )
47
+ // 默认 timeIcon 使用 TimeLine 组件
48
+ expect(container.querySelector('svg')).toBeInTheDocument()
49
+ })
50
+
51
+ it('success 状态应显示勾选图标', () => {
52
+ const { container } = render(
53
+ <ToolInvocationCard toolName="test" status="success" />
54
+ )
55
+ expect(container.querySelector('svg')).toBeInTheDocument()
56
+ })
57
+
58
+ it('error 状态应显示关闭图标', () => {
59
+ const { container } = render(
60
+ <ToolInvocationCard toolName="test" status="error" />
61
+ )
62
+ expect(container.querySelector('svg')).toBeInTheDocument()
63
+ })
64
+
65
+ it('应接受自定义图标', () => {
66
+ render(
67
+ <ToolInvocationCard
68
+ toolName="test"
69
+ status="success"
70
+ checkboxCircleIcon={<span data-testid="custom-check">✓</span>}
71
+ />
72
+ )
73
+ expect(screen.getByTestId('custom-check')).toBeInTheDocument()
74
+ })
75
+ })
76
+
77
+ // ============================================================
78
+ // 状态文本测试
79
+ // ============================================================
80
+ describe('状态文本', () => {
81
+ it('running 状态应显示默认文本 "Running"', () => {
82
+ render(<ToolInvocationCard toolName="test" status="running" />)
83
+ expect(screen.getByText('Running')).toBeInTheDocument()
84
+ })
85
+
86
+ it('success 状态应显示默认文本 "Ran"', () => {
87
+ render(<ToolInvocationCard toolName="test" status="success" />)
88
+ expect(screen.getByText('Ran')).toBeInTheDocument()
89
+ })
90
+
91
+ it('error 状态应显示默认文本 "Failed"', () => {
92
+ render(<ToolInvocationCard toolName="test" status="error" />)
93
+ expect(screen.getByText('Failed')).toBeInTheDocument()
94
+ })
95
+
96
+ it('应接受自定义 statusText', () => {
97
+ render(
98
+ <ToolInvocationCard
99
+ toolName="test"
100
+ status="success"
101
+ statusText="执行完成"
102
+ />
103
+ )
104
+ expect(screen.getByText('执行完成')).toBeInTheDocument()
105
+ })
106
+
107
+ it('statusText 为空字符串时不应显示状态文本', () => {
108
+ render(
109
+ <ToolInvocationCard
110
+ toolName="test"
111
+ status="success"
112
+ statusText=""
113
+ />
114
+ )
115
+ expect(screen.queryByText('Ran')).not.toBeInTheDocument()
116
+ })
117
+
118
+ it('应接受自定义状态文本 props', () => {
119
+ render(
120
+ <ToolInvocationCard
121
+ toolName="test"
122
+ status="running"
123
+ runningText="执行中"
124
+ />
125
+ )
126
+ expect(screen.getByText('执行中')).toBeInTheDocument()
127
+ })
128
+ })
129
+
130
+ // ============================================================
131
+ // 响应展示测试
132
+ // ============================================================
133
+ describe('响应展示', () => {
134
+ it('有响应时应可展开', () => {
135
+ render(
136
+ <ToolInvocationCard
137
+ toolName="test"
138
+ status="success"
139
+ response="Tool output result"
140
+ />
141
+ )
142
+ // 点击展开
143
+ fireEvent.click(screen.getByText('test'))
144
+ expect(screen.getByText('Tool output result')).toBeInTheDocument()
145
+ })
146
+
147
+ it('defaultExpanded=true 时应默认展开', () => {
148
+ render(
149
+ <ToolInvocationCard
150
+ toolName="test"
151
+ status="success"
152
+ response="Already expanded"
153
+ defaultExpanded={true}
154
+ />
155
+ )
156
+ expect(screen.getByText('Already expanded')).toBeInTheDocument()
157
+ })
158
+
159
+ it('无响应时不应显示展开箭头', () => {
160
+ const { container } = render(
161
+ <ToolInvocationCard toolName="test" status="success" />
162
+ )
163
+ // 没有箭头图标(ArrowRightLine 的 SVG)
164
+ const arrows = container.querySelectorAll('[class*="rotate"]')
165
+ expect(arrows).toHaveLength(0)
166
+ })
167
+
168
+ it('running 状态时不应可展开', () => {
169
+ render(
170
+ <ToolInvocationCard
171
+ toolName="test"
172
+ status="running"
173
+ response="Some response"
174
+ />
175
+ )
176
+ // running 状态下点击不应展开
177
+ fireEvent.click(screen.getByText('test'))
178
+ expect(screen.queryByText('Some response')).not.toBeInTheDocument()
179
+ })
180
+
181
+ it('点击应切换展开/收起状态', () => {
182
+ render(
183
+ <ToolInvocationCard
184
+ toolName="toggle-test"
185
+ status="success"
186
+ response="Toggle content"
187
+ />
188
+ )
189
+ const trigger = screen.getByText('toggle-test')
190
+
191
+ // 第一次点击展开
192
+ fireEvent.click(trigger)
193
+ expect(screen.getByText('Toggle content')).toBeInTheDocument()
194
+
195
+ // 第二次点击收起
196
+ fireEvent.click(trigger)
197
+ expect(screen.queryByText('Toggle content')).not.toBeInTheDocument()
198
+ })
199
+ })
200
+
201
+ // ============================================================
202
+ // 响应标签测试
203
+ // ============================================================
204
+ describe('响应标签', () => {
205
+ it('展开时应显示默认标签 "Response"', () => {
206
+ render(
207
+ <ToolInvocationCard
208
+ toolName="test"
209
+ status="success"
210
+ response="Content"
211
+ defaultExpanded
212
+ />
213
+ )
214
+ expect(screen.getByText('Response')).toBeInTheDocument()
215
+ })
216
+
217
+ it('应接受自定义 responseLabel', () => {
218
+ render(
219
+ <ToolInvocationCard
220
+ toolName="test"
221
+ status="success"
222
+ response="Content"
223
+ responseLabel="输出结果"
224
+ defaultExpanded
225
+ />
226
+ )
227
+ expect(screen.getByText('输出结果')).toBeInTheDocument()
228
+ })
229
+ })
230
+
231
+ // ============================================================
232
+ // 错误状态样式测试
233
+ // ============================================================
234
+ describe('错误状态', () => {
235
+ it('error 状态响应应应用错误样式', () => {
236
+ render(
237
+ <ToolInvocationCard
238
+ toolName="test"
239
+ status="error"
240
+ response="Error message"
241
+ defaultExpanded
242
+ />
243
+ )
244
+ const errorText = screen.getByText('Error message')
245
+ expect(errorText).toHaveClass('text-error')
246
+ })
247
+
248
+ it('success 状态响应不应有错误样式', () => {
249
+ render(
250
+ <ToolInvocationCard
251
+ toolName="test"
252
+ status="success"
253
+ response="Success message"
254
+ defaultExpanded
255
+ />
256
+ )
257
+ const successText = screen.getByText('Success message')
258
+ expect(successText).not.toHaveClass('text-error')
259
+ })
260
+ })
261
+
262
+ // ============================================================
263
+ // className 测试
264
+ // ============================================================
265
+ describe('className', () => {
266
+ it('应合并自定义 className', () => {
267
+ const { container } = render(
268
+ <ToolInvocationCard
269
+ toolName="test"
270
+ status="success"
271
+ className="custom-card"
272
+ />
273
+ )
274
+ expect(container.firstChild).toHaveClass('custom-card')
275
+ })
276
+ })
277
+
278
+ // ============================================================
279
+ // 自定义图标 props 测试
280
+ // ============================================================
281
+ describe('自定义图标 props', () => {
282
+ it('应接受自定义 timeIcon', () => {
283
+ render(
284
+ <ToolInvocationCard
285
+ toolName="test"
286
+ status="running"
287
+ timeIcon={<span data-testid="time-icon">⏱</span>}
288
+ />
289
+ )
290
+ expect(screen.getByTestId('time-icon')).toBeInTheDocument()
291
+ })
292
+
293
+ it('应接受自定义 closeCircleIcon', () => {
294
+ render(
295
+ <ToolInvocationCard
296
+ toolName="test"
297
+ status="error"
298
+ closeCircleIcon={<span data-testid="close-icon">✗</span>}
299
+ />
300
+ )
301
+ expect(screen.getByTestId('close-icon')).toBeInTheDocument()
302
+ })
303
+
304
+ it('应接受自定义 arrowRightIcon', () => {
305
+ render(
306
+ <ToolInvocationCard
307
+ toolName="test"
308
+ status="success"
309
+ response="Content"
310
+ arrowRightIcon={<span data-testid="arrow-icon">→</span>}
311
+ />
312
+ )
313
+ expect(screen.getByTestId('arrow-icon')).toBeInTheDocument()
314
+ })
315
+ })
316
+
317
+ // ============================================================
318
+ // 组合场景测试
319
+ // ============================================================
320
+ describe('组合场景', () => {
321
+ it('应支持所有 props 组合', () => {
322
+ render(
323
+ <ToolInvocationCard
324
+ toolName="full_test"
325
+ params="/path/to/file.ts"
326
+ status="success"
327
+ response="File content here..."
328
+ defaultExpanded
329
+ statusText="已完成"
330
+ responseLabel="文件内容"
331
+ className="test-class"
332
+ checkboxCircleIcon={<span data-testid="check">✓</span>}
333
+ arrowRightIcon={<span data-testid="arrow">→</span>}
334
+ />
335
+ )
336
+
337
+ expect(screen.getByText('full_test')).toBeInTheDocument()
338
+ expect(screen.getByText('/path/to/file.ts')).toBeInTheDocument()
339
+ expect(screen.getByText('已完成')).toBeInTheDocument()
340
+ expect(screen.getByText('文件内容')).toBeInTheDocument()
341
+ expect(screen.getByText('File content here...')).toBeInTheDocument()
342
+ expect(screen.getByTestId('check')).toBeInTheDocument()
343
+ expect(screen.getByTestId('arrow')).toBeInTheDocument()
344
+ })
345
+ })
346
+ })
@@ -2,12 +2,12 @@ import { forwardRef } from 'react'
2
2
  import type { HTMLAttributes, ReactNode } from 'react'
3
3
  import { cn } from '@/lib/utils'
4
4
 
5
- export interface RequestProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
5
+ export interface UserMessageProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
6
6
  children: ReactNode
7
7
  maxWidth?: string | number
8
8
  }
9
9
 
10
- export const Request = forwardRef<HTMLDivElement, RequestProps>(
10
+ export const UserMessage = forwardRef<HTMLDivElement, UserMessageProps>(
11
11
  ({ children, className, maxWidth = '752px', ...props }, ref) => {
12
12
  const containerStyles = cn(
13
13
  'w-full',
@@ -35,4 +35,4 @@ export const Request = forwardRef<HTMLDivElement, RequestProps>(
35
35
  }
36
36
  )
37
37
 
38
- Request.displayName = 'Request'
38
+ UserMessage.displayName = 'UserMessage'