300 lines
10 KiB
Dart
300 lines
10 KiB
Dart
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<void> 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));
|
|
});
|
|
});
|
|
}
|