import 'package:dmc_app/domain/mission/mission_stats.dart'; import 'package:dmc_app/domain/waypoint/flat_waypoint_list.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:latlong2/latlong.dart'; Waypoint _wp(double lat, double lon, {double speed = 15}) => Waypoint(lat: lat, lon: lon, altitudeM: 60, speedMs: speed, catchRadiusM: 60); void main() { group('computeMissionStats', () { test('leere/einzelne Wegpunktliste ergibt Distanz/Dauer 0', () { expect(computeMissionStats(const []).distanceM, 0); expect(computeMissionStats([_wp(0, 0)]).distanceM, 0); expect(computeMissionStats([_wp(0, 0)]).durationSec, 0); }); test('zwei Wegpunkte: Distanz aus Grosskreis, Dauer aus Zielgeschwindigkeit', () { final stats = computeMissionStats([_wp(0, 0), _wp(0, 0.01, speed: 20)]); // 0.01 Grad Laengendifferenz am Aequator entspricht rund 1112 m. expect(stats.distanceM, closeTo(1112.6, 1)); expect(stats.durationSec, closeTo(stats.distanceM / 20, 0.01)); }); test('mehrere Beine summieren sich, Dauer je Bein mit dessen Zielgeschwindigkeit', () { final stats = computeMissionStats( [_wp(0, 0, speed: 10), _wp(0, 0.01, speed: 20), _wp(0, 0.02, speed: 10)], ); final leg1 = 1112.6; final leg2 = 1112.6; expect(stats.distanceM, closeTo(leg1 + leg2, 2)); // Erstes Bein zaehlt mit der Geschwindigkeit des Zielwegpunkts (20), // zweites Bein mit dessen Zielwegpunkt (10). expect(stats.durationSec, closeTo(leg1 / 20 + leg2 / 10, 0.1)); }); }); group('computeFlyMissionStatus', () { final waypoints = [_wp(0, 0), _wp(0, 0.01), _wp(0, 0.02)]; test('kein aktiver Wegpunkt (null) ergibt null', () { expect( computeFlyMissionStatus(waypoints: waypoints, activeWaypointIndex: null), isNull, ); }); test('activeWaypointIndex ausserhalb der Liste ergibt null', () { expect( computeFlyMissionStatus(waypoints: waypoints, activeWaypointIndex: 3), isNull, ); expect( computeFlyMissionStatus(waypoints: waypoints, activeWaypointIndex: -1), isNull, ); }); test('activeWaypointNumber ist 1-basiert (Index 1 -> Nummer 2)', () { final status = computeFlyMissionStatus( waypoints: waypoints, activeWaypointIndex: 1, ); expect(status!.activeWaypointNumber, 2); expect(status.waypointCount, 3); }); test('ohne Position (kein Fix) bleiben Distanz/ETA null', () { final status = computeFlyMissionStatus( waypoints: waypoints, activeWaypointIndex: 1, ); expect(status!.distanceToActiveWaypointM, isNull); expect(status.etaToActiveWaypointSec, isNull); }); test('mit Position wird die Distanz zum aktiven Wegpunkt berechnet', () { final status = computeFlyMissionStatus( waypoints: waypoints, activeWaypointIndex: 1, currentLat: 0, currentLon: 0, ); // Wegpunkt 1 (Index 1) liegt bei (0, 0.01) - rund 1112.6 m vom // Ursprung entfernt. expect(status!.distanceToActiveWaypointM, closeTo(1112.6, 1)); // Ohne Geschwindigkeit keine ETA. expect(status.etaToActiveWaypointSec, isNull); }); test('mit Position und Geschwindigkeit wird die ETA berechnet', () { final status = computeFlyMissionStatus( waypoints: waypoints, activeWaypointIndex: 1, currentLat: 0, currentLon: 0, currentSpeedMs: 10, ); expect(status!.etaToActiveWaypointSec, closeTo(1112.6 / 10, 0.1)); }); test('unter 0.5 m/s wird keine (irrefuehrende) ETA berechnet', () { final status = computeFlyMissionStatus( waypoints: waypoints, activeWaypointIndex: 1, currentLat: 0, currentLon: 0, currentSpeedMs: 0.1, ); expect(status!.etaToActiveWaypointSec, isNull); }); }); group('computeRubberBandTarget', () { final waypoints = [_wp(0, 0), _wp(0, 0.01), _wp(0, 0.02)]; const home = LatLng(1, 1); test('zeigt normalerweise auf den aktiven Missionswegpunkt', () { final target = computeRubberBandTarget( navMode: 3, // Waypoint mission activeWaypointIndex: 1, waypoints: waypoints, homePoint: home, ); expect(target, const LatLng(0, 0.01)); }); test('zeigt bei Return-to-Home (navMode 2) auf den Homepoint statt auf ' 'den (ggf. laengst passierten) eingefrorenen Missionswegpunkt', () { final target = computeRubberBandTarget( navMode: navModeReturnToHome, activeWaypointIndex: 1, waypoints: waypoints, homePoint: home, ); expect(target, home); }); test('liefert null bei RTH ohne bekannten Homepoint statt eines ' 'erfundenen Ziels', () { final target = computeRubberBandTarget( navMode: navModeReturnToHome, activeWaypointIndex: 1, waypoints: waypoints, homePoint: null, ); expect(target, isNull); }); test('schaltet nach Abbruch von RTH (navMode wieder 3) automatisch ' 'zurueck auf den Missionswegpunkt', () { const rth = 2; final duringRth = computeRubberBandTarget( navMode: rth, activeWaypointIndex: 1, waypoints: waypoints, homePoint: home, ); final afterAbort = computeRubberBandTarget( navMode: 3, activeWaypointIndex: 1, waypoints: waypoints, homePoint: home, ); expect(duringRth, home); expect(afterAbort, const LatLng(0, 0.01)); }); test('activeWaypointIndex null oder ausserhalb der Liste ergibt null ' '(ausserhalb RTH)', () { expect( computeRubberBandTarget( navMode: 3, activeWaypointIndex: null, waypoints: waypoints, ), isNull, ); expect( computeRubberBandTarget( navMode: 3, activeWaypointIndex: 3, waypoints: waypoints, ), isNull, ); }); }); group('computeDistanceToHomeM', () { test('berechnet die Distanz aus Grosskreis', () { final distance = computeDistanceToHomeM( currentPosition: const LatLng(0, 0), homePoint: const LatLng(0, 0.01), ); // 0.01 Grad Laengendifferenz am Aequator entspricht rund 1112 m. expect(distance, closeTo(1112.6, 1)); }); test('null ohne aktuelle Position (kein GPS-Fix)', () { expect( computeDistanceToHomeM(currentPosition: null, homePoint: const LatLng(0, 0)), isNull, ); }); test('null ohne bekannten Homepoint', () { expect( computeDistanceToHomeM(currentPosition: const LatLng(0, 0), homePoint: null), isNull, ); }); }); }