MotorSignal-App/test/address_autocomplete_field_...

271 lines
8.2 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/widgets/address_autocomplete_field.dart';
import 'package:motorcycle_dash/services/routes/route_provider.dart';
import 'package:motorcycle_dash/services/routes/route_providers.dart';
import 'fake_route_provider.dart';
void main() {
group('AddressAutocompleteField', () {
testWidgets('debounces input and displays suggestions', (tester) async {
final fake = FakeRouteProvider(
customSearchResults: [
const PlaceSearchResult(
providerId: 'p_1',
primaryText: 'Dahlonega Square',
secondaryText: 'Dahlonega, GA',
latitude: 34.5327,
longitude: -83.9849,
formattedAddress: '1 Public Sq, Dahlonega, GA',
),
],
);
ResolvedPlace? resolved;
await tester.pumpWidget(
ProviderScope(
overrides: [
activeRouteCalculationProvider.overrideWithValue(fake),
],
child: MaterialApp(
home: Scaffold(
body: AddressAutocompleteField(
labelText: 'Meetup Location',
onPlaceResolved: (p) => resolved = p,
),
),
),
),
);
await tester.pumpAndSettle();
// Enter 1 char -> should not search
await tester.enterText(find.byType(TextField), 'D');
await tester.pump(const Duration(milliseconds: 400));
expect(find.text('Dahlonega Square'), findsNothing);
// Enter 3 chars -> debounce 350ms -> suggestions appear
await tester.enterText(find.byType(TextField), 'Dah');
await tester.pump(const Duration(milliseconds: 200));
// Mid-debounce -> suggestions not yet displayed
expect(find.text('Dahlonega Square'), findsNothing);
await tester.pump(const Duration(milliseconds: 200));
await tester.pumpAndSettle();
// Suggestions visible
expect(find.text('Dahlonega Square'), findsOneWidget);
expect(find.text('Dahlonega, GA'), findsOneWidget);
expect(resolved, isNull);
// Tap suggestion to select
await tester.tap(find.text('Dahlonega Square'));
await tester.pumpAndSettle();
// Place is now resolved
expect(resolved, isNotNull);
expect(resolved!.displayName, 'Dahlonega Square');
expect(resolved!.latitude, 34.5327);
expect(resolved!.longitude, -83.9849);
expect(find.byIcon(Icons.check_circle), findsWidgets);
});
testWidgets('editing text invalidates prior resolution', (tester) async {
final fake = FakeRouteProvider(
customSearchResults: [
const PlaceSearchResult(
providerId: 'p_1',
primaryText: 'Blood Mountain',
secondaryText: 'Blairsville, GA',
latitude: 34.7398,
longitude: -83.9372,
),
],
);
ResolvedPlace? resolved;
await tester.pumpWidget(
ProviderScope(
overrides: [
activeRouteCalculationProvider.overrideWithValue(fake),
],
child: MaterialApp(
home: Scaffold(
body: AddressAutocompleteField(
labelText: 'Destination',
onPlaceResolved: (p) => resolved = p,
),
),
),
),
);
await tester.pumpAndSettle();
// Type and select
await tester.enterText(find.byType(TextField), 'Blood');
await tester.pump(const Duration(milliseconds: 400));
await tester.pumpAndSettle();
await tester.tap(find.text('Blood Mountain'));
await tester.pumpAndSettle();
expect(resolved, isNotNull);
// User edits the text -> resolution MUST be invalidated
await tester.enterText(find.byType(TextField), 'Blood Mountain modified');
expect(resolved, isNull);
});
testWidgets('displays no-results state when search yields no matches', (tester) async {
final fake = FakeRouteProvider(
customSearchResults: const [],
);
await tester.pumpWidget(
ProviderScope(
overrides: [
activeRouteCalculationProvider.overrideWithValue(fake),
],
child: MaterialApp(
home: Scaffold(
body: AddressAutocompleteField(
labelText: 'Stop Location',
onPlaceResolved: (_) {},
),
),
),
),
);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Nowhereville');
await tester.pump(const Duration(milliseconds: 400));
await tester.pumpAndSettle();
expect(find.text('No matching addresses found'), findsOneWidget);
});
testWidgets('displays error and retry when provider fails', (tester) async {
final fake = FakeRouteProvider(shouldFail: true);
await tester.pumpWidget(
ProviderScope(
overrides: [
activeRouteCalculationProvider.overrideWithValue(fake),
],
child: MaterialApp(
home: Scaffold(
body: AddressAutocompleteField(
labelText: 'Meetup Location',
onPlaceResolved: (_) {},
),
),
),
),
);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'ErrorPlace');
await tester.pump(const Duration(milliseconds: 400));
await tester.pumpAndSettle();
expect(find.text('Fake search error'), findsOneWidget);
expect(find.text('RETRY'), findsOneWidget);
});
testWidgets('clearing text resets state and invalidates resolution', (tester) async {
final fake = FakeRouteProvider();
ResolvedPlace? resolved;
await tester.pumpWidget(
ProviderScope(
overrides: [
activeRouteCalculationProvider.overrideWithValue(fake),
],
child: MaterialApp(
home: Scaffold(
body: AddressAutocompleteField(
labelText: 'Meetup Location',
onPlaceResolved: (p) => resolved = p,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Dahlonega');
await tester.pump(const Duration(milliseconds: 400));
await tester.pumpAndSettle();
await tester.tap(find.textContaining('Fake Result 1'));
await tester.pumpAndSettle();
expect(resolved, isNotNull);
// Tap clear icon
await tester.tap(find.byIcon(Icons.clear));
await tester.pumpAndSettle();
expect(resolved, isNull);
expect(find.text(''), findsOneWidget);
});
testWidgets('resolves place via provider if coordinates are missing in search result', (tester) async {
final fake = FakeRouteProvider(
customSearchResults: [
const PlaceSearchResult(
providerId: 'need_coords_id',
primaryText: 'Neels Gap Store',
secondaryText: 'Blairsville, GA',
// No coordinates
),
],
customResolvedPlace: const ResolvedPlace(
providerId: 'need_coords_id',
displayName: 'Neels Gap Store',
latitude: 34.8698,
longitude: -83.9182,
),
);
ResolvedPlace? resolved;
await tester.pumpWidget(
ProviderScope(
overrides: [
activeRouteCalculationProvider.overrideWithValue(fake),
],
child: MaterialApp(
home: Scaffold(
body: AddressAutocompleteField(
labelText: 'Destination',
onPlaceResolved: (p) => resolved = p,
),
),
),
),
);
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Neels');
await tester.pump(const Duration(milliseconds: 400));
await tester.pumpAndSettle();
await tester.tap(find.text('Neels Gap Store'));
await tester.pumpAndSettle();
expect(resolved, isNotNull);
expect(resolved!.latitude, 34.8698);
expect(resolved!.longitude, -83.9182);
});
});
}