mirror of https://github.com/flutter/samples.git
Compass App: Integration tests and image error handling (#2389)
This PR goes on top of PR #2385 adding integration test using the `integration_test` package. Adds `integration_test` folder with two test suits: - Local test: Uses the local dependency config that pulls data from the assets folder and has no login logic. - Remote test: Starts the dart server in the background and uses the remote dependency config, pulls data from the server and performs login/logout. To run the tests: ``` flutter test integration_test/app_server_data_test.dart ``` or ``` flutter test integration_test/app_local_data_test.dart ``` Running both at once with `flutter test integration_test` will likely fail, seems this issue is related: https://github.com/flutter/flutter/issues/101031 Also, this PR fixes exceptions being thrown by the network image library, now instead they get logged using the app `Logger`. ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.mdpull/2444/head
parent
bb58c63be5
commit
56bf31fa21
@ -1,3 +1,22 @@
|
||||
# compass_app
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
Run separately with:
|
||||
|
||||
**Integration tests with local data**
|
||||
|
||||
```
|
||||
flutter test integration_test/app_local_data_test.dart
|
||||
```
|
||||
|
||||
**Integration tests with background server and remote data**
|
||||
|
||||
```
|
||||
flutter test integration_test/app_server_data_test.dart
|
||||
```
|
||||
|
||||
Running the tests together with `flutter test integration_test` will fail.
|
||||
See: https://github.com/flutter/flutter/issues/101031
|
||||
|
@ -0,0 +1,96 @@
|
||||
import 'package:compass_app/config/dependencies.dart';
|
||||
import 'package:compass_app/main.dart';
|
||||
import 'package:compass_app/ui/activities/widgets/activities_screen.dart';
|
||||
import 'package:compass_app/ui/booking/widgets/booking_screen.dart';
|
||||
import 'package:compass_app/ui/core/ui/custom_checkbox.dart';
|
||||
import 'package:compass_app/ui/results/widgets/result_card.dart';
|
||||
import 'package:compass_app/ui/results/widgets/results_screen.dart';
|
||||
import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// This Integration Test launches the Compass-App with the local configuration.
|
||||
/// The app uses data from the assets folder to create a booking.
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('end-to-end test with local data', () {
|
||||
testWidgets('should load app', (tester) async {
|
||||
// Load app widget.
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: providersLocal,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Create booking', (tester) async {
|
||||
// Load app widget with local configuration
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: providersLocal,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Search destinations screen
|
||||
expect(find.byType(SearchFormScreen), findsOneWidget);
|
||||
|
||||
// Select Europe because it is always the first result
|
||||
await tester.tap(find.text('Europe'), warnIfMissed: false);
|
||||
|
||||
// Select dates
|
||||
await tester.tap(find.text('Add Dates'));
|
||||
await tester.pumpAndSettle();
|
||||
final tomorrow = DateTime.now().add(const Duration(days: 1)).day;
|
||||
final nextDay = DateTime.now().add(const Duration(days: 2)).day;
|
||||
// Select first and last widget that matches today number
|
||||
//and tomorrow number, sort of ensures a valid range
|
||||
await tester.tap(find.text(tomorrow.toString()).first);
|
||||
await tester.tap(find.text(nextDay.toString()).last);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Save'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Select guests
|
||||
await tester.tap(find.byKey(const ValueKey('add_guests')),
|
||||
warnIfMissed: false);
|
||||
|
||||
// Refresh screen state
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Perform search and navigate to next screen
|
||||
await tester.tap(find.byKey(const ValueKey('submit_button')));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Results Screen
|
||||
expect(find.byType(ResultsScreen), findsOneWidget);
|
||||
|
||||
// Amalfi Coast should be the first result for Europe
|
||||
// Tap and navigate to activities screen
|
||||
await tester.tap(find.byType(ResultCard).first);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Activities Screen
|
||||
expect(find.byType(ActivitiesScreen), findsOneWidget);
|
||||
|
||||
// Select one activity
|
||||
await tester.tap(find.byType(CustomCheckbox).first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('1 selected'), findsOneWidget);
|
||||
|
||||
// Submit selection
|
||||
await tester.tap(find.byKey(const ValueKey('confirm-button')));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Should be at booking screen
|
||||
expect(find.byType(BookingScreen), findsOneWidget);
|
||||
expect(find.text('Amalfi Coast'), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:compass_app/config/dependencies.dart';
|
||||
import 'package:compass_app/main.dart';
|
||||
import 'package:compass_app/ui/activities/widgets/activities_screen.dart';
|
||||
import 'package:compass_app/ui/auth/login/widgets/login_screen.dart';
|
||||
import 'package:compass_app/ui/auth/logout/widgets/logout_button.dart';
|
||||
import 'package:compass_app/ui/booking/widgets/booking_screen.dart';
|
||||
import 'package:compass_app/ui/core/ui/custom_checkbox.dart';
|
||||
import 'package:compass_app/ui/core/ui/home_button.dart';
|
||||
import 'package:compass_app/ui/results/widgets/result_card.dart';
|
||||
import 'package:compass_app/ui/results/widgets/results_screen.dart';
|
||||
import 'package:compass_app/ui/search_form/widgets/search_form_screen.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// This Integration Test starts the Dart server
|
||||
/// before launching the Compass-App with the remote configuration.
|
||||
/// The app connects to its endpoints to perform login and create a booking.
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('end-to-end test with remote data', () {
|
||||
final port = '8080';
|
||||
late Process p;
|
||||
|
||||
setUpAll(() async {
|
||||
// Clear any stored shared preferences
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
await sharedPreferences.clear();
|
||||
|
||||
// Start the dart server
|
||||
p = await Process.start(
|
||||
'dart',
|
||||
['run', 'bin/compass_server.dart'],
|
||||
environment: {'PORT': port},
|
||||
// Relative to the app/ folder
|
||||
workingDirectory: '../server',
|
||||
);
|
||||
// Wait for server to start and print to stdout.
|
||||
await p.stdout.first;
|
||||
});
|
||||
|
||||
tearDownAll(() => p.kill());
|
||||
|
||||
testWidgets('should load app', (tester) async {
|
||||
// Load app widget.
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: providersRemote,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Login screen because logget out
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Create booking', (tester) async {
|
||||
// Load app widget with local configuration
|
||||
await tester.pumpWidget(
|
||||
MultiProvider(
|
||||
providers: providersRemote,
|
||||
child: const MainApp(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Login screen because logget out
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
|
||||
// Perform login (credentials are prefilled)
|
||||
await tester.tap(find.text('Login'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Search destinations screen
|
||||
expect(find.byType(SearchFormScreen), findsOneWidget);
|
||||
|
||||
// Select Europe because it is always the first result
|
||||
await tester.tap(find.text('Europe'), warnIfMissed: false);
|
||||
|
||||
// Select dates
|
||||
await tester.tap(find.text('Add Dates'));
|
||||
await tester.pumpAndSettle();
|
||||
final tomorrow = DateTime.now().add(const Duration(days: 1)).day;
|
||||
final nextDay = DateTime.now().add(const Duration(days: 2)).day;
|
||||
// Select first and last widget that matches today number
|
||||
//and tomorrow number, sort of ensures a valid range
|
||||
await tester.tap(find.text(tomorrow.toString()).first);
|
||||
await tester.tap(find.text(nextDay.toString()).last);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Save'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Select guests
|
||||
await tester.tap(find.byKey(const ValueKey('add_guests')),
|
||||
warnIfMissed: false);
|
||||
|
||||
// Refresh screen state
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Perform search and navigate to next screen
|
||||
await tester.tap(find.byKey(const ValueKey('submit_button')));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Results Screen
|
||||
expect(find.byType(ResultsScreen), findsOneWidget);
|
||||
|
||||
// Amalfi Coast should be the first result for Europe
|
||||
// Tap and navigate to activities screen
|
||||
await tester.tap(find.byType(ResultCard).first);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Activities Screen
|
||||
expect(find.byType(ActivitiesScreen), findsOneWidget);
|
||||
|
||||
// Select one activity
|
||||
await tester.tap(find.byType(CustomCheckbox).first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('1 selected'), findsOneWidget);
|
||||
|
||||
// Submit selection
|
||||
await tester.tap(find.byKey(const ValueKey('confirm-button')));
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Should be at booking screen
|
||||
expect(find.byType(BookingScreen), findsOneWidget);
|
||||
expect(find.text('Amalfi Coast'), findsOneWidget);
|
||||
|
||||
// Navigate back to home
|
||||
await tester.tap(find.byType(HomeButton).first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(SearchFormScreen), findsOneWidget);
|
||||
|
||||
// Perform logout
|
||||
await tester.tap(find.byType(LogoutButton).first);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(LoginScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final _log = Logger('ImageErrorListener');
|
||||
|
||||
void imageErrorListener(Object error) {
|
||||
_log.warning('Failed to load image', error);
|
||||
}
|
Loading…
Reference in new issue