Files
dmc/app/test/widget_test.dart
T
Constantin LeueandClaude Sonnet 5 db6b46140c Add center reticle control for dropping waypoints
- ReticleButton: fixed-at-screen-center "+" circle (HTML prototype:
  #centerCluster/#reticleBtn), tapping it drops a waypoint at the current
  map center.
- CurrentMissionNotifier (Riverpod): holds the waypoint list for the
  mission being edited. Reactive single-value state per doc 4.16, so
  Riverpod rather than a new Bloc/Cubit.
- MissionMap now renders each waypoint as a CircleLayer marker (white
  border, dark fill, matching the prototype's L.circleMarker styling).
- PlanScreen owns the MapController needed to read the camera center on
  tap, and wires the reticle to the provider.

Default altitude/speed/catch-radius (60m / 15 m/s / 60m) are hardcoded to
the T1 Ranger default profile values from the prototype for now - TODO
once drone profile settings (doc 3.5) exist.

Verified with flutter analyze, flutter test (incl. a new test covering the
add-waypoint path), and a real run on the Pixel_10a emulator: panned the
map and dropped two waypoints, both markers stayed correctly georeferenced.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 22:21:35 +02:00

76 lines
2.4 KiB
Dart

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/ui/app_shell.dart';
import 'package:dmc_app/ui/providers/current_mission_provider.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));
});
}