01c36eb32e
- 删除 mock_api_client、mock_calendar_service、mock_history_service - 新增 fixed_length_code_input、link_button、message_composer 共享组件 - 优化登录/注册/密码重置页面使用新组件 - 简化 injection.dart 移除 mock 分支 - 更新 env.dart 配置(BACKEND_URL 替换 API_URL) - 后端 agentscope 工具和测试更新 - 重构 AGENTS.md 文档结构 - 新增 deploy/ 目录和 protocol 文档
238 lines
6.8 KiB
Dart
238 lines
6.8 KiB
Dart
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
import 'package:social_app/shared/widgets/message_composer.dart';
|
|
|
|
Widget _buildTestApp({
|
|
required MessageComposerMode mode,
|
|
required MessageComposerProcess process,
|
|
required bool hasMessage,
|
|
required bool isWaitingAgent,
|
|
VoidCallback? onHoldStart,
|
|
VoidCallback? onHoldEnd,
|
|
VoidCallback? onHoldCancel,
|
|
}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: MessageComposer(
|
|
mode: mode,
|
|
process: process,
|
|
hasMessage: hasMessage,
|
|
isWaitingAgent: isWaitingAgent,
|
|
iconSize: 24,
|
|
composerMinHeight: 48,
|
|
onTapPlus: () {},
|
|
onTapRightAction: () {},
|
|
onHoldToSpeakStart: onHoldStart ?? () {},
|
|
onHoldToSpeakEnd: onHoldEnd ?? () {},
|
|
onHoldToSpeakMoveUpdate: (_) {},
|
|
onHoldToSpeakCancel: onHoldCancel ?? () {},
|
|
textInputChild: const SizedBox.shrink(),
|
|
recordingAnimation: const SizedBox.shrink(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
group('MessageComposer', () {
|
|
testWidgets('renders one unified rounded composer container', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.text,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
);
|
|
|
|
expect(find.byKey(messageComposerContainerKey), findsOneWidget);
|
|
|
|
final containerFinder = find.byKey(messageComposerContainerKey);
|
|
final plusFinder = find.byKey(messageComposerPlusButtonKey);
|
|
final rightFinder = find.byKey(messageComposerRightButtonKey);
|
|
|
|
expect(
|
|
find.descendant(of: containerFinder, matching: plusFinder),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: containerFinder, matching: rightFinder),
|
|
findsOneWidget,
|
|
);
|
|
});
|
|
|
|
testWidgets('right action icon follows state priority', (tester) async {
|
|
Future<IconData> rightIconFor({
|
|
required MessageComposerMode mode,
|
|
required MessageComposerProcess process,
|
|
required bool hasMessage,
|
|
required bool isWaitingAgent,
|
|
}) async {
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: mode,
|
|
process: process,
|
|
hasMessage: hasMessage,
|
|
isWaitingAgent: isWaitingAgent,
|
|
),
|
|
);
|
|
|
|
final iconFinder = find.descendant(
|
|
of: find.byKey(messageComposerRightButtonKey),
|
|
matching: find.byType(Icon),
|
|
);
|
|
final iconWidget = tester.widget<Icon>(iconFinder.first);
|
|
return iconWidget.icon!;
|
|
}
|
|
|
|
expect(
|
|
await rightIconFor(
|
|
mode: MessageComposerMode.text,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: true,
|
|
),
|
|
LucideIcons.square,
|
|
);
|
|
|
|
expect(
|
|
await rightIconFor(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: true,
|
|
isWaitingAgent: false,
|
|
),
|
|
LucideIcons.send,
|
|
);
|
|
|
|
expect(
|
|
await rightIconFor(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
LucideIcons.keyboard,
|
|
);
|
|
|
|
expect(
|
|
await rightIconFor(
|
|
mode: MessageComposerMode.text,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
LucideIcons.mic,
|
|
);
|
|
});
|
|
|
|
testWidgets('recording hint appears only while recording', (tester) async {
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
);
|
|
expect(find.byKey(messageComposerRecordingHintKey), findsNothing);
|
|
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.recording,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
);
|
|
expect(find.byKey(messageComposerRecordingHintKey), findsOneWidget);
|
|
expect(find.text('松开发送,上滑取消'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('composer height remains stable across mode switches', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.text,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
);
|
|
final textHeight = tester.getSize(
|
|
find.byKey(messageComposerContainerKey),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
),
|
|
);
|
|
final holdHeight = tester.getSize(
|
|
find.byKey(messageComposerContainerKey),
|
|
);
|
|
|
|
expect(textHeight.height, holdHeight.height);
|
|
});
|
|
|
|
testWidgets('invokes long press start/end callbacks in hold mode', (
|
|
tester,
|
|
) async {
|
|
var started = false;
|
|
var ended = false;
|
|
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
onHoldStart: () => started = true,
|
|
onHoldEnd: () => ended = true,
|
|
),
|
|
);
|
|
|
|
final center = tester.getCenter(find.byKey(messageComposerHoldAreaKey));
|
|
final gesture = await tester.startGesture(center);
|
|
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 10));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
expect(started, isTrue);
|
|
expect(ended, isTrue);
|
|
});
|
|
|
|
testWidgets('invokes long press cancel callback when gesture canceled', (
|
|
tester,
|
|
) async {
|
|
var canceled = false;
|
|
|
|
await tester.pumpWidget(
|
|
_buildTestApp(
|
|
mode: MessageComposerMode.holdToSpeak,
|
|
process: MessageComposerProcess.idle,
|
|
hasMessage: false,
|
|
isWaitingAgent: false,
|
|
onHoldCancel: () => canceled = true,
|
|
),
|
|
);
|
|
|
|
final center = tester.getCenter(find.byKey(messageComposerHoldAreaKey));
|
|
final gesture = await tester.startGesture(center);
|
|
await tester.pump(kLongPressTimeout + const Duration(milliseconds: 10));
|
|
await gesture.cancel();
|
|
await tester.pump();
|
|
|
|
expect(canceled, isTrue);
|
|
});
|
|
});
|
|
}
|