252 lines
7.7 KiB
Dart
252 lines
7.7 KiB
Dart
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<void> start() async {
|
|
started = true;
|
|
}
|
|
|
|
@override
|
|
Future<String?> stop() async {
|
|
started = false;
|
|
stoppedPath ??=
|
|
'${Directory.systemTemp.path}/test-audio-${DateTime.now().microsecondsSinceEpoch}.wav';
|
|
return stoppedPath;
|
|
}
|
|
|
|
@override
|
|
Future<void> 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<String>();
|
|
|
|
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<EditableTextState>(
|
|
find.byType(EditableText),
|
|
);
|
|
expect(editableBefore.widget.focusNode.hasFocus, isTrue);
|
|
|
|
await tester.tap(find.byIcon(LucideIcons.send));
|
|
await tester.pump();
|
|
|
|
final editableAfter = tester.state<EditableTextState>(
|
|
find.byType(EditableText),
|
|
);
|
|
expect(editableAfter.widget.focusNode.hasFocus, isFalse);
|
|
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
});
|
|
});
|
|
}
|