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
@@ -2,16 +2,19 @@ import 'package:flutter/material.dart';
import 'package:social_app/core/theme/design_tokens.dart';
import '../../data/models/tool_result.dart';
/// 卡片类型常量
const _calendarCardType = 'calendar_card.v1';
const _errorCardType = 'error_card.v1';
const _aiGeneratedSource = 'ai_generated';
const _primaryActionType = 'primary';
class UiSchemaRenderer {
static Widget render(UiCard card) {
switch (card.cardType) {
case 'calendar_card.v1':
return _renderCalendarCard(card);
case 'error_card.v1':
return _renderErrorCard(card);
default:
return _renderUnknownCard(card);
}
return switch (card.cardType) {
_calendarCardType => _renderCalendarCard(card),
_errorCardType => _renderErrorCard(card),
_ => _renderUnknownCard(card),
};
}
static Widget _renderCalendarCard(UiCard card) {
@@ -19,6 +22,7 @@ class UiSchemaRenderer {
final color = data.color != null
? Color(int.parse(data.color!.replaceFirst('#', '0xFF')))
: AppColors.blue500;
final isAiGenerated = data.sourceType == _aiGeneratedSource;
return Container(
decoration: BoxDecoration(
@@ -44,23 +48,10 @@ class UiSchemaRenderer {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (data.sourceType == 'ai_generated')
Container(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: AppColors.messageTagBg,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'AI生成',
style: TextStyle(fontSize: 10, color: AppColors.blue600),
),
),
if (data.sourceType == 'ai_generated')
if (isAiGenerated) ...[
_buildAiTag(),
SizedBox(height: AppSpacing.sm),
],
Text(
_formatTime(data.startAt, data.endAt),
style: TextStyle(fontSize: 12, color: AppColors.slate500),
@@ -83,32 +74,11 @@ class UiSchemaRenderer {
],
if (data.location != null) ...[
SizedBox(height: AppSpacing.sm),
Row(
children: [
Icon(
Icons.location_on_outlined,
size: 16,
color: AppColors.slate500,
),
SizedBox(width: AppSpacing.xs),
Text(
data.location!,
style: TextStyle(
fontSize: 12,
color: AppColors.slate500,
),
),
],
),
_buildLocation(data.location!),
],
if (card.actions != null && card.actions!.isNotEmpty) ...[
SizedBox(height: AppSpacing.md),
Wrap(
spacing: AppSpacing.sm,
children: card.actions!
.map((action) => _buildActionButton(action))
.toList(),
),
_buildActions(card.actions!),
],
],
),
@@ -118,8 +88,45 @@ class UiSchemaRenderer {
);
}
static Widget _buildAiTag() {
return Container(
padding: EdgeInsets.symmetric(
horizontal: AppSpacing.sm,
vertical: AppSpacing.xs,
),
decoration: BoxDecoration(
color: AppColors.messageTagBg,
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'AI生成',
style: TextStyle(fontSize: 10, color: AppColors.blue600),
),
);
}
static Widget _buildLocation(String location) {
return Row(
children: [
Icon(Icons.location_on_outlined, size: 16, color: AppColors.slate500),
SizedBox(width: AppSpacing.xs),
Text(
location,
style: TextStyle(fontSize: 12, color: AppColors.slate500),
),
],
);
}
static Widget _buildActions(List<CardAction> actions) {
return Wrap(
spacing: AppSpacing.sm,
children: actions.map((action) => _buildActionButton(action)).toList(),
);
}
static Widget _buildActionButton(CardAction action) {
final isPrimary = action.type == 'primary';
final isPrimary = action.type == _primaryActionType;
return GestureDetector(
onTap: () => _handleAction(action),
child: Container(