MotorSignal-App/test/group_rides_widget_test.dart

304 lines
9.7 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/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);
});
});
}