import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.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'; void main() { testWidgets('RoutePlannerScreen renders title, empty state, and stop controls', (tester) async { await tester.binding.setSurfaceSize(const Size(360, 640)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // Verify AppBar title expect(find.text('ROUTE PLANNER'), findsOneWidget); expect(find.text('SAVE'), findsOneWidget); // Verify Empty state message expect(find.text('No stops added yet.'), findsOneWidget); expect(find.text('ADD STOP'), findsOneWidget); expect(find.text('CURRENT GPS'), findsOneWidget); }); testWidgets('RoutePlannerScreen handles adding a stop', (tester) async { await tester.binding.setSurfaceSize(const Size(360, 640)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // Tap ADD STOP await tester.tap(find.text('ADD STOP')); await tester.pumpAndSettle(); // Dialog appears expect(find.text('Add Route Stop'), findsOneWidget); // Enter name, lat, lng final dialogTextFields = find.descendant( of: find.byType(AlertDialog), matching: find.byType(TextField), ); await tester.enterText(dialogTextFields.at(0), 'Alice\'s Restaurant'); await tester.enterText(dialogTextFields.at(1), '37.3861'); await tester.enterText(dialogTextFields.at(2), '-122.2641'); // Tap ADD button in dialog await tester.tap(find.text('ADD')); await tester.pumpAndSettle(); // Verify stop is added in list expect(find.text('Alice\'s Restaurant'), findsOneWidget); expect(find.text('1/25 STOPS'), findsOneWidget); }); testWidgets('RoutePlannerScreen responsive layout on narrow 320dp screen', (tester) async { // 320dp narrow device test await tester.binding.setSurfaceSize(const Size(320, 568)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // On narrow screen, compact labels 'ADD' and 'GPS' are shown expect(find.text('ADD'), findsOneWidget); expect(find.text('GPS'), findsOneWidget); // No RenderFlex overflow expect(tester.takeException(), isNull); }); testWidgets('RoutePlannerScreen handles title editing under Pixel 8a IME keyboard inset without overflow', (tester) async { // Pixel 8a viewport with keyboard open (height reduced from 857 to 440) await tester.binding.setSurfaceSize(const Size(385, 440)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // Find Route Title TextField final titleField = find.byType(TextField).first; expect(titleField, findsOneWidget); // Enter title text while constrained await tester.enterText(titleField, 'Marionette Route Validation'); await tester.pumpAndSettle(); expect(find.text('Marionette Route Validation'), findsOneWidget); // Verify SAVE button in AppBar and stop controls remain visible and reachable expect(find.text('SAVE'), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_add_stop_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_current_gps_button')), findsOneWidget); expect(find.text('No stops added yet.'), findsOneWidget); // No RenderFlex overflow expect(tester.takeException(), isNull); }); testWidgets('RoutePlannerScreen handles constrained 360x360 and 320x320 dimensions without overflow', (tester) async { // Severely constrained 360x360 viewport await tester.binding.setSurfaceSize(const Size(360, 360)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); final titleField = find.byType(TextField).first; await tester.enterText(titleField, 'Constrained 360'); await tester.pumpAndSettle(); expect(find.text('Constrained 360'), findsOneWidget); expect(tester.takeException(), isNull); // Extremely constrained 320x320 viewport await tester.binding.setSurfaceSize(const Size(320, 320)); await tester.pumpAndSettle(); await tester.enterText(titleField, 'Constrained 320'); await tester.pumpAndSettle(); expect(find.text('Constrained 320'), findsOneWidget); expect(tester.takeException(), isNull); }); testWidgets('RoutePlannerScreen landscape layout (800x360) renders without RenderFlex overflow', (tester) async { // Landscape orientation on wide screen await tester.binding.setSurfaceSize(const Size(800, 360)); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // Verify title field and controls render properly final titleField = find.byType(TextField).first; await tester.enterText(titleField, 'Skyline Ridge Landscape'); await tester.pumpAndSettle(); expect(find.text('Skyline Ridge Landscape'), findsOneWidget); expect(find.text('SAVE'), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_add_stop_button')), findsOneWidget); expect(find.byKey(const ValueKey('route_planner_current_gps_button')), findsOneWidget); // No RenderFlex overflow expect(tester.takeException(), isNull); }); testWidgets( 'RoutePlannerScreen landscape (800x360) with status bar and keyboard inset scrolls cleanly and portrait renders cleanly (#76)', (tester) async { addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); addTearDown(tester.view.resetPadding); addTearDown(tester.view.resetViewInsets); tester.view.physicalSize = const Size(800, 360); tester.view.devicePixelRatio = 1.0; tester.view.padding = const FakeViewPadding(top: 24); tester.view.resetViewInsets(); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWith((ref) => const AuthUser( uid: 'alice', isAnonymous: false, )), isProSubscriberProvider.overrideWith((ref) => false), ], child: const MaterialApp( home: RoutePlannerScreen(), ), ), ); await tester.pumpAndSettle(); // No 2.5px overflow on landscape with status bar padding expect(tester.takeException(), isNull); // Simulate keyboard opening in landscape (260.5px inset) tester.view.viewInsets = const FakeViewPadding(bottom: 200); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); final titleField = find.byType(TextField).first; await tester.ensureVisible(titleField); await tester.pumpAndSettle(); await tester.enterText(titleField, 'Cherohala Skyway'); await tester.pumpAndSettle(); expect(find.text('Cherohala Skyway'), findsOneWidget); expect(tester.takeException(), isNull); // Scroll down to ADD STOP button and open Add Stop dialog with keyboard inset final addStopBtn = find.byKey(const ValueKey('route_planner_add_stop_button')); await tester.ensureVisible(addStopBtn); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); await tester.tap(addStopBtn); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); expect(find.text('Add Route Stop'), findsOneWidget); await tester.ensureVisible(find.text('CANCEL')); await tester.pumpAndSettle(); await tester.tap(find.text('CANCEL')); await tester.pumpAndSettle(); // Portrait check with keyboard inset tester.view.physicalSize = const Size(400, 800); tester.view.padding = const FakeViewPadding(top: 24); tester.view.viewInsets = const FakeViewPadding(bottom: 280); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); expect(find.text('SAVE'), findsOneWidget); }); }