// 功能描述:语音结果选区替换、重复中间结果、拒绝权限、离页取消与长度边界回归。 // 版本:1.0.0。 import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:user_app/data/services/repair_speech.dart'; import 'package:user_app/ui/features/tickets/repair_speech_button.dart'; class _Speech implements RepairSpeech { bool available = true; int cancels = 0, starts = 0; Completer? pending; late void Function(String) words, error; late void Function() done; @override Future start({ required void Function(String) onWords, required void Function(String) onError, required void Function() onDone, }) async { starts++; words = onWords; error = onError; done = onDone; return pending?.future ?? available; } @override Future stop() async { done(); } @override Future cancel() async { cancels++; } } void main() { testWidgets('权限弹窗不取消初始化,切后台立即取消', (tester) async { final speech = _Speech()..pending = Completer(); final controller = TextEditingController(text: '保留'); await tester.pumpWidget( MaterialApp( home: Scaffold( body: RepairSpeechButton(controller: controller, onBusy: (_) {}, speech: speech), ), ), ); await tester.tap(find.text('语音输入')); await tester.pump(); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.inactive); await tester.pump(); expect(speech.cancels, 0); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.paused); await tester.pump(); expect(speech.cancels, 1); speech.pending!.complete(true); speech.words('迟到'); await tester.pump(); expect(controller.text, '保留'); tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); await tester.pumpWidget(const SizedBox()); controller.dispose(); }); testWidgets('选区替换和中间结果不重复追加,结束后可再次录入', (tester) async { final speech = _Speech(), controller = TextEditingController(text: '前旧后'); controller.selection = const TextSelection(baseOffset: 1, extentOffset: 2); final busy = []; await tester.pumpWidget( MaterialApp( home: Scaffold( body: RepairSpeechButton(controller: controller, onBusy: busy.add, speech: speech), ), ), ); await tester.tap(find.text('语音输入')); await tester.pump(); speech.words('阀门'); speech.words('阀门异响'); await tester.pump(); expect(controller.text, '前阀门异响后'); await tester.tap(find.text('结束识别')); await tester.pump(); speech.words('迟到结果'); expect(controller.text, '前阀门异响后'); expect(busy.last, false); await tester.tap(find.text('语音输入')); await tester.pump(); speech.words('补充'); speech.done(); await tester.pump(); expect(controller.text, '前阀门异响补充后'); expect(speech.starts, 2); await tester.pumpWidget(const SizedBox()); controller.dispose(); }); testWidgets('不可用和权限拒绝保留输入并解除忙碌', (tester) async { final speech = _Speech()..available = false, controller = TextEditingController(text: '已有内容'); final busy = []; await tester.pumpWidget( MaterialApp( home: Scaffold( body: RepairSpeechButton(controller: controller, onBusy: busy.add, speech: speech), ), ), ); await tester.tap(find.text('语音输入')); await tester.pump(); expect(find.textContaining('无法使用语音识别'), findsOneWidget); expect(controller.text, '已有内容'); expect(busy.last, false); speech.available = true; await tester.tap(find.text('语音输入')); await tester.pump(); speech.error('error_permission'); await tester.pump(); expect(find.textContaining('未获得麦克风'), findsOneWidget); expect(controller.text, '已有内容'); expect(busy.last, false); await tester.pumpWidget(const SizedBox()); controller.dispose(); }); testWidgets('初始化中离页取消,迟到结果不改变文字', (tester) async { final speech = _Speech()..pending = Completer(), controller = TextEditingController(text: '保留'); await tester.pumpWidget( MaterialApp( home: Scaffold( body: RepairSpeechButton(controller: controller, onBusy: (_) {}, speech: speech), ), ), ); await tester.tap(find.text('语音输入')); await tester.pump(); await tester.pumpWidget(const SizedBox()); speech.pending!.complete(true); speech.words('迟到'); await tester.pump(); expect(speech.cancels, 1); expect(controller.text, '保留'); expect(tester.takeException(), isNull); controller.dispose(); }); testWidgets('识别遵守2000字符上限,不截断表情字符', (tester) async { final speech = _Speech(), controller = TextEditingController(text: List.filled(1999, '字').join()); await tester.pumpWidget( MaterialApp( home: Scaffold( body: RepairSpeechButton(controller: controller, onBusy: (_) {}, speech: speech), ), ), ); await tester.tap(find.text('语音输入')); await tester.pump(); speech.words('😀多余'); speech.done(); await tester.pump(); expect(controller.text.characters.length, 2000); expect(controller.text.endsWith('😀'), true); await tester.pumpWidget(const SizedBox()); controller.dispose(); }); }