import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:social_app/core/api/api_exception.dart'; import 'package:social_app/features/home/data/voice_recorder.dart'; import 'package:social_app/features/home/ui/screens/home_screen.dart'; class _FakeVoiceRecorder implements VoiceRecorder { bool started = false; String? stoppedPath; @override Future start() async { started = true; } @override Future stop() async { started = false; stoppedPath ??= '${Directory.systemTemp.path}/test-audio-${DateTime.now().microsecondsSinceEpoch}.wav'; return stoppedPath; } @override Future dispose() async {} } void main() { group('HomeScreen Widget Tests', () { testWidgets('displays input field', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp(home: HomeScreen(autoLoadHistory: false)), ); await tester.pumpAndSettle(); expect(find.byType(TextField), findsOneWidget); expect(find.text('输入消息...'), findsOneWidget); }); testWidgets('displays header icons', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp(home: HomeScreen(autoLoadHistory: false)), ); await tester.pumpAndSettle(); expect(find.byIcon(LucideIcons.settings), findsOneWidget); expect(find.byIcon(LucideIcons.calendar), findsOneWidget); expect(find.byIcon(LucideIcons.messageSquare), findsOneWidget); }); testWidgets('displays send or mic icon based on input', ( WidgetTester tester, ) async { await tester.pumpWidget( const MaterialApp(home: HomeScreen(autoLoadHistory: false)), ); await tester.pumpAndSettle(); expect(find.byIcon(LucideIcons.mic), findsOneWidget); }); testWidgets('tap mic starts recording and shows listening state', ( WidgetTester tester, ) async { final fakeRecorder = _FakeVoiceRecorder(); await tester.pumpWidget( MaterialApp( home: HomeScreen(voiceRecorder: fakeRecorder, autoLoadHistory: false), ), ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.mic)); await tester.pump(); expect(fakeRecorder.started, true); expect(find.text('正在聆听...'), findsOneWidget); expect(find.byIcon(LucideIcons.square), findsOneWidget); expect(find.byIcon(LucideIcons.send), findsOneWidget); }); testWidgets('tap send while recording transcribes and auto sends message', ( WidgetTester tester, ) async { final fakeRecorder = _FakeVoiceRecorder(); String? sentTranscript; await tester.pumpWidget( MaterialApp( home: HomeScreen( voiceRecorder: fakeRecorder, autoLoadHistory: false, onTranscribeAudio: (filePath) async { expect(filePath.endsWith('.wav'), true); return '语音自动发送'; }, onAutoSendTranscript: (transcript) async { sentTranscript = transcript; }, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.mic)); await tester.pump(); await tester.tap(find.byIcon(LucideIcons.send)); await tester.pump(const Duration(milliseconds: 300)); expect(sentTranscript, '语音自动发送'); expect(find.byIcon(LucideIcons.plus), findsOneWidget); }); testWidgets('tap stop enters transcribing state', ( WidgetTester tester, ) async { final fakeRecorder = _FakeVoiceRecorder(); await tester.pumpWidget( MaterialApp( home: HomeScreen( voiceRecorder: fakeRecorder, autoLoadHistory: false, onTranscribeAudio: (filePath) async { expect(filePath.endsWith('.wav'), true); return '语音转文字结果'; }, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.mic)); await tester.pump(); await tester.tap(find.byIcon(LucideIcons.square)); await tester.pump(); expect(find.text('语音识别中...'), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsOneWidget); }); testWidgets('tap stop shows readable unauthorized message', ( WidgetTester tester, ) async { final fakeRecorder = _FakeVoiceRecorder(); await tester.pumpWidget( MaterialApp( home: HomeScreen( voiceRecorder: fakeRecorder, autoLoadHistory: false, onTranscribeAudio: (_) async { throw const UnauthorizedException(); }, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.mic)); await tester.pump(); await tester.tap(find.byIcon(LucideIcons.square)); await tester.pump(const Duration(milliseconds: 300)); expect(find.text('请重新登录'), findsOneWidget); await tester.pump(const Duration(seconds: 3)); }); testWidgets('tap stop shows message when transcript is empty', ( WidgetTester tester, ) async { final fakeRecorder = _FakeVoiceRecorder(); await tester.pumpWidget( MaterialApp( home: HomeScreen( voiceRecorder: fakeRecorder, autoLoadHistory: false, onTranscribeAudio: (_) async => '', ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.mic)); await tester.pump(); await tester.tap(find.byIcon(LucideIcons.square)); await tester.pump(const Duration(milliseconds: 300)); expect(find.text('未识别到有效语音,请靠近麦克风并连续说话后重试'), findsOneWidget); await tester.pump(const Duration(seconds: 3)); }); testWidgets('shows transcribing indicator while waiting ASR result', ( WidgetTester tester, ) async { final fakeRecorder = _FakeVoiceRecorder(); final completer = Completer(); await tester.pumpWidget( MaterialApp( home: HomeScreen( voiceRecorder: fakeRecorder, autoLoadHistory: false, onTranscribeAudio: (_) => completer.future, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byIcon(LucideIcons.mic)); await tester.pump(); await tester.tap(find.byIcon(LucideIcons.square)); await tester.pump(); expect(find.text('语音识别中...'), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsOneWidget); completer.complete('识别完成'); }); testWidgets('tap send unfocuses text input after sending', ( WidgetTester tester, ) async { await tester.pumpWidget( const MaterialApp(home: HomeScreen(autoLoadHistory: false)), ); await tester.pumpAndSettle(); await tester.tap(find.byType(TextField)); await tester.pump(); await tester.enterText(find.byType(TextField), 'hello'); await tester.pump(); final editableBefore = tester.state( find.byType(EditableText), ); expect(editableBefore.widget.focusNode.hasFocus, isTrue); await tester.tap(find.byIcon(LucideIcons.send)); await tester.pump(); final editableAfter = tester.state( find.byType(EditableText), ); expect(editableAfter.widget.focusNode.hasFocus, isFalse); await tester.pump(const Duration(milliseconds: 300)); }); }); }