fix: 优化语音识别功能,增加转写中状态和错误处理

This commit is contained in:
qzl
2026-03-10 17:43:28 +08:00
parent f30bfc2006
commit 2ec0965322
2 changed files with 153 additions and 24 deletions
@@ -1,3 +1,6 @@
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';
@@ -17,7 +20,8 @@ class _FakeVoiceRecorder implements VoiceRecorder {
@override
Future<String?> stop() async {
started = false;
stoppedPath = '/tmp/test-audio.wav';
stoppedPath ??=
'${Directory.systemTemp.path}/test-audio-${DateTime.now().microsecondsSinceEpoch}.wav';
return stoppedPath;
}
@@ -90,7 +94,7 @@ void main() {
voiceRecorder: fakeRecorder,
autoLoadHistory: false,
onTranscribeAudio: (filePath) async {
expect(filePath, '/tmp/test-audio.wav');
expect(filePath.endsWith('.wav'), true);
return '语音自动发送';
},
onAutoSendTranscript: (transcript) async {
@@ -104,13 +108,13 @@ void main() {
await tester.tap(find.byIcon(LucideIcons.mic));
await tester.pump();
await tester.tap(find.byIcon(LucideIcons.send));
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(sentTranscript, '语音自动发送');
expect(find.byIcon(LucideIcons.plus), findsOneWidget);
});
testWidgets('tap stop transcribes audio and fills input', (
testWidgets('tap stop enters transcribing state', (
WidgetTester tester,
) async {
final fakeRecorder = _FakeVoiceRecorder();
@@ -120,7 +124,7 @@ void main() {
voiceRecorder: fakeRecorder,
autoLoadHistory: false,
onTranscribeAudio: (filePath) async {
expect(filePath, '/tmp/test-audio.wav');
expect(filePath.endsWith('.wav'), true);
return '语音转文字结果';
},
),
@@ -131,10 +135,10 @@ void main() {
await tester.tap(find.byIcon(LucideIcons.mic));
await tester.pump();
await tester.tap(find.byIcon(LucideIcons.square));
await tester.pumpAndSettle();
await tester.pump();
expect(find.text('语音转文字结果'), findsOneWidget);
expect(find.byIcon(LucideIcons.plus), findsOneWidget);
expect(find.text('语音识别中...'), findsOneWidget);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
});
testWidgets('tap stop shows readable unauthorized message', (
@@ -157,10 +161,91 @@ void main() {
await tester.tap(find.byIcon(LucideIcons.mic));
await tester.pump();
await tester.tap(find.byIcon(LucideIcons.square));
await tester.pumpAndSettle();
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));
});
});
}