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/live_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/group_rides/group_ride_service.dart'; class _FakeGroupRideService extends GroupRideService { String? lastCallerUid; String? lastGroupRideId; GroupRideStatus? lastNewStatus; int updateRideStatusCallCount = 0; @override Future updateRideStatus({ required String callerUid, required String groupRideId, required GroupRideStatus newStatus, }) async { lastCallerUid = callerUid; lastGroupRideId = groupRideId; lastNewStatus = newStatus; updateRideStatusCallCount++; } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('LiveGroupRideScreen - Organizer End Ride Action Tests', () { final now = DateTime.now(); final underwayRide = GroupRide( id: 'ride_test_123', organizerUid: 'organizer_alice', organizerHandle: 'alice_apex', organizerDisplayName: 'Alice Apex', title: 'Sunday Morning Twisties', status: GroupRideStatus.underway, scheduledStartAt: now, departureAt: now, participantCount: 2, meetingPoint: const GroupRideLocation( latitude: 34.0, longitude: -84.0, displayName: 'Start Point', ), destination: const GroupRideLocation( latitude: 34.5, longitude: -84.5, displayName: 'End Point', ), stops: const [], createdAt: now, updatedAt: now, ); final completedRide = underwayRide.copyWith( status: GroupRideStatus.completed, ); final participants = [ GroupRideParticipant( userId: 'organizer_alice', role: GroupRideRole.lead, status: ParticipantStatus.accepted, riderHandle: 'alice_apex', riderDisplayName: 'Alice Apex', createdAt: now, updatedAt: now, ), GroupRideParticipant( userId: 'rider_bob', role: GroupRideRole.participant, status: ParticipantStatus.accepted, riderHandle: 'bob_rider', riderDisplayName: 'Bob Rider', createdAt: now, updatedAt: now, ), ]; testWidgets('organizer sees "Ride Options" popup menu with "End Ride" when underway', (tester) async { final fakeService = _FakeGroupRideService(); const user = AuthUser( uid: 'organizer_alice', email: 'alice@example.com', isAnonymous: false, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), groupRideServiceProvider.overrideWithValue(fakeService), groupRideStreamProvider('ride_test_123') .overrideWith((ref) => Stream.value(underwayRide)), groupRideParticipantsProvider('ride_test_123') .overrideWith((ref) => Stream.value(participants)), groupRideLivePresenceProvider('ride_test_123') .overrideWith((ref) => Stream.value([])), groupRideLiveSharingAllowedProvider.overrideWith((ref) => true), ], child: const MaterialApp( home: LiveGroupRideScreen(rideId: 'ride_test_123'), ), ), ); await tester.pumpAndSettle(); // Ride Options menu button should be visible final rideOptionsFinder = find.byTooltip('Ride Options'); expect(rideOptionsFinder, findsOneWidget); // Open the popup menu await tester.tap(rideOptionsFinder); await tester.pumpAndSettle(); // "End Ride" menu item should be visible expect(find.text('End Ride'), findsOneWidget); }); testWidgets('non-organizer participant does NOT see "Ride Options" popup menu', (tester) async { final fakeService = _FakeGroupRideService(); const nonOrganizerUser = AuthUser( uid: 'rider_bob', email: 'bob@example.com', isAnonymous: false, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(nonOrganizerUser), groupRideServiceProvider.overrideWithValue(fakeService), groupRideStreamProvider('ride_test_123') .overrideWith((ref) => Stream.value(underwayRide)), groupRideParticipantsProvider('ride_test_123') .overrideWith((ref) => Stream.value(participants)), groupRideLivePresenceProvider('ride_test_123') .overrideWith((ref) => Stream.value([])), groupRideLiveSharingAllowedProvider.overrideWith((ref) => true), ], child: const MaterialApp( home: LiveGroupRideScreen(rideId: 'ride_test_123'), ), ), ); await tester.pumpAndSettle(); // Non-organizer should NOT see Ride Options expect(find.byTooltip('Ride Options'), findsNothing); expect(find.text('End Ride'), findsNothing); }); testWidgets('organizer does NOT see "Ride Options" popup menu when ride is completed', (tester) async { final fakeService = _FakeGroupRideService(); const user = AuthUser( uid: 'organizer_alice', email: 'alice@example.com', isAnonymous: false, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), groupRideServiceProvider.overrideWithValue(fakeService), groupRideStreamProvider('ride_test_123') .overrideWith((ref) => Stream.value(completedRide)), groupRideParticipantsProvider('ride_test_123') .overrideWith((ref) => Stream.value(participants)), groupRideLivePresenceProvider('ride_test_123') .overrideWith((ref) => Stream.value([])), groupRideLiveSharingAllowedProvider.overrideWith((ref) => true), ], child: const MaterialApp( home: LiveGroupRideScreen(rideId: 'ride_test_123'), ), ), ); await tester.pumpAndSettle(); expect(find.byTooltip('Ride Options'), findsNothing); }); testWidgets('tapping End Ride shows confirmation dialog and CANCEL dismisses without state change', (tester) async { final fakeService = _FakeGroupRideService(); const user = AuthUser( uid: 'organizer_alice', email: 'alice@example.com', isAnonymous: false, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), groupRideServiceProvider.overrideWithValue(fakeService), groupRideStreamProvider('ride_test_123') .overrideWith((ref) => Stream.value(underwayRide)), groupRideParticipantsProvider('ride_test_123') .overrideWith((ref) => Stream.value(participants)), groupRideLivePresenceProvider('ride_test_123') .overrideWith((ref) => Stream.value([])), groupRideLiveSharingAllowedProvider.overrideWith((ref) => true), ], child: const MaterialApp( home: LiveGroupRideScreen(rideId: 'ride_test_123'), ), ), ); await tester.pumpAndSettle(); // Tap Ride Options await tester.tap(find.byTooltip('Ride Options')); await tester.pumpAndSettle(); // Tap End Ride await tester.tap(find.text('End Ride')); await tester.pumpAndSettle(); // Dialog should be displayed with expected title and copy expect(find.text('End this group ride?'), findsOneWidget); expect( find.text('Ending the ride will stop live location sharing and mark the Group Ride as completed.'), findsOneWidget, ); // Tap CANCEL await tester.tap(find.text('CANCEL')); await tester.pumpAndSettle(); // Dialog dismissed, no service call made expect(find.text('End this group ride?'), findsNothing); expect(fakeService.updateRideStatusCallCount, equals(0)); }); testWidgets('tapping END RIDE in confirmation dialog triggers status update to completed', (tester) async { final fakeService = _FakeGroupRideService(); const user = AuthUser( uid: 'organizer_alice', email: 'alice@example.com', isAnonymous: false, ); await tester.pumpWidget( ProviderScope( overrides: [ currentUserProvider.overrideWithValue(user), groupRideServiceProvider.overrideWithValue(fakeService), groupRideStreamProvider('ride_test_123') .overrideWith((ref) => Stream.value(underwayRide)), groupRideParticipantsProvider('ride_test_123') .overrideWith((ref) => Stream.value(participants)), groupRideLivePresenceProvider('ride_test_123') .overrideWith((ref) => Stream.value([])), groupRideLiveSharingAllowedProvider.overrideWith((ref) => true), ], child: const MaterialApp( home: LiveGroupRideScreen(rideId: 'ride_test_123'), ), ), ); await tester.pumpAndSettle(); // Tap Ride Options await tester.tap(find.byTooltip('Ride Options')); await tester.pumpAndSettle(); // Tap End Ride menu item await tester.tap(find.text('End Ride')); await tester.pumpAndSettle(); // Tap END RIDE button in dialog await tester.tap(find.text('END RIDE')); await tester.pumpAndSettle(); // Verify updateRideStatus was invoked with correct arguments expect(fakeService.updateRideStatusCallCount, equals(1)); expect(fakeService.lastCallerUid, equals('organizer_alice')); expect(fakeService.lastGroupRideId, equals('ride_test_123')); expect(fakeService.lastNewStatus, equals(GroupRideStatus.completed)); }); }); }