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.text('1 Public Sq, Dahlonega, GA'), findsOneWidget); 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); }, ); }); }