import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:motorcycle_dash/core/theme/theme_extensions.dart'; import 'package:motorcycle_dash/features/map/map_screen.dart'; import 'package:motorcycle_dash/features/routes/route_detail_screen.dart'; import 'package:motorcycle_dash/features/routes/route_planner_screen.dart'; import 'package:motorcycle_dash/services/auth/auth_providers.dart'; import 'package:motorcycle_dash/services/auth/auth_user.dart'; import 'package:motorcycle_dash/services/entitlements/entitlement_providers.dart'; import 'package:motorcycle_dash/services/map/unified_map_view.dart'; import 'package:motorcycle_dash/services/routes/navigation_handoff.dart'; import 'package:motorcycle_dash/services/routes/route_models.dart'; import 'package:motorcycle_dash/services/routes/route_providers.dart'; const List testViewports = [ Size(320, 640), Size(360, 800), Size(390, 844), Size(412, 915), Size(640, 320), Size(800, 360), Size(844, 390), Size(768, 1024), Size(1024, 768), ]; MotorSignalRoute createSampleRoute({ String id = 'sample-route-1', String title = 'Skyline Ridge Cruise', String description = 'Scenic ridge route with twisties', String ownerUid = 'alice', RouteVisibility visibility = RouteVisibility.public, String? attachedGroupRideId, }) { return MotorSignalRoute( id: id, title: title, description: description, ownerUid: ownerUid, createdAt: DateTime(2026, 1, 1), updatedAt: DateTime(2026, 1, 2), visibility: visibility, attachedGroupRideId: attachedGroupRideId, stops: const [ RouteStop( id: 'stop-1', orderIndex: 0, latitude: 37.3861, longitude: -122.2641, displayName: 'Alice\'s Restaurant', stopType: RouteStopType.start, notes: 'Meet at 9:00 AM', ), RouteStop( id: 'stop-2', orderIndex: 1, latitude: 37.3200, longitude: -122.2100, displayName: 'Skyline Vista Point', stopType: RouteStopType.scenic, notes: 'Quick photo stop', ), RouteStop( id: 'stop-3', orderIndex: 2, latitude: 37.2500, longitude: -122.1800, displayName: 'Summit Overlook', stopType: RouteStopType.destination, notes: 'Final regroup', ), ], ); } void main() { group('UI-2E MapScreen Responsive Matrix', () { for (final size in testViewports) { testWidgets('MapScreen live mode renders cleanly at ${size.width}x${size.height}', (tester) async { await tester.binding.setSurfaceSize(size); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)), isProSubscriberProvider.overrideWith((ref) => false), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const MapScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.text('Live Map'), findsOneWidget); expect(find.byType(UnifiedMapView), findsOneWidget); expect(tester.takeException(), isNull); }); } }); group('UI-2E RoutePlannerScreen Responsive Matrix', () { for (final size in testViewports) { testWidgets('RoutePlannerScreen renders without overflow at ${size.width}x${size.height}', (tester) async { await tester.binding.setSurfaceSize(size); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)), isProSubscriberProvider.overrideWith((ref) => false), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.text('ROUTE PLANNER'), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_save_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_add_stop_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_current_gps_button')), findsOneWidget); expect(tester.takeException(), isNull); }); } testWidgets('RoutePlannerScreen handles adding stops and reversing', (tester) async { await tester.binding.setSurfaceSize(const Size(412, 915)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)), isProSubscriberProvider.overrideWith((ref) => false), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // Add stop 1 await tester.tap(find.byKey(const ValueKey('route_planner_add_stop_button'))); await tester.pumpAndSettle(); var fields = find.descendant(of: find.byType(AlertDialog), matching: find.byType(TextField)); await tester.enterText(fields.at(0), 'Start Point'); await tester.enterText(fields.at(1), '37.3861'); await tester.enterText(fields.at(2), '-122.2641'); await tester.tap(find.text('ADD')); await tester.pumpAndSettle(); // Add stop 2 await tester.tap(find.byKey(const ValueKey('route_planner_add_stop_button'))); await tester.pumpAndSettle(); fields = find.descendant(of: find.byType(AlertDialog), matching: find.byType(TextField)); await tester.enterText(fields.at(0), 'End Point'); await tester.enterText(fields.at(1), '37.2500'); await tester.enterText(fields.at(2), '-122.1800'); await tester.tap(find.text('ADD')); await tester.pumpAndSettle(); expect(find.text('Start Point'), findsOneWidget); expect(find.text('End Point'), findsOneWidget); expect(find.text('2/25 STOPS'), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_reverse_button')), findsOneWidget); // Reverse stops await tester.tap(find.byKey(const ValueKey('route_planner_reverse_button'))); await tester.pumpAndSettle(); expect(find.text('Start Point'), findsOneWidget); expect(find.text('End Point'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('RoutePlannerScreen wide layout renders split-pane', (tester) async { await tester.binding.setSurfaceSize(const Size(1024, 768)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)), isProSubscriberProvider.overrideWith((ref) => false), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('route_planner_map_expanded')), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_save_button')), findsOneWidget); expect(find.text('ROUTE PLANNER'), findsOneWidget); expect(tester.takeException(), isNull); }); }); group('UI-2E RouteDetailScreen Responsive Matrix', () { for (final size in testViewports) { testWidgets('RouteDetailScreen renders without overflow at ${size.width}x${size.height}', (tester) async { await tester.binding.setSurfaceSize(size); final sampleRoute = createSampleRoute(attachedGroupRideId: 'group-ride-123'); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)), routeByIdProvider('sample-route-1').overrideWith((ref) => Stream.value(sampleRoute)), isProSubscriberProvider.overrideWith((ref) => false), ], child: MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: const RouteDetailScreen(routeId: 'sample-route-1'), ), ), ); await tester.pumpAndSettle(); expect(find.text('ROUTE DETAILS'), findsOneWidget); expect(find.text('Skyline Ridge Cruise'), findsOneWidget); expect(find.text('Scenic ridge route with twisties'), findsOneWidget); expect(find.text('STRAIGHT-LINE DISTANCE'), findsOneWidget); expect(find.text('WAYPOINTS'), findsOneWidget); expect(find.text('3 STOPS'), findsOneWidget); expect(find.byKey(const ValueKey('route_detail_edit_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_detail_delete_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_detail_view_attached_group_ride_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_detail_navigate_start_button')), findsOneWidget); expect(tester.takeException(), isNull); }); } }); group('UI-2E NavigationHandoffService Modal', () { testWidgets('Navigation handoff bottom sheet displays options with theme tokens', (tester) async { await tester.binding.setSurfaceSize(const Size(390, 844)); await tester.pumpWidget( MaterialApp( theme: ThemeData.dark().copyWith( extensions: [MotorSignalThemeColors.dark], ), home: Scaffold( body: Builder( builder: (context) => ElevatedButton( onPressed: () { NavigationHandoffService.showHandoffSheet( context: context, latitude: 37.3861, longitude: -122.2641, displayName: 'Alice\'s Restaurant', ); }, child: const Text('OPEN HANDOFF'), ), ), ), ), ); await tester.pumpAndSettle(); // Open sheet await tester.tap(find.text('OPEN HANDOFF')); await tester.pumpAndSettle(); expect(find.text('NAVIGATION HANDOFF'), findsOneWidget); expect(find.text('Alice\'s Restaurant'), findsOneWidget); expect(find.byKey(const ValueKey('handoff_google_maps_tile')), findsOneWidget); expect(find.byKey(const ValueKey('handoff_waze_tile')), findsOneWidget); expect(find.byKey(const ValueKey('handoff_system_map_tile')), findsOneWidget); expect(tester.takeException(), isNull); }); }); }