已完成用户APP首期功能开发

交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
czl231
2026-09-13 00:57:32 +08:00
parent a0a4bb1218
commit 3ef33b531d
793 changed files with 40959 additions and 1204 deletions

View File

@@ -0,0 +1,165 @@
// 功能描述:语音结果选区替换、重复中间结果、拒绝权限、离页取消与长度边界回归。
// 版本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<bool>? pending;
late void Function(String) words, error;
late void Function() done;
@override
Future<bool> 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<void> stop() async {
done();
}
@override
Future<void> cancel() async {
cancels++;
}
}
void main() {
testWidgets('权限弹窗不取消初始化,切后台立即取消', (tester) async {
final speech = _Speech()..pending = Completer<bool>();
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 = <bool>[];
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 = <bool>[];
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<bool>(),
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();
});
}