@@ -493,4 +493,85 @@ mod tests {
493493 let has_tool_call_text = snap. iter ( ) . any ( |l| l. contains ( "bash" ) || l. contains ( "Bash" ) ) ;
494494 assert ! ( has_tool_call_text, "ToolCall 创建的 ToolBlock 应在快照中可见,但实际内容为:\n {}" , snap. join( "\n " ) ) ;
495495 }
496+
497+ #[ tokio:: test]
498+ async fn test_empty_assistant_chunk_no_bubble ( ) {
499+ // 空 AssistantChunk 不应创建空白的 AssistantBubble
500+ let ( mut app, _handle) = App :: new_headless ( 120 , 30 ) ;
501+
502+ // 发送空 chunk,不应创建 AssistantBubble
503+ app. push_agent_event ( AgentEvent :: AssistantChunk ( "" . into ( ) ) ) ;
504+ app. process_pending_events ( ) ;
505+
506+ // view_messages 应为空(没有创建空白气泡)
507+ assert ! (
508+ app. view_messages. is_empty( ) ,
509+ "空 AssistantChunk 不应创建 AssistantBubble,实际: {:?}" ,
510+ app. view_messages. len( )
511+ ) ;
512+
513+ // 发送多个空 chunk,仍不应创建气泡
514+ app. push_agent_event ( AgentEvent :: AssistantChunk ( "" . into ( ) ) ) ;
515+ app. push_agent_event ( AgentEvent :: AssistantChunk ( "" . into ( ) ) ) ;
516+ app. process_pending_events ( ) ;
517+
518+ assert ! (
519+ app. view_messages. is_empty( ) ,
520+ "多个空 AssistantChunk 仍不应创建 AssistantBubble"
521+ ) ;
522+ }
523+
524+ #[ tokio:: test]
525+ async fn test_empty_then_nonempty_assistant_chunk ( ) {
526+ // 空_chunk → 非空_chunk:非空 chunk 应正常创建气泡
527+ let ( mut app, mut handle) = App :: new_headless ( 120 , 30 ) ;
528+
529+ // 先发送空 chunk
530+ app. push_agent_event ( AgentEvent :: AssistantChunk ( "" . into ( ) ) ) ;
531+ app. process_pending_events ( ) ;
532+
533+ // 再发送非空 chunk
534+ let notify = Arc :: clone ( & handle. render_notify ) ;
535+ let n1 = notify. notified ( ) ;
536+ let n2 = notify. notified ( ) ;
537+ app. push_agent_event ( AgentEvent :: AssistantChunk ( "Hello" . into ( ) ) ) ;
538+ app. push_agent_event ( AgentEvent :: Done ) ;
539+ app. process_pending_events ( ) ;
540+ tokio:: join!( n1, n2) ;
541+
542+ handle. terminal . draw ( |f| main_ui:: render ( f, & mut app) ) . unwrap ( ) ;
543+
544+ // 应该只有 1 个 AssistantBubble,内容为 "Hello"
545+ assert_eq ! ( app. view_messages. len( ) , 1 , "应只有 1 条消息" ) ;
546+ assert ! ( app. view_messages[ 0 ] . is_assistant( ) , "应为 AssistantBubble" ) ;
547+ assert ! ( handle. contains( "Hello" ) , "应显示 Hello 内容" ) ;
548+ }
549+
550+ #[ tokio:: test]
551+ async fn test_tool_call_without_assistant_chunk_no_bubble ( ) {
552+ // 模拟 AI 只调用工具不输出文本的场景
553+ let ( mut app, mut handle) = App :: new_headless ( 120 , 30 ) ;
554+
555+ // 直接发送 ToolCall 事件(无 AssistantChunk)
556+ let notified = handle. render_notify . notified ( ) ;
557+ app. push_agent_event ( AgentEvent :: ToolCall {
558+ tool_call_id : "tc1" . into ( ) ,
559+ name : "bash" . into ( ) ,
560+ display : "Bash" . into ( ) ,
561+ args : Some ( "ls" . into ( ) ) ,
562+ is_error : false ,
563+ } ) ;
564+ app. process_pending_events ( ) ;
565+ notified. await ;
566+
567+ handle. terminal . draw ( |f| main_ui:: render ( f, & mut app) ) . unwrap ( ) ;
568+
569+ // 应该有 1 个 ToolBlock,不应有空白 AssistantBubble
570+ assert_eq ! ( app. view_messages. len( ) , 1 , "应有 1 条消息(ToolBlock)" ) ;
571+ // 确保不是 AssistantBubble(空白气泡)
572+ assert ! (
573+ !app. view_messages[ 0 ] . is_assistant( ) ,
574+ "不应创建 AssistantBubble,应为 ToolBlock"
575+ ) ;
576+ }
496577}
0 commit comments