import 'package:flutter_test/flutter_test.dart'; import 'package:pj_supermarket_cloud/common/models/plugs/face_recognition_session_models.dart'; import 'package:pj_supermarket_cloud/common/style/xfj_design_tokens.dart'; import 'package:pj_supermarket_cloud/common/widgets/face/face_recognition_preview_layer.dart'; /// 验证仅识别最大脸时的面积排序、可见文案和语义颜色。 void main() { const smallFace = FaceRecognitionFaceBox( left: 0.05, top: 0.10, right: 0.25, bottom: 0.30, recognizeStatus: 'waiting', livenessStatus: 'unknown', ); const largestFace = FaceRecognitionFaceBox( left: 0.30, top: 0.20, right: 0.70, bottom: 0.70, recognizeStatus: 'comparing', livenessStatus: 'alive', similarity: 0.86, ); const mediumFace = FaceRecognitionFaceBox( left: 0.72, top: 0.15, right: 0.97, bottom: 0.55, recognizeStatus: 'failed', livenessStatus: 'notAlive', similarity: 0.42, ); test('会话事件解析原生最大脸配置并保留全部人脸框', () { final event = FaceRecognitionSessionEvent.fromMap({ 'sessionId': 'face-session-1', 'scene': 'cashier', 'state': 'detecting', 'revision': 3, 'keepMaxFace': true, 'faceBoxes': [ { 'left': 0.05, 'top': 0.10, 'right': 0.25, 'bottom': 0.30, 'recognizeStatus': 'waiting', 'livenessStatus': 'unknown', }, { 'left': 0.30, 'top': 0.20, 'right': 0.70, 'bottom': 0.70, 'recognizeStatus': 'comparing', 'livenessStatus': 'alive', }, ], }); expect(event.keepMaxFace, isTrue); expect(event.eventSequence, 0); expect(event.faceBoxes, hasLength(2)); }); test('原生事件可解析进程级单调序号', () { final event = FaceRecognitionSessionEvent.fromMap({ 'sessionId': 'face-session-2', 'scene': 'reservation', 'state': 'detecting', 'revision': 3, 'eventSequence': 17, 'faceBoxes': const [], }); expect(event.eventSequence, 17); }); test('归一化面积计算异常坐标时不会产生负占比', () { const invalidFace = FaceRecognitionFaceBox( left: 0.8, top: 0.7, right: 0.2, bottom: 0.1, recognizeStatus: 'waiting', livenessStatus: 'unknown', ); expect(smallFace.normalizedArea, closeTo(0.04, 0.000001)); expect(invalidFace.normalizedArea, 0); }); test('最大脸模式按面积选中绿色目标,其余使用红色文字标识', () { final painter = _painter( boxes: const [smallFace, largestFace, mediumFace], keepMaxFace: true, ); expect(painter.primaryFaceIndex, 1); expect(painter.colorFor(1), XfjDesignTokens.success); expect(painter.colorFor(0), XfjDesignTokens.danger); expect(painter.colorFor(2), XfjDesignTokens.danger); expect(painter.labelFor(1), contains('识别目标 · 占画面 20.0%')); expect(painter.labelFor(1), contains('相似度 86.0%')); expect(painter.labelFor(0), contains('非识别目标 · 占画面 4.0%')); expect(painter.labelFor(0), contains('不参与识别')); }); test('旋转和镜像不改变最大脸面积判断', () { final boxes = const [smallFace, largestFace, mediumFace]; final rotatedPainter = _painter( boxes: boxes, keepMaxFace: true, rotation: 90, mirror: true, ); final normalPainter = _painter( boxes: boxes, keepMaxFace: true, ); expect(rotatedPainter.primaryFaceIndex, normalPainter.primaryFaceIndex); }); test('关闭最大脸模式后继续沿用识别结果颜色且不显示面积标签', () { final painter = _painter( boxes: const [smallFace, largestFace, mediumFace], keepMaxFace: false, ); expect(painter.colorFor(0), XfjDesignTokens.primary); expect(painter.colorFor(1), XfjDesignTokens.success); expect(painter.colorFor(2), XfjDesignTokens.danger); expect(painter.labelFor(1), isNot(contains('占画面'))); expect(painter.labelFor(1), contains('相似度 86.0%')); }); test('低相似度和尚未比对的人脸仍全部进入绘制器', () { const lowSimilarityFace = FaceRecognitionFaceBox( left: 0.08, top: 0.12, right: 0.34, bottom: 0.48, recognizeStatus: 'failed', livenessStatus: 'unknown', similarity: 0.12, ); const waitingFace = FaceRecognitionFaceBox( left: 0.55, top: 0.10, right: 0.88, bottom: 0.52, recognizeStatus: 'waiting', livenessStatus: 'unknown', ); final painter = _painter( boxes: const [lowSimilarityFace, waitingFace], keepMaxFace: true, ); expect(painter.boxes, hasLength(2)); expect(painter.labelFor(0), contains('非识别目标')); expect(painter.labelFor(0), contains('相似度 12.0%')); expect(painter.labelFor(1), contains('识别目标')); expect(painter.labelFor(1), contains('检测中')); }); } /// 创建只用于判断面积、颜色和标签的绘制器,不实际申请摄像头 Texture。 FaceRecognitionBoxPainter _painter({ required List boxes, required bool keepMaxFace, int rotation = 0, bool mirror = false, }) { return FaceRecognitionBoxPainter( boxes: boxes, rotation: rotation, mirror: mirror, livenessEnabled: true, keepMaxFace: keepMaxFace, similarityThreshold: 0.8, ); }