import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:dmc_app/app_mode/app_mode_cubit.dart'; import 'package:dmc_app/domain/waypoint/flat_waypoint_list.dart'; import 'package:dmc_app/ui/app_shell.dart'; import 'package:dmc_app/ui/providers/current_mission_provider.dart'; import 'package:dmc_app/ui/widgets/halo_menu.dart'; import 'package:dmc_app/ui/widgets/reticle_button.dart'; Widget _wrap(Widget child) => ProviderScope( child: BlocProvider( create: (_) => AppModeCubit(), child: MaterialApp(home: child), ), ); void main() { testWidgets('AppShell zeigt die Karte und den Plan/Fly-Umschalter im Startzustand', (tester) async { await tester.pumpWidget(_wrap(const AppShell())); // Nur ein Frame pumpen statt pumpAndSettle: TileLayer laedt echte // Netzwerk-Kacheln, die in der Testumgebung nie ankommen. await tester.pump(); expect(find.byType(FlutterMap), findsOneWidget); expect(find.text('Plan'), findsOneWidget); expect(find.text('Fly'), findsOneWidget); }); testWidgets( 'Fly-Button ohne erfuellten Ready-to-Fly-Gate zeigt Hinweis statt Absturz', (tester) async { await tester.pumpWidget(_wrap(const AppShell())); await tester.pump(); await tester.tap(find.text('Fly')); await tester.pump(); expect( find.text('Mission muss zuerst hochgeladen und verifiziert werden.'), findsOneWidget, ); // Weiterhin im Plan-Modus, da das Gate nicht erfuellt war. expect(find.byType(FlutterMap), findsOneWidget); }); testWidgets('Reticle-Button fuegt einen Wegpunkt am Kartenzentrum hinzu', (tester) async { final container = ProviderContainer(); addTearDown(container.dispose); await tester.pumpWidget( UncontrolledProviderScope( container: container, child: BlocProvider( create: (_) => AppModeCubit(), child: const MaterialApp(home: AppShell()), ), ), ); await tester.pump(); expect(container.read(currentMissionProvider), isEmpty); await tester.tap(find.byType(ReticleButton)); await tester.pump(); final waypoints = container.read(currentMissionProvider); expect(waypoints, hasLength(1)); expect(waypoints.single.lat, closeTo(52.3676, 0.0001)); expect(waypoints.single.lon, closeTo(4.9041, 0.0001)); }); testWidgets( 'Zweiter Tap auf den frisch gesetzten Wegpunkt oeffnet das Halo-Menue', (tester) async { final container = ProviderContainer(); addTearDown(container.dispose); await tester.pumpWidget( UncontrolledProviderScope( container: container, child: BlocProvider( create: (_) => AppModeCubit(), child: const MaterialApp(home: AppShell()), ), ), ); await tester.pump(); // 1. Tap: idle -> snapped (legt Wegpunkt an). await tester.tap(find.byType(ReticleButton)); await tester.pump(); expect(find.byType(HaloMenu), findsNothing); // 2. Tap: snapped -> editing. await tester.tap(find.byType(ReticleButton)); await tester.pump(); expect(find.byType(HaloMenu), findsOneWidget); }); testWidgets('Halo-Remove entfernt den Wegpunkt und schliesst das Menue', (tester) async { final container = ProviderContainer(); addTearDown(container.dispose); await tester.pumpWidget( UncontrolledProviderScope( container: container, child: BlocProvider( create: (_) => AppModeCubit(), child: const MaterialApp(home: AppShell()), ), ), ); await tester.pump(); await tester.tap(find.byType(ReticleButton)); // idle -> snapped await tester.pump(); await tester.tap(find.byType(ReticleButton)); // snapped -> editing await tester.pump(); // Die Wedge-Bounding-Box-Mitte liegt ausserhalb der Keilform (Donut- // Segment) - stattdessen exakt auf das (nicht hit-testbare) Icon tippen, // das per Konstruktion innerhalb des Keils sitzt. await tester.tapAt(tester.getCenter(find.byIcon(Icons.remove))); await tester.pump(); expect(container.read(currentMissionProvider), isEmpty); expect(find.byType(HaloMenu), findsNothing); }); testWidgets('Halo-Loiter setzt die Execute-Aktion und schliesst das Menue', (tester) async { final container = ProviderContainer(); addTearDown(container.dispose); await tester.pumpWidget( UncontrolledProviderScope( container: container, child: BlocProvider( create: (_) => AppModeCubit(), child: const MaterialApp(home: AppShell()), ), ), ); await tester.pump(); await tester.tap(find.byType(ReticleButton)); // idle -> snapped await tester.pump(); await tester.tap(find.byType(ReticleButton)); // snapped -> editing await tester.pump(); await tester.tapAt(tester.getCenter(find.byIcon(Icons.loop))); await tester.pump(); final waypoints = container.read(currentMissionProvider); expect(waypoints.single.action, WaypointAction.loiter); expect(find.byType(HaloMenu), findsNothing); }); }