299 lines
11 KiB
Dart
299 lines
11 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/core/theme/theme_extensions.dart';
|
|
import 'package:motorcycle_dash/features/map/map_screen.dart';
|
|
import 'package:motorcycle_dash/features/routes/route_detail_screen.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';
|
|
import 'package:motorcycle_dash/services/map/unified_map_view.dart';
|
|
import 'package:motorcycle_dash/services/routes/navigation_handoff.dart';
|
|
import 'package:motorcycle_dash/services/routes/route_models.dart';
|
|
import 'package:motorcycle_dash/services/routes/route_providers.dart';
|
|
|
|
const List<Size> testViewports = [
|
|
Size(320, 640),
|
|
Size(360, 800),
|
|
Size(390, 844),
|
|
Size(412, 915),
|
|
Size(640, 320),
|
|
Size(800, 360),
|
|
Size(844, 390),
|
|
Size(768, 1024),
|
|
Size(1024, 768),
|
|
];
|
|
|
|
MotorSignalRoute createSampleRoute({
|
|
String id = 'sample-route-1',
|
|
String title = 'Skyline Ridge Cruise',
|
|
String description = 'Scenic ridge route with twisties',
|
|
String ownerUid = 'alice',
|
|
RouteVisibility visibility = RouteVisibility.public,
|
|
String? attachedGroupRideId,
|
|
}) {
|
|
return MotorSignalRoute(
|
|
id: id,
|
|
title: title,
|
|
description: description,
|
|
ownerUid: ownerUid,
|
|
createdAt: DateTime(2026, 1, 1),
|
|
updatedAt: DateTime(2026, 1, 2),
|
|
visibility: visibility,
|
|
attachedGroupRideId: attachedGroupRideId,
|
|
stops: const [
|
|
RouteStop(
|
|
id: 'stop-1',
|
|
orderIndex: 0,
|
|
latitude: 37.3861,
|
|
longitude: -122.2641,
|
|
displayName: 'Alice\'s Restaurant',
|
|
stopType: RouteStopType.start,
|
|
notes: 'Meet at 9:00 AM',
|
|
),
|
|
RouteStop(
|
|
id: 'stop-2',
|
|
orderIndex: 1,
|
|
latitude: 37.3200,
|
|
longitude: -122.2100,
|
|
displayName: 'Skyline Vista Point',
|
|
stopType: RouteStopType.scenic,
|
|
notes: 'Quick photo stop',
|
|
),
|
|
RouteStop(
|
|
id: 'stop-3',
|
|
orderIndex: 2,
|
|
latitude: 37.2500,
|
|
longitude: -122.1800,
|
|
displayName: 'Summit Overlook',
|
|
stopType: RouteStopType.destination,
|
|
notes: 'Final regroup',
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
group('UI-2E MapScreen Responsive Matrix', () {
|
|
for (final size in testViewports) {
|
|
testWidgets('MapScreen live mode renders cleanly at ${size.width}x${size.height}', (tester) async {
|
|
await tester.binding.setSurfaceSize(size);
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: MaterialApp(
|
|
theme: ThemeData.dark().copyWith(
|
|
extensions: [MotorSignalThemeColors.dark],
|
|
),
|
|
home: const MapScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Live Map'), findsOneWidget);
|
|
expect(find.byType(UnifiedMapView), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
});
|
|
|
|
group('UI-2E RoutePlannerScreen Responsive Matrix', () {
|
|
for (final size in testViewports) {
|
|
testWidgets('RoutePlannerScreen renders without overflow at ${size.width}x${size.height}', (tester) async {
|
|
await tester.binding.setSurfaceSize(size);
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: MaterialApp(
|
|
theme: ThemeData.dark().copyWith(
|
|
extensions: [MotorSignalThemeColors.dark],
|
|
),
|
|
home: const RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('ROUTE PLANNER'), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_save_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_add_stop_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_current_gps_button')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
|
|
testWidgets('RoutePlannerScreen handles adding stops and reversing', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(412, 915));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: MaterialApp(
|
|
theme: ThemeData.dark().copyWith(
|
|
extensions: [MotorSignalThemeColors.dark],
|
|
),
|
|
home: const RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Add stop 1
|
|
await tester.tap(find.byKey(const ValueKey('route_planner_add_stop_button')));
|
|
await tester.pumpAndSettle();
|
|
var fields = find.descendant(of: find.byType(AlertDialog), matching: find.byType(TextField));
|
|
await tester.enterText(fields.at(0), 'Start Point');
|
|
await tester.enterText(fields.at(1), '37.3861');
|
|
await tester.enterText(fields.at(2), '-122.2641');
|
|
await tester.tap(find.text('ADD'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Add stop 2
|
|
await tester.tap(find.byKey(const ValueKey('route_planner_add_stop_button')));
|
|
await tester.pumpAndSettle();
|
|
fields = find.descendant(of: find.byType(AlertDialog), matching: find.byType(TextField));
|
|
await tester.enterText(fields.at(0), 'End Point');
|
|
await tester.enterText(fields.at(1), '37.2500');
|
|
await tester.enterText(fields.at(2), '-122.1800');
|
|
await tester.tap(find.text('ADD'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Start Point'), findsOneWidget);
|
|
expect(find.text('End Point'), findsOneWidget);
|
|
expect(find.text('2/25 STOPS'), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_reverse_button')), findsOneWidget);
|
|
|
|
// Reverse stops
|
|
await tester.tap(find.byKey(const ValueKey('route_planner_reverse_button')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Start Point'), findsOneWidget);
|
|
expect(find.text('End Point'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('RoutePlannerScreen wide layout renders split-pane', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(1024, 768));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: MaterialApp(
|
|
theme: ThemeData.dark().copyWith(
|
|
extensions: [MotorSignalThemeColors.dark],
|
|
),
|
|
home: const RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const ValueKey('route_planner_map_expanded')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_save_button')), findsOneWidget);
|
|
expect(find.text('ROUTE PLANNER'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
});
|
|
|
|
group('UI-2E RouteDetailScreen Responsive Matrix', () {
|
|
for (final size in testViewports) {
|
|
testWidgets('RouteDetailScreen renders without overflow at ${size.width}x${size.height}', (tester) async {
|
|
await tester.binding.setSurfaceSize(size);
|
|
final sampleRoute = createSampleRoute(attachedGroupRideId: 'group-ride-123');
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(uid: 'alice', isAnonymous: false)),
|
|
routeByIdProvider('sample-route-1').overrideWith((ref) => Stream.value(sampleRoute)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: MaterialApp(
|
|
theme: ThemeData.dark().copyWith(
|
|
extensions: [MotorSignalThemeColors.dark],
|
|
),
|
|
home: const RouteDetailScreen(routeId: 'sample-route-1'),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('ROUTE DETAILS'), findsOneWidget);
|
|
expect(find.text('Skyline Ridge Cruise'), findsOneWidget);
|
|
expect(find.text('Scenic ridge route with twisties'), findsOneWidget);
|
|
expect(find.text('STRAIGHT-LINE DISTANCE'), findsOneWidget);
|
|
expect(find.text('WAYPOINTS'), findsOneWidget);
|
|
expect(find.text('3 STOPS'), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_detail_edit_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_detail_delete_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_detail_view_attached_group_ride_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_detail_navigate_start_button')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
});
|
|
|
|
group('UI-2E NavigationHandoffService Modal', () {
|
|
testWidgets('Navigation handoff bottom sheet displays options with theme tokens', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(390, 844));
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData.dark().copyWith(
|
|
extensions: [MotorSignalThemeColors.dark],
|
|
),
|
|
home: Scaffold(
|
|
body: Builder(
|
|
builder: (context) => ElevatedButton(
|
|
onPressed: () {
|
|
NavigationHandoffService.showHandoffSheet(
|
|
context: context,
|
|
latitude: 37.3861,
|
|
longitude: -122.2641,
|
|
displayName: 'Alice\'s Restaurant',
|
|
);
|
|
},
|
|
child: const Text('OPEN HANDOFF'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Open sheet
|
|
await tester.tap(find.text('OPEN HANDOFF'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('NAVIGATION HANDOFF'), findsOneWidget);
|
|
expect(find.text('Alice\'s Restaurant'), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('handoff_google_maps_tile')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('handoff_waze_tile')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('handoff_system_map_tile')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
});
|
|
}
|