fix(chat): fix ChatBloc event callback and test reliability

- Fix onEvent callback initialization in ChatBloc constructor
- Add MockAgUiService to isolate test from mock API behavior
- Remove unnecessary non-null assertions in tests
This commit is contained in:
qzl
2026-02-28 14:41:21 +08:00
parent 92781ddbbe
commit d37677c533
9 changed files with 254 additions and 217 deletions
@@ -8,6 +8,18 @@ import '../models/ag_ui_event.dart';
import '../models/tool_result.dart';
import '../tools/tool_registry.dart';
/// Mock ID 前缀常量
const _threadIdPrefix = 'thread_';
const _runIdPrefix = 'run_';
const _toolCallIdPrefix = 'tc_';
const _messageIdPrefix = 'msg_';
/// 流式输出延迟 (毫秒)
const _streamChunkDelayMs = 50;
/// 文本块大小
const _textChunkSize = 10;
typedef EventCallback = void Function(AgUiEvent event);
class AgUiService {
@@ -27,8 +39,8 @@ class AgUiService {
}
Future<void> _mockEventStream(String content) async {
final threadId = 'thread_${DateTime.now().millisecondsSinceEpoch}';
final runId = 'run_${DateTime.now().millisecondsSinceEpoch}';
final threadId = '$_threadIdPrefix${DateTime.now().millisecondsSinceEpoch}';
final runId = '$_runIdPrefix${DateTime.now().millisecondsSinceEpoch}';
onEvent(RunStartedEvent(threadId: threadId, runId: runId));
@@ -58,7 +70,8 @@ class AgUiService {
String toolName,
Map<String, dynamic> args,
) async {
final toolCallId = 'tc_${DateTime.now().millisecondsSinceEpoch}';
final toolCallId =
'$_toolCallIdPrefix${DateTime.now().millisecondsSinceEpoch}';
onEvent(ToolCallStartEvent(toolCallId: toolCallId, toolCallName: toolName));
@@ -82,7 +95,8 @@ class AgUiService {
ToolRegistry.initialize();
final result = await ToolRegistry.execute(toolName, args);
final ui = _buildUiCard(toolName, result);
final messageId = 'msg_${DateTime.now().millisecondsSinceEpoch}';
final messageId =
'$_messageIdPrefix${DateTime.now().millisecondsSinceEpoch}';
onEvent(
ToolCallResultEvent(
@@ -145,20 +159,20 @@ class AgUiService {
Future<void> _mockTextMessageStream(List<String> replies) async {
for (final reply in replies) {
final messageId = 'msg_${DateTime.now().millisecondsSinceEpoch}';
final messageId =
'$_messageIdPrefix${DateTime.now().millisecondsSinceEpoch}';
onEvent(TextMessageStartEvent(messageId: messageId, role: 'assistant'));
const chunkSize = 10;
for (var i = 0; i < reply.length; i += chunkSize) {
final end = (i + chunkSize < reply.length)
? i + chunkSize
for (var i = 0; i < reply.length; i += _textChunkSize) {
final end = (i + _textChunkSize < reply.length)
? i + _textChunkSize
: reply.length;
final chunk = reply.substring(i, end);
onEvent(TextMessageContentEvent(messageId: messageId, delta: chunk));
await Future.delayed(const Duration(milliseconds: 50));
await Future.delayed(const Duration(milliseconds: _streamChunkDelayMs));
}
onEvent(TextMessageEndEvent(messageId: messageId));