import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:dmc_app/domain/waypoint/flat_waypoint_list.dart'; import 'package:dmc_app/services/wind/wind_service.dart'; import 'package:dmc_app/ui/providers/current_mission_provider.dart'; import 'package:dmc_app/ui/providers/wind_provider.dart'; /// Zaehlt fetchWindForWaypoints()-Aufrufe statt echte HTTP-Requests zu /// stellen - schreibt synthetische Winddaten zurueck, damit /// ensurePerWaypointWind()s "bereits vorhanden"-Kurzschluss (siehe dortige /// Doku) bei einem zweiten Aufruf greifen kann. class _CountingWindService extends WindService { int fetchCalls = 0; @override Future> fetchWindForWaypoints(List waypoints) async { fetchCalls++; return [ for (final w in waypoints) w.copyWith(windSpeedMs: 5, windDirFromDeg: 200), ]; } } Waypoint _wp(double lat, double lon, {double? windSpeedMs}) => Waypoint( lat: lat, lon: lon, altitudeM: 60, speedMs: 15, catchRadiusM: 60, windSpeedMs: windSpeedMs, windDirFromDeg: windSpeedMs != null ? 180 : null, ); void main() { test('ensurePerWaypointWind() fetches once for a list without wind data', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); container.read(currentMissionProvider.notifier).addWaypoint(_wp(0, 0)); await container.read(headerWindProvider.notifier).ensurePerWaypointWind(); expect(service.fetchCalls, 1); expect(container.read(currentMissionProvider).single.windSpeedMs, 5); }); test('ensurePerWaypointWind() does not fetch when every waypoint already ' 'has wind data (e.g. loaded from a saved mission)', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); container.read(currentMissionProvider.notifier).addWaypoint(_wp(0, 0, windSpeedMs: 3)); await container.read(headerWindProvider.notifier).ensurePerWaypointWind(); expect(service.fetchCalls, 0); }); test('a second ensurePerWaypointWind() call for the unchanged list does ' 'not fetch again', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); container.read(currentMissionProvider.notifier).addWaypoint(_wp(0, 0)); final notifier = container.read(headerWindProvider.notifier); await notifier.ensurePerWaypointWind(); await notifier.ensurePerWaypointWind(); expect(service.fetchCalls, 1); }); test('toggleShowPerWaypoint() fetches when turning on, not when turning off', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); container.read(currentMissionProvider.notifier).addWaypoint(_wp(0, 0)); final notifier = container.read(headerWindProvider.notifier); notifier.toggleShowPerWaypoint(); // an await Future.delayed(Duration.zero); expect(container.read(headerWindProvider).showPerWaypoint, isTrue); expect(service.fetchCalls, 1); notifier.toggleShowPerWaypoint(); // aus await Future.delayed(Duration.zero); expect(container.read(headerWindProvider).showPerWaypoint, isFalse); expect(service.fetchCalls, 1, reason: 'Ausschalten loest keinen Abruf aus'); }); test('after activation, changing the waypoint list (e.g. adding a point) ' 'triggers an automatic refetch', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); final missionNotifier = container.read(currentMissionProvider.notifier); missionNotifier.addWaypoint(_wp(0, 0)); await container.read(headerWindProvider.notifier).ensurePerWaypointWind(); expect(service.fetchCalls, 1); missionNotifier.addWaypoint(_wp(0, 0.01)); await Future.delayed(Duration.zero); expect(service.fetchCalls, 2, reason: 'die veraenderte Wegpunktliste loest automatisch neu aus'); }); test('after activation, editing only altitude (position unchanged) does ' 'NOT trigger a refetch', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); final missionNotifier = container.read(currentMissionProvider.notifier); missionNotifier.addWaypoint(_wp(0, 0)); await container.read(headerWindProvider.notifier).ensurePerWaypointWind(); expect(service.fetchCalls, 1); missionNotifier.setAltitude(0, 150); await Future.delayed(Duration.zero); expect(service.fetchCalls, 1, reason: 'nur die Position ist Teil der Aenderungs-Signatur, nicht die Hoehe'); }); test('before activation (menu never opened, pill never toggled), list ' 'changes trigger no fetch at all', () async { final service = _CountingWindService(); final container = ProviderContainer(overrides: [ windServiceProvider.overrideWithValue(service), ]); addTearDown(container.dispose); // Beobachtet headerWindProvider, ohne ensurePerWaypointWind() oder // toggleShowPerWaypoint() aufzurufen - build() registriert den Listener // zwar, er soll aber inaktiv bleiben. container.read(headerWindProvider); container.read(currentMissionProvider.notifier).addWaypoint(_wp(0, 0)); await Future.delayed(Duration.zero); expect(service.fetchCalls, 0); }); }