import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:motorcycle_dash/features/group_rides/group_rides_hub_screen.dart'; import 'package:motorcycle_dash/features/group_rides/create_group_ride_screen.dart'; import 'package:motorcycle_dash/services/auth/auth_user.dart'; import 'package:motorcycle_dash/services/auth/auth_providers.dart'; import 'package:motorcycle_dash/services/group_rides/group_ride_models.dart'; import 'package:motorcycle_dash/services/group_rides/group_ride_providers.dart'; import 'package:motorcycle_dash/services/social/rider_profile_models.dart'; import 'package:motorcycle_dash/services/social/social_providers.dart'; import 'package:motorcycle_dash/core/router.dart'; void main() { group('GroupRidesHubScreen Widget Tests', () { testWidgets('shows account required notice when signed out', (tester) async { await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(null), ], child: const MaterialApp( home: GroupRidesHubScreen(), ), ), ); expect(find.text('Rider Account Required'), findsOneWidget); expect(find.text('SIGN IN / CREATE ACCOUNT'), findsOneWidget); }); testWidgets('tapping sign in button navigates to /account', (tester) async { appRouter.go('/group-rides'); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(null), ], child: MaterialApp.router( routerConfig: appRouter, ), ), ); await tester.pumpAndSettle(); expect(find.text('SIGN IN / CREATE ACCOUNT'), findsOneWidget); await tester.tap(find.text('SIGN IN / CREATE ACCOUNT')); await tester.pumpAndSettle(); expect(find.text('Account & Cloud Identity'), findsOneWidget); }); testWidgets('renders tabs and empty states when authenticated', (tester) async { const user = AuthUser( uid: 'alice', email: 'alice@example.com', isAnonymous: false, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), upcomingGroupRidesProvider.overrideWith( (ref) => Stream.value([]), ), ], child: const MaterialApp( home: GroupRidesHubScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.text('UPCOMING RIDES'), findsOneWidget); expect(find.text('MY RIDES'), findsOneWidget); expect(find.text('No Upcoming Group Rides'), findsOneWidget); expect(find.text('NEW RIDE'), findsOneWidget); }); testWidgets('renders upcoming ride card when data present', (tester) async { const user = AuthUser( uid: 'alice', email: 'alice@example.com', isAnonymous: false, ); final now = DateTime.now(); final ride = GroupRide( id: 'ride_1', organizerUid: 'bob', organizerHandle: 'bob_fast', organizerDisplayName: 'Bob Fast', title: 'Sunday Morning Twisties', scheduledStartAt: now.add(const Duration(days: 1)), departureAt: now.add(const Duration(days: 1, minutes: 30)), participantCount: 3, meetingPoint: const GroupRideLocation( latitude: 34.0, longitude: -84.0, displayName: 'Dahlonega Chevron', ), createdAt: now, updatedAt: now, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), upcomingGroupRidesProvider.overrideWith( (ref) => Stream.value([ride]), ), ], child: const MaterialApp( home: GroupRidesHubScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.text('Sunday Morning Twisties'), findsOneWidget); expect(find.textContaining('Dahlonega Chevron'), findsOneWidget); expect(find.text('3'), findsOneWidget); }); testWidgets( 'Join Group Ride dialog renders without overflow in landscape with keyboard inset and in portrait (#69)', (tester) async { const user = AuthUser( uid: 'alice', email: 'alice@example.com', isAnonymous: false, ); tester.view.physicalSize = const Size(800, 360); tester.view.devicePixelRatio = 1.0; tester.view.viewInsets = const FakeViewPadding(bottom: 180); addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); addTearDown(tester.view.resetViewInsets); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), upcomingGroupRidesProvider.overrideWith( (ref) => Stream.value([]), ), ], child: const MaterialApp( home: GroupRidesHubScreen(), ), ), ); await tester.pumpAndSettle(); await tester.tap( find.byKey(const ValueKey('group_rides_join_appbar_button')), ); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); final fieldFinder = find.byKey(const ValueKey('join_ride_id_field')); final submitFinder = find.byKey(const ValueKey('join_ride_dialog_submit_button')); await tester.ensureVisible(fieldFinder); await tester.pumpAndSettle(); await tester.enterText(fieldFinder, 'ride_test_123'); await tester.pumpAndSettle(); expect(find.text('ride_test_123'), findsOneWidget); await tester.ensureVisible(submitFinder); await tester.pumpAndSettle(); expect(submitFinder, findsOneWidget); expect(find.text('CANCEL'), findsOneWidget); expect(tester.takeException(), isNull); // Dismiss and verify portrait as well await tester.tap(find.text('CANCEL')); await tester.pumpAndSettle(); tester.view.physicalSize = const Size(400, 800); tester.view.viewInsets = const FakeViewPadding(bottom: 300); await tester.pumpAndSettle(); await tester.tap( find.byKey(const ValueKey('group_rides_join_appbar_button')), ); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); expect(fieldFinder, findsOneWidget); expect(submitFinder, findsOneWidget); }); for (final size in const [Size(800, 360), Size(640, 360), Size(400, 800)]) { testWidgets('ride card renders without overflow on $size (#68)', (tester) async { tester.view.physicalSize = size; tester.view.devicePixelRatio = 1.0; addTearDown(tester.view.resetPhysicalSize); const user = AuthUser( uid: 'alice', email: 'alice@example.com', isAnonymous: false, ); final now = DateTime.now(); final ride = GroupRide( id: 'ride_1', organizerUid: 'alice', organizerHandle: 'alice_lead', organizerDisplayName: 'Alice Lead', title: 'Sunday Morning Twisties', scheduledStartAt: now.add(const Duration(days: 1)), departureAt: now.add(const Duration(days: 1, minutes: 30)), participantCount: 3, meetingPoint: const GroupRideLocation( latitude: 34.0, longitude: -84.0, displayName: 'Dahlonega Chevron', ), destination: const GroupRideLocation( latitude: 34.5, longitude: -83.8, displayName: 'Blood Mountain Summit', ), createdAt: now, updatedAt: now, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), upcomingGroupRidesProvider.overrideWith( (ref) => Stream.value([ride]), ), ], child: const MaterialApp( home: GroupRidesHubScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.byKey(const ValueKey('group_ride_card_ride_1')), findsOneWidget); expect(find.byKey(const ValueKey('manage_ride_ride_1')), findsOneWidget); expect(find.byKey(const ValueKey('open_hub_ride_1')), findsOneWidget); expect(tester.takeException(), isNull); }); } }); group('CreateGroupRideScreen Widget Tests', () { testWidgets('renders form fields and validates title', (tester) async { final now = DateTime.now(); final profile = RiderProfile( uid: 'alice', handle: 'alice_apex', displayName: 'Alice Apex', createdAt: now, updatedAt: now, ); await tester.pumpWidget( ProviderScope( overrides: [ myRiderProfileProvider.overrideWith( (ref) => Stream.value(profile), ), ], child: const MaterialApp( home: CreateGroupRideScreen(), ), ), ); await tester.pumpAndSettle(); expect(find.text('CREATE GROUP RIDE'), findsOneWidget); expect(find.text('1. RIDE DETAILS'), findsOneWidget); expect(find.text('2. DATE & SCHEDULE'), findsOneWidget); expect(find.text('3. MEETING LOCATION'), findsOneWidget); // Attempt submit without filling title await tester.tap(find.text('PUBLISH')); await tester.pumpAndSettle(); expect(find.text('Title must be at least 3 characters'), findsOneWidget); }); }); }