302 lines
10 KiB
Dart
302 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/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';
|
|
|
|
void main() {
|
|
testWidgets('RoutePlannerScreen renders title, empty state, and stop controls', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(360, 640));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify AppBar title
|
|
expect(find.text('ROUTE PLANNER'), findsOneWidget);
|
|
expect(find.text('SAVE'), findsOneWidget);
|
|
|
|
// Verify Empty state message
|
|
expect(find.text('No stops added yet.'), findsOneWidget);
|
|
expect(find.text('ADD STOP'), findsOneWidget);
|
|
expect(find.text('CURRENT GPS'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('RoutePlannerScreen handles adding a stop', (tester) async {
|
|
await tester.binding.setSurfaceSize(const Size(360, 640));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Tap ADD STOP
|
|
await tester.tap(find.text('ADD STOP'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Dialog appears
|
|
expect(find.text('Add Route Stop'), findsOneWidget);
|
|
|
|
// Enter name, lat, lng
|
|
final dialogTextFields = find.descendant(
|
|
of: find.byType(AlertDialog),
|
|
matching: find.byType(TextField),
|
|
);
|
|
await tester.enterText(dialogTextFields.at(0), 'Alice\'s Restaurant');
|
|
await tester.enterText(dialogTextFields.at(1), '37.3861');
|
|
await tester.enterText(dialogTextFields.at(2), '-122.2641');
|
|
|
|
// Tap ADD button in dialog
|
|
await tester.tap(find.text('ADD'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify stop is added in list
|
|
expect(find.text('Alice\'s Restaurant'), findsOneWidget);
|
|
expect(find.text('1/25 STOPS'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('RoutePlannerScreen responsive layout on narrow 320dp screen', (tester) async {
|
|
// 320dp narrow device test
|
|
await tester.binding.setSurfaceSize(const Size(320, 568));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// On narrow screen, compact labels 'ADD' and 'GPS' are shown
|
|
expect(find.text('ADD'), findsOneWidget);
|
|
expect(find.text('GPS'), findsOneWidget);
|
|
|
|
// No RenderFlex overflow
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('RoutePlannerScreen handles title editing under Pixel 8a IME keyboard inset without overflow', (tester) async {
|
|
// Pixel 8a viewport with keyboard open (height reduced from 857 to 440)
|
|
await tester.binding.setSurfaceSize(const Size(385, 440));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Find Route Title TextField
|
|
final titleField = find.byType(TextField).first;
|
|
expect(titleField, findsOneWidget);
|
|
|
|
// Enter title text while constrained
|
|
await tester.enterText(titleField, 'Marionette Route Validation');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Marionette Route Validation'), findsOneWidget);
|
|
|
|
// Verify SAVE button in AppBar and stop controls remain visible and reachable
|
|
expect(find.text('SAVE'), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_add_stop_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_current_gps_button')), findsOneWidget);
|
|
expect(find.text('No stops added yet.'), findsOneWidget);
|
|
|
|
// No RenderFlex overflow
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('RoutePlannerScreen handles constrained 360x360 and 320x320 dimensions without overflow', (tester) async {
|
|
// Severely constrained 360x360 viewport
|
|
await tester.binding.setSurfaceSize(const Size(360, 360));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
final titleField = find.byType(TextField).first;
|
|
await tester.enterText(titleField, 'Constrained 360');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Constrained 360'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Extremely constrained 320x320 viewport
|
|
await tester.binding.setSurfaceSize(const Size(320, 320));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.enterText(titleField, 'Constrained 320');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Constrained 320'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('RoutePlannerScreen landscape layout (800x360) renders without RenderFlex overflow', (tester) async {
|
|
// Landscape orientation on wide screen
|
|
await tester.binding.setSurfaceSize(const Size(800, 360));
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify title field and controls render properly
|
|
final titleField = find.byType(TextField).first;
|
|
await tester.enterText(titleField, 'Skyline Ridge Landscape');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Skyline Ridge Landscape'), findsOneWidget);
|
|
expect(find.text('SAVE'), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_add_stop_button')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('route_planner_current_gps_button')), findsOneWidget);
|
|
|
|
// No RenderFlex overflow
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'RoutePlannerScreen landscape (800x360) with status bar and keyboard inset scrolls cleanly and portrait renders cleanly (#76)',
|
|
(tester) async {
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
addTearDown(tester.view.resetPadding);
|
|
addTearDown(tester.view.resetViewInsets);
|
|
|
|
tester.view.physicalSize = const Size(800, 360);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
tester.view.padding = const FakeViewPadding(top: 24);
|
|
tester.view.resetViewInsets();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
currentUserProvider.overrideWith((ref) => const AuthUser(
|
|
uid: 'alice',
|
|
isAnonymous: false,
|
|
)),
|
|
isProSubscriberProvider.overrideWith((ref) => false),
|
|
],
|
|
child: const MaterialApp(
|
|
home: RoutePlannerScreen(),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// No 2.5px overflow on landscape with status bar padding
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Simulate keyboard opening in landscape (260.5px inset)
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 200);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
final titleField = find.byType(TextField).first;
|
|
await tester.ensureVisible(titleField);
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(titleField, 'Cherohala Skyway');
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Cherohala Skyway'), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Scroll down to ADD STOP button and open Add Stop dialog with keyboard inset
|
|
final addStopBtn =
|
|
find.byKey(const ValueKey('route_planner_add_stop_button'));
|
|
await tester.ensureVisible(addStopBtn);
|
|
await tester.pumpAndSettle();
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.tap(addStopBtn);
|
|
await tester.pumpAndSettle();
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.text('Add Route Stop'), findsOneWidget);
|
|
|
|
await tester.ensureVisible(find.text('CANCEL'));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.text('CANCEL'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Portrait check with keyboard inset
|
|
tester.view.physicalSize = const Size(400, 800);
|
|
tester.view.padding = const FakeViewPadding(top: 24);
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 280);
|
|
await tester.pumpAndSettle();
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.text('SAVE'), findsOneWidget);
|
|
});
|
|
}
|