import 'dart:math' as math; import 'package:flutter_test/flutter_test.dart'; import 'package:bully/services/parallelism_service.dart'; /// Vecteur de gravité mesuré par l'accéléromètre pour une pose donnée. /// /// [pitch] et [roll] sont exprimés comme dans le service : le tangage vaut 0° /// appareil vertical et -90° appareil à plat caméra vers le sol. ({double x, double y, double z}) gravity({ double pitch = 0, double roll = 0, }) { const double g = 9.81; final double p = pitch * math.pi / 180.0; final double r = roll * math.pi / 180.0; final double nz = math.sin(p); final double nx = math.sin(r); // Ce qui reste va sur y, l'axe vertical de l'écran. final double ny = math.sqrt(math.max(0.0, 1.0 - nz * nz - nx * nx)); return (x: nx * g, y: ny * g, z: nz * g); } ParallelismData evaluatePose( ParallelismService service, { double pitch = 0, double roll = 0, }) { final v = gravity(pitch: pitch, roll: roll); final data = service.evaluate(v.x, v.y, v.z); expect(data, isNotNull, reason: 'mesure jugée aberrante à tort'); return data!; } void main() { group('pose cible au mur (comportement existant)', () { test('appareil vertical → aligné', () { final data = evaluatePose(ParallelismService()); expect(data.pose, TargetPose.wall); expect(data.status, ParallelismStatus.aligned); expect(data.pitchDeviation, closeTo(0, 0.5)); }); test('l\'écart de tangage reste le tangage brut', () { final data = evaluatePose(ParallelismService(), pitch: 10); expect(data.pose, TargetPose.wall); expect(data.pitchDeviation, closeTo(data.pitchDegrees, 0.001)); }); test('inclinaison au-delà du seuil → désaligné', () { final data = evaluatePose(ParallelismService(), pitch: 40); expect(data.pose, TargetPose.wall); expect(data.status, ParallelismStatus.misaligned); }); test('roulis excessif → désaligné même si le tangage est bon', () { final data = evaluatePose(ParallelismService(), roll: 35); expect(data.status, ParallelismStatus.misaligned); }); }); group('pose cible au sol', () { test('appareil à plat caméra vers le bas → aligné', () { final data = evaluatePose(ParallelismService(), pitch: -90); expect(data.pose, TargetPose.ground); expect(data.status, ParallelismStatus.aligned); expect(data.pitchDeviation, closeTo(0, 0.5)); }); test('l\'écart se mesure par rapport à -90°, pas à 0°', () { final data = evaluatePose(ParallelismService(), pitch: -80); expect(data.pose, TargetPose.ground); expect(data.pitchDeviation, closeTo(10, 0.5)); expect(data.status, ParallelismStatus.aligned); }); test('à plat mais trop incliné → désaligné', () { final data = evaluatePose(ParallelismService(), pitch: -50); expect(data.status, ParallelismStatus.misaligned); }); test('à plat écran vers le haut → jamais aligné', () { // La caméra vise le plafond : le vert n'aurait aucun sens. final data = evaluatePose(ParallelismService(), pitch: 90); expect(data.pose, TargetPose.wall); expect(data.status, ParallelismStatus.misaligned); }); }); group('hystérésis', () { test('le vert survit à un léger tremblement entre les deux seuils', () { final service = ParallelismService(); expect(evaluatePose(service).status, ParallelismStatus.aligned); // 28° est au-dessus du seuil d'entrée (25°) mais sous celui de sortie (32°). expect(evaluatePose(service, pitch: 28).status, ParallelismStatus.aligned); expect(evaluatePose(service, pitch: 40).status, ParallelismStatus.misaligned); // Repasser sous 32° ne suffit pas : il faut redescendre sous 25°. expect(evaluatePose(service, pitch: 28).status, ParallelismStatus.misaligned); expect(evaluatePose(service, pitch: 20).status, ParallelismStatus.aligned); }); test('la pose ne bascule pas pour un écart négligeable', () { final service = ParallelismService(); expect(evaluatePose(service).pose, TargetPose.wall); // Pile à la frontière des deux poses : on garde celle déjà retenue. expect(evaluatePose(service, pitch: -45).pose, TargetPose.wall); // Franchement du côté du sol : on bascule. expect(evaluatePose(service, pitch: -70).pose, TargetPose.ground); }); test('stop() remet la pose et le statut à zéro', () { final service = ParallelismService(); expect(evaluatePose(service, pitch: -90).pose, TargetPose.ground); service.stop(); expect(evaluatePose(service).pose, TargetPose.wall); }); }); test('une mesure aberrante est ignorée', () { expect(ParallelismService().evaluate(0, 0, 0), isNull); }); }