MotorSignal-App/test/canonical_routes_maps_respo...

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);
});
});
}