Files
pj_supermarket_cloud/test/face_recognition_panel_test.dart
T
2026-10-23 15:53:43 +08:00

259 lines
7.8 KiB
Dart

import 'dart:async';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:forui/forui.dart';
import 'package:pj_supermarket_cloud/common/models/plugs/face_discern_models.dart';
import 'package:pj_supermarket_cloud/common/models/plugs/face_recognition_session_models.dart';
import 'package:pj_supermarket_cloud/common/services/face_recognition_session_service.dart';
import 'package:pj_supermarket_cloud/common/widgets/face/face_recognition_panel.dart';
const _sessionId = 'panel-test-session';
const _faceChannel = MethodChannel('local/face_discern');
class FakeFaceRecognitionSessionService extends FaceRecognitionSessionService {
FakeFaceRecognitionSessionService({this.finishCompletion});
final Completer<void>? finishCompletion;
final result = Completer<FaceStream>();
bool _ownsSession = true;
int finishCalls = 0;
int cancelCalls = 0;
FaceRecognitionSessionHandle get handle => FaceRecognitionSessionHandle(
sessionId: _sessionId,
scene: FaceRecognitionScene.cashier,
events: const Stream<FaceRecognitionSessionEvent>.empty(),
result: result.future,
);
@override
bool owns(String? sessionId) => _ownsSession && sessionId == _sessionId;
@override
Future<void> finish() async {
finishCalls++;
await finishCompletion?.future;
_ownsSession = false;
}
@override
Future<void> cancel({String message = '已取消人脸识别'}) async {
cancelCalls++;
_ownsSession = false;
}
}
Widget _harness({
required FakeFaceRecognitionSessionService service,
required ValueChanged<FaceStream> onRecognized,
FaceRecognitionPanelController? controller,
VoidCallback? onCancelled,
}) {
final base = FThemes.zinc.light;
return ScreenUtilInit(
designSize: const Size(1920, 1080),
minTextAdapt: true,
builder: (_, __) => FTheme(
data: FThemeData.inherit(
colorScheme: base.colorScheme,
typography: base.typography,
style: base.style,
),
child: MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 600,
child: FaceRecognitionPanel(
service: service,
handle: service.handle,
controller: controller,
onRecognized: onRecognized,
onCancelled: onCancelled,
),
),
),
),
),
),
);
}
FaceStream _recognitionResult() => FaceStream(
similar: 0.96,
cost: 42,
sampleId: 123,
sessionId: _sessionId,
);
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (_) async => null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(_faceChannel, (call) async {
switch (call.method) {
case 'createPreviewTexture':
return {
'textureId': 7,
'width': 1280,
'height': 720,
'rotation': 0,
'mirror': false,
};
case 'releasePreviewTexture':
return null;
default:
throw PlatformException(code: 'UNEXPECTED_METHOD');
}
});
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null);
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(_faceChannel, null);
});
testWidgets('成功结果无需推进一秒即可结束会话并通知业务', (tester) async {
final service = FakeFaceRecognitionSessionService();
final recognized = <FaceStream>[];
await tester.pumpWidget(
_harness(service: service, onRecognized: recognized.add),
);
await tester.pump();
final beforeResult = tester.binding.clock.now();
final result = _recognitionResult();
service.result.complete(result);
await tester.pump();
expect(tester.binding.clock.now(), beforeResult);
expect(service.finishCalls, 1);
expect(recognized, [same(result)]);
expect(tester.takeException(), isNull);
});
testWidgets('会话结束尚未完成时不提前通知业务', (tester) async {
final finishCompletion = Completer<void>();
final service = FakeFaceRecognitionSessionService(
finishCompletion: finishCompletion,
);
final recognized = <FaceStream>[];
await tester.pumpWidget(
_harness(service: service, onRecognized: recognized.add),
);
await tester.pump();
final result = _recognitionResult();
service.result.complete(result);
await tester.pump();
expect(service.finishCalls, 1);
expect(recognized, isEmpty);
finishCompletion.complete();
await tester.pump();
expect(recognized, [same(result)]);
await tester.pump();
expect(recognized, hasLength(1));
expect(tester.takeException(), isNull);
});
testWidgets('结束会话期间销毁面板会忽略迟到成功结果', (tester) async {
final finishCompletion = Completer<void>();
final service = FakeFaceRecognitionSessionService(
finishCompletion: finishCompletion,
);
final recognized = <FaceStream>[];
await tester.pumpWidget(
_harness(service: service, onRecognized: recognized.add),
);
await tester.pump();
service.result.complete(_recognitionResult());
await tester.pump();
expect(service.finishCalls, 1);
await tester.pumpWidget(const SizedBox.shrink());
finishCompletion.complete();
await tester.pump();
expect(recognized, isEmpty);
expect(tester.takeException(), isNull);
});
testWidgets('成功结果到达前取消面板会忽略迟到结果', (tester) async {
final service = FakeFaceRecognitionSessionService();
final controller = FaceRecognitionPanelController();
final recognized = <FaceStream>[];
var cancelled = 0;
await tester.pumpWidget(
_harness(
service: service,
onRecognized: recognized.add,
controller: controller,
onCancelled: () => cancelled++,
),
);
await tester.pump();
final cancel = controller.cancel();
await tester.pump();
await cancel;
expect(service.cancelCalls, 1);
expect(cancelled, 1);
service.result.complete(_recognitionResult());
await tester.pump();
expect(service.finishCalls, 0);
expect(recognized, isEmpty);
expect(tester.takeException(), isNull);
});
testWidgets('成功结果锁定后重复取消不会打断结束会话或重复通知业务', (tester) async {
final finishCompletion = Completer<void>();
final service = FakeFaceRecognitionSessionService(
finishCompletion: finishCompletion,
);
final controller = FaceRecognitionPanelController();
final recognized = <FaceStream>[];
var cancelled = 0;
await tester.pumpWidget(
_harness(
service: service,
onRecognized: recognized.add,
controller: controller,
onCancelled: () => cancelled++,
),
);
await tester.pump();
service.result.complete(_recognitionResult());
await tester.pump();
expect(service.finishCalls, 1);
await controller.cancel();
await controller.cancel();
await tester.pump();
expect(service.cancelCalls, 0);
expect(cancelled, 0);
expect(recognized, isEmpty);
finishCompletion.complete();
await tester.pump();
expect(recognized, hasLength(1));
await controller.cancel();
await tester.pump();
expect(recognized, hasLength(1));
expect(service.finishCalls, 1);
expect(service.cancelCalls, 0);
expect(cancelled, 0);
expect(tester.takeException(), isNull);
});
}