Skip to content

Instantly share code, notes, and snippets.

@rubywai
Created June 7, 2025 06:55
Show Gist options
  • Select an option

  • Save rubywai/729c394f3a99b7942cf7c1fb1e2d15d6 to your computer and use it in GitHub Desktop.

Select an option

Save rubywai/729c394f3a99b7942cf7c1fb1e2d15d6 to your computer and use it in GitHub Desktop.
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