Created
June 7, 2025 06:55
-
-
Save rubywai/729c394f3a99b7942cf7c1fb1e2d15d6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:flutter_test/flutter_test.dart'; | |
| import 'package:mockito/mockito.dart'; | |
| import 'package:get_it/get_it.dart'; | |
| import 'package:flutter_bloc_weather/features/search/ui/city_search_page.dart'; | |
| import 'package:flutter_bloc_weather/features/search/data/services/search_api_services.dart'; | |
| import 'package:flutter_bloc_weather/features/search/data/model/city_model.dart'; | |
| import '../bloc/api_mock.mocks.dart'; | |
| void main() { | |
| final sl = GetIt.instance; | |
| late MockSearchApiServices mockService; | |
| setUp(() { | |
| mockService = MockSearchApiServices(); | |
| if (sl.isRegistered<SearchApiServices>()) { | |
| sl.unregister<SearchApiServices>(); | |
| } | |
| sl.registerSingleton<SearchApiServices>(mockService); | |
| }); | |
| tearDown(() => sl.reset()); | |
| group('CitySearchPage Widget Tests', () { | |
| testWidgets('should display initial state with search prompt', | |
| (tester) async { | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| expect(find.text('Weather Application'), findsOneWidget); | |
| expect(find.text('Please search city'), findsOneWidget); | |
| expect(find.byIcon(Icons.search), findsOneWidget); | |
| expect(find.byType(TextField), findsOneWidget); | |
| expect(find.byIcon(Icons.search_rounded), findsOneWidget); | |
| expect(find.text('Search city'), findsOneWidget); // hintText | |
| }); | |
| testWidgets('should not search when text field is empty', (tester) async { | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pump(); | |
| // Should remain in initial state | |
| expect(find.text('Please search city'), findsOneWidget); | |
| expect(find.byIcon(Icons.search), findsOneWidget); | |
| }); | |
| testWidgets('should not search when text field contains only spaces', | |
| (tester) async { | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), ' '); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pump(); | |
| // Should remain in initial state | |
| expect(find.text('Please search city'), findsOneWidget); | |
| expect(find.byIcon(Icons.search), findsOneWidget); | |
| }); | |
| testWidgets('should show loading state during search', (tester) async { | |
| final mockResult = CityModel(results: [ | |
| CitySearchModel( | |
| id: 1, | |
| name: 'Yangon', | |
| latitude: 16.8, | |
| longitude: 96.2, | |
| country: 'Myanmar', | |
| ), | |
| ]); | |
| when(mockService.searchCities(name: 'Yangon', count: 15)).thenAnswer( | |
| (_) async { | |
| await Future.delayed(const Duration(milliseconds: 500)); | |
| return mockResult; | |
| }, | |
| ); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'Yangon'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| // Check loading state | |
| await tester.pump(const Duration(milliseconds: 100)); | |
| expect(find.byType(CircularProgressIndicator), findsOneWidget); | |
| // Wait for completion | |
| await tester.pumpAndSettle(); | |
| }); | |
| testWidgets('should display search results successfully', (tester) async { | |
| final mockResult = CityModel(results: [ | |
| CitySearchModel( | |
| id: 1, | |
| name: 'Yangon', | |
| latitude: 16.8, | |
| longitude: 96.2, | |
| country: 'Myanmar', | |
| ), | |
| CitySearchModel( | |
| id: 2, | |
| name: 'Mandalay', | |
| latitude: 21.9, | |
| longitude: 96.1, | |
| country: 'Myanmar', | |
| ), | |
| ]); | |
| when(mockService.searchCities(name: 'Myanmar', count: 15)).thenAnswer( | |
| (_) async => mockResult, | |
| ); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'Myanmar'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| // Verify results are displayed | |
| expect(find.byType(ListTile), findsNWidgets(2)); | |
| expect(find.byType(Card), findsNWidgets(2)); | |
| // Check specific city names in ListTiles only | |
| final listTiles = tester.widgetList<ListTile>(find.byType(ListTile)); | |
| expect(listTiles.any((tile) => (tile.title as Text).data == 'Yangon'), | |
| isTrue); | |
| expect(listTiles.any((tile) => (tile.title as Text).data == 'Mandalay'), | |
| isTrue); | |
| }); | |
| testWidgets('should display empty results', (tester) async { | |
| final mockResult = CityModel(results: []); | |
| when(mockService.searchCities(name: 'NonExistentCity', count: 15)) | |
| .thenAnswer( | |
| (_) async => mockResult, | |
| ); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'NonExistentCity'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| // Should show ListView but with no items | |
| expect(find.byType(ListView), findsOneWidget); | |
| expect(find.byType(ListTile), findsNothing); | |
| }); | |
| testWidgets('should show error state when search fails', (tester) async { | |
| when(mockService.searchCities(name: 'ErrorCity', count: 15)) | |
| .thenThrow(Exception("Network error")); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'ErrorCity'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| expect(find.text('Failed to search'), findsOneWidget); | |
| expect(find.text('Try Again'), findsOneWidget); | |
| expect(find.byType(TextButton), findsOneWidget); | |
| }); | |
| testWidgets('should retry search when Try Again is tapped', (tester) async { | |
| // First call fails | |
| when(mockService.searchCities(name: 'RetryCity', count: 15)) | |
| .thenThrow(Exception("Network error")); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'RetryCity'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| expect(find.text('Failed to search'), findsOneWidget); | |
| expect(find.text('Try Again'), findsOneWidget); | |
| // Mock successful retry | |
| final mockResult = CityModel(results: [ | |
| CitySearchModel( | |
| id: 1, | |
| name: 'RetryCity', | |
| latitude: 10.0, | |
| longitude: 20.0, | |
| country: 'TestCountry', | |
| ), | |
| ]); | |
| when(mockService.searchCities(name: 'RetryCity', count: 15)) | |
| .thenAnswer((_) async => mockResult); | |
| // Tap retry button | |
| await tester.tap(find.text('Try Again')); | |
| await tester.pumpAndSettle(); | |
| // Should show successful results | |
| expect(find.byType(ListTile), findsOneWidget); | |
| final listTile = tester.widget<ListTile>(find.byType(ListTile)); | |
| expect((listTile.title as Text).data, equals('RetryCity')); | |
| expect((listTile.subtitle as Text).data, equals('TestCountry')); | |
| }); | |
| testWidgets('should handle multiple searches with different queries', | |
| (tester) async { | |
| // First search | |
| final firstResult = CityModel(results: [ | |
| CitySearchModel( | |
| id: 1, | |
| name: 'London', | |
| latitude: 51.5, | |
| longitude: -0.1, | |
| country: 'UK', | |
| ), | |
| ]); | |
| when(mockService.searchCities(name: 'London', count: 15)) | |
| .thenAnswer((_) async => firstResult); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'London'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| expect(find.byType(ListTile), findsOneWidget); | |
| final firstListTile = tester.widget<ListTile>(find.byType(ListTile)); | |
| expect((firstListTile.title as Text).data, equals('London')); | |
| expect((firstListTile.subtitle as Text).data, equals('UK')); | |
| // Second search | |
| final secondResult = CityModel(results: [ | |
| CitySearchModel( | |
| id: 2, | |
| name: 'Paris', | |
| latitude: 48.9, | |
| longitude: 2.3, | |
| country: 'France', | |
| ), | |
| ]); | |
| when(mockService.searchCities(name: 'Paris', count: 15)) | |
| .thenAnswer((_) async => secondResult); | |
| await tester.enterText(find.byType(TextField), 'Paris'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| expect(find.byType(ListTile), findsOneWidget); | |
| final secondListTile = tester.widget<ListTile>(find.byType(ListTile)); | |
| expect((secondListTile.title as Text).data, equals('Paris')); | |
| expect((secondListTile.subtitle as Text).data, equals('France')); | |
| }); | |
| testWidgets('should handle ListTile tap event', (tester) async { | |
| final mockResult = CityModel(results: [ | |
| CitySearchModel( | |
| id: 1, | |
| name: 'TestCity', | |
| latitude: 10.0, | |
| longitude: 20.0, | |
| country: 'TestCountry', | |
| ), | |
| ]); | |
| when(mockService.searchCities(name: 'TestCity', count: 15)) | |
| .thenAnswer((_) async => mockResult); | |
| await tester.pumpWidget(const MaterialApp(home: CitySearchPage())); | |
| await tester.enterText(find.byType(TextField), 'TestCity'); | |
| await tester.tap(find.byIcon(Icons.search_rounded)); | |
| await tester.pumpAndSettle(); | |
| // Tap on the ListTile (currently does nothing but should not crash) | |
| await tester.tap(find.byType(ListTile)); | |
| await tester.pump(); | |
| // Verify the UI is still stable by checking ListTile content | |
| expect(find.byType(ListTile), findsOneWidget); | |
| final listTile = tester.widget<ListTile>(find.byType(ListTile)); | |
| expect((listTile.title as Text).data, equals('TestCity')); | |
| expect((listTile.subtitle as Text).data, equals('TestCountry')); | |
| }); | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment