main
dart 133 lines 3.86 KB
Raw
1 import 'dart:convert';
2
3 import 'package:flutter/foundation.dart';
4 import 'package:flutter/services.dart';
5 import 'package:flutter_riverpod/flutter_riverpod.dart';
6 import 'package:flutter_test/flutter_test.dart';
7 import 'package:localmind/features/stt/providers/stt_providers.dart';
8 import 'package:localmind/features/stt/utils/stt_error_messages.dart';
9 import 'package:speech_to_text/speech_to_text.dart';
10 import 'package:speech_to_text_platform_interface/speech_to_text_platform_interface.dart';
11
12 void main() {
13 TestWidgetsFlutterBinding.ensureInitialized();
14
15 late _FakeSpeechPlatform platform;
16 late SpeechToTextPlatform original;
17 late ProviderContainer container;
18
19 setUp(() {
20 original = SpeechToTextPlatform.instance;
21 platform = _FakeSpeechPlatform();
22 SpeechToTextPlatform.instance = platform;
23 container = ProviderContainer(
24 overrides: [
25 sttProvider.overrideWith(
26 () => SttNotifier(speechFactory: SpeechToText.withMethodChannel),
27 ),
28 ],
29 );
30 });
31
32 tearDown(() {
33 container.dispose();
34 SpeechToTextPlatform.instance = original;
35 debugDefaultTargetPlatformOverride = null;
36 });
37
38 Future<void> startListening() =>
39 container.read(sttProvider.notifier).startListening(onResult: (_) {});
40
41 Future<void> waitForRetry() => Future<void>.delayed(
42 SttNotifier.clientErrorRetryDelay + const Duration(milliseconds: 50),
43 );
44
45 test('retries once on error_client with the on-device recognizer', () async {
46 debugDefaultTargetPlatformOverride = TargetPlatform.android;
47 await startListening();
48 expect(platform.onDeviceFlags, [false]);
49
50 platform.emitError('error_client');
51 await waitForRetry();
52
53 expect(platform.onDeviceFlags, [false, true]);
54 expect(container.read(sttProvider).error, isNull);
55 });
56
57 test('surfaces error_client when the retry fails too', () async {
58 debugDefaultTargetPlatformOverride = TargetPlatform.android;
59 await startListening();
60 platform.emitError('error_client');
61 await waitForRetry();
62 platform.emitError('error_client');
63 await Future<void>.delayed(Duration.zero);
64
65 expect(platform.onDeviceFlags, [false, true]);
66 expect(container.read(sttProvider).error, 'error_client');
67
68 // Neither recognizer worked, so the next attempt uses the default one.
69 await startListening();
70 expect(platform.onDeviceFlags.last, isFalse);
71 });
72
73 test('does not retry after the caller stopped listening', () async {
74 await startListening();
75 platform.emitError('error_client');
76 await container.read(sttProvider.notifier).cancelListening();
77 await waitForRetry();
78
79 expect(platform.onDeviceFlags, hasLength(1));
80 });
81
82 test('reports a missing recognizer as stt_unavailable', () async {
83 platform.initError = PlatformException(code: 'recognizerNotAvailable');
84 final available = await container.read(sttProvider.notifier).initSpeech();
85
86 expect(available, isFalse);
87 expect(container.read(sttProvider).error, sttUnavailableCode);
88 });
89 }
90
91 class _FakeSpeechPlatform extends SpeechToTextPlatform {
92 final List<bool> onDeviceFlags = [];
93 PlatformException? initError;
94
95 void emitError(String code) {
96 onError?.call(jsonEncode({'errorMsg': code, 'permanent': true}));
97 }
98
99 @override
100 Future<bool> hasPermission() async => true;
101
102 @override
103 Future<bool> initialize({
104 debugLogging = false,
105 List<SpeechConfigOption>? options,
106 }) async {
107 final error = initError;
108 if (error != null) throw error;
109 return true;
110 }
111
112 @override
113 Future<bool> listen({
114 String? localeId,
115 partialResults = true,
116 onDevice = false,
117 int listenMode = 0,
118 sampleRate = 0,
119 SpeechListenOptions? options,
120 }) async {
121 onDeviceFlags.add(options?.onDevice ?? onDevice as bool);
122 return true;
123 }
124
125 @override
126 Future<void> stop() async {}
127
128 @override
129 Future<void> cancel() async {}
130
131 @override
132 Future<List<dynamic>> locales() async => [];
133 }