From 1670f19511d870f9c2da32f75311e02586ab407a Mon Sep 17 00:00:00 2001 From: Abestanis Date: Wed, 11 May 2022 23:16:40 +0200 Subject: [PATCH] Improve the channel observer and split the tests better --- test/observer/app.dart | 30 ----------- test/observer/observer.dart | 2 +- test/observer/system.dart | 31 +++++++++++ test/observer/toast.dart | 43 +++++---------- test/routes/home_route_test.dart | 91 +++++++++++--------------------- 5 files changed, 76 insertions(+), 121 deletions(-) delete mode 100644 test/observer/app.dart create mode 100644 test/observer/system.dart diff --git a/test/observer/app.dart b/test/observer/app.dart deleted file mode 100644 index 9e6913e956..0000000000 --- a/test/observer/app.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/services.dart'; - -import '../test.dart'; - -/// An observer for the application state. -class AppObserver { - - /// Whether a close request was recorded. - bool _haveCloseRequest = false; - - /// Create a new application state observer, which automatically - /// unregisters any previously created observer. - AppObserver(WidgetTester tester) { - tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) { - if (call.method == 'SystemNavigator.pop') { - _haveCloseRequest = true; - return null; - } - return null; // Ignore unimplemented method calls - }); - } - - /// Execute the provided [callable] and return whether - /// an application close request was made by it. - Future haveCloseRequest(Future Function() callable) async { - _haveCloseRequest = false; - await callable(); - return _haveCloseRequest; - } -} diff --git a/test/observer/observer.dart b/test/observer/observer.dart index 4c48ccabf9..a70b18b673 100644 --- a/test/observer/observer.dart +++ b/test/observer/observer.dart @@ -1,2 +1,2 @@ export 'toast.dart'; -export 'app.dart'; +export 'system.dart'; diff --git a/test/observer/system.dart b/test/observer/system.dart new file mode 100644 index 0000000000..7525fa6a9f --- /dev/null +++ b/test/observer/system.dart @@ -0,0 +1,31 @@ +import 'package:flutter/services.dart'; + +import '../test.dart'; + +/// An observer for the system channel. +class SystemChannelObserver { + + int _closeRequests = 0; /// How many close request was recorded since the last observation. + int get closeRequests { + final numRequests = _closeRequests; + clearCloseRequest(); + return numRequests; + } + + /// Create a new system channel observer, which automatically + /// unregisters any previously created observer. + SystemChannelObserver(WidgetTester tester) { + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) { + if (call.method == 'SystemNavigator.pop') { + _closeRequests++; + return null; + } + return null; // Ignore unimplemented method calls + }); + } + + /// Reset the number of recorded close requests to zero. + void clearCloseRequest() { + _closeRequests = 0; + } +} diff --git a/test/observer/toast.dart b/test/observer/toast.dart index b10fd3d2d2..cb91fcea58 100644 --- a/test/observer/toast.dart +++ b/test/observer/toast.dart @@ -3,45 +3,30 @@ import 'package:flutter/services.dart'; import '../test.dart'; /// An observer for toast messages from the flutter toast package. -class ToastObserver { +class ToastChannelObserver { /// The method channel used by the flutter toast package static const MethodChannel _channel = MethodChannel('PonnamKarthik/fluttertoast'); - /// The arguments for the last requested toast message. - Map? lastToastMessage; - - /// Create a new toast observer, which automatically + Map? _lastToastArguments; /// The arguments for the last requested toast message. + String? get lastToastMessage { + final lastArguments = _lastToastArguments; + clearLastToast(); + return lastArguments?['msg'] as String?; + } + + /// Create a new toast channel observer, which automatically /// unregisters any previously created observer. - ToastObserver(WidgetTester tester) { + ToastChannelObserver(WidgetTester tester) { tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(_channel, (call) { if (call.method == 'showToast') { - lastToastMessage = Map.castFrom(call.arguments); + _lastToastArguments = Map.castFrom(call.arguments); return null; } return null; // Ignore unimplemented method calls }); } - /// Verify that the provided [callable] shows a toast. - /// If provided, also verify that the toast showed the given [message]. - /// If no toast was shown or the message doesn't match, - /// fail with the given [reason]. - Future expectShowsToast(Future Function() callable, {String? message, String? reason}) async { - lastToastMessage = null; - await callable(); - expect(lastToastMessage, isNotNull, - reason: 'Expected a toast to be shown' + (reason != null ? ': $reason' : '')); - if (message != null) { - expect(lastToastMessage!['msg'] as String?, equals(message), - reason: 'Expected a toast to show the expected message' + (reason != null ? ': $reason' : '')); - } - } - - /// Verify that the provided [callable] doesn't show a toast. - /// Otherwise fail with the given [reason]. - Future expectShowsNoToast(Future Function() callable, {String? reason}) async { - lastToastMessage = null; - await callable(); - expect(lastToastMessage, isNull, - reason: 'Expected no toast to be shown' + (reason != null ? ': $reason' : '')); + /// Forget the last received toast. + void clearLastToast() { + _lastToastArguments = null; } } diff --git a/test/routes/home_route_test.dart b/test/routes/home_route_test.dart index 993400d2cd..f7f97f25ef 100644 --- a/test/routes/home_route_test.dart +++ b/test/routes/home_route_test.dart @@ -1,4 +1,5 @@ import 'package:back_button_interceptor/back_button_interceptor.dart'; +import 'package:sweyer/constants.dart'; import '../observer/observer.dart'; import '../test.dart'; @@ -71,74 +72,42 @@ void main() { }); }); - testWidgets('app shows exit confirmation toast', (WidgetTester tester) async { + testWidgets('app shows exit confirmation toast if enabled in the preferences', (WidgetTester tester) async { await Prefs.confirmExitingWithBackButton.set(true); - final ToastObserver toastObserver = ToastObserver(tester); - final AppObserver appObserver = AppObserver(tester); await tester.runAppTest(() async { - expect( - await appObserver.haveCloseRequest( - () => toastObserver.expectShowsToast( - BackButtonInterceptor.popRoute, - message: l10n.pressOnceAgainToExit, - reason: 'Expected the app to ask for confirmation before exiting', - ), - ), - isFalse, - reason: 'Expected the app not to close after showing the toast', - ); - await tester.binding.delayed(const Duration(seconds: 5)); - expect( - await appObserver.haveCloseRequest( - () => toastObserver.expectShowsToast( - BackButtonInterceptor.popRoute, - message: l10n.pressOnceAgainToExit, - reason: 'Expected the app to ask for confirmation before exiting after the previous message timed out', - ), - ), - isFalse, - reason: 'Expected the app not to close after showing the toast', - ); - expect( - await appObserver.haveCloseRequest( - () => toastObserver.expectShowsNoToast( - BackButtonInterceptor.popRoute, + final SystemChannelObserver systemObserver = SystemChannelObserver(tester); + final ToastChannelObserver toastObserver = ToastChannelObserver(tester); + await BackButtonInterceptor.popRoute(); + expect(toastObserver.lastToastMessage, l10n.pressOnceAgainToExit, + reason: 'Expected the app to ask for confirmation before exiting'); + expect(systemObserver.closeRequests, 0, + reason: 'Expected the app not to close after showing the toast'); + await tester.binding.delayed(Config.BACK_PRESS_CLOSE_TIMEOUT + const Duration(milliseconds: 1)); + await BackButtonInterceptor.popRoute(); + expect(toastObserver.lastToastMessage, l10n.pressOnceAgainToExit, + reason: 'Expected the app to ask for confirmation before exiting after the previous message timed out'); + expect(systemObserver.closeRequests, 0, + reason: 'Expected the app not to close after showing the toast'); + await tester.binding.delayed(Config.BACK_PRESS_CLOSE_TIMEOUT - const Duration(milliseconds: 1)); + await BackButtonInterceptor.popRoute(); + expect(toastObserver.lastToastMessage, null, reason: 'Expected the app to show no toast when pressing the back button a second time', - ), - ), - isTrue, - ); + ); + expect(systemObserver.closeRequests, 1, + reason: 'Expected the app to close if back is pressed after the confirmation toast was shown'); }); }); - testWidgets('app respects the exit confirmation preference', (WidgetTester tester) async { - await Prefs.confirmExitingWithBackButton.set(true); - final ToastObserver toastObserver = ToastObserver(tester); - final AppObserver appObserver = AppObserver(tester); + testWidgets('app does not ask for exit confirmation if disabled in the preferences', (WidgetTester tester) async { + await Prefs.confirmExitingWithBackButton.set(false); await tester.runAppTest(() async { - expect( - await appObserver.haveCloseRequest( - () => toastObserver.expectShowsToast( - BackButtonInterceptor.popRoute, - message: l10n.pressOnceAgainToExit, - reason: 'Expected the app to ask for confirmation before exiting', - ), - ), - isFalse, - reason: 'Expected the app not to close after showing the toast', - ); - await Prefs.confirmExitingWithBackButton.set(false); - await tester.binding.delayed(const Duration(seconds: 5)); - expect( - await appObserver.haveCloseRequest( - () => toastObserver.expectShowsNoToast( - BackButtonInterceptor.popRoute, - reason: 'Expected the app to show no toast when pressing the back button after disabling it', - ), - ), - isTrue, - reason: 'Expected the confirmation toast to be disabled', - ); + final SystemChannelObserver systemObserver = SystemChannelObserver(tester); + final ToastChannelObserver toastObserver = ToastChannelObserver(tester); + await BackButtonInterceptor.popRoute(); + expect(toastObserver.lastToastMessage, null, + reason: 'Expected the app to show no toast when pressing the back button after disabling it'); + expect(systemObserver.closeRequests, 1, + reason: 'Expected the app to close if the confirmation toast is disabled'); }); }); }