diff --git a/lib/locator.dart b/lib/locator.dart index a980f45f3..72e92050b 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -8,6 +8,7 @@ import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/org_service.dart'; import 'package:talawa/services/post_service.dart'; @@ -70,12 +71,15 @@ final connectivity = locator(); ///creating GetIt for OrganizationService. final organizationService = locator(); -///creating GetIt for ImageCropper. -final imageCropper = locator(); - ///creating GetIt for ImagePicker. final imagePicker = locator(); +///creating GetIt for ImageService. +final imageService = locator(); + +///creating GetIt for ImageCropper. +final imageCropper = locator(); + /// This function registers the widgets/objects in "GetIt". /// /// **params**: @@ -104,8 +108,9 @@ void setupLocator() { locator.registerLazySingleton(() => MultiMediaPickerService()); locator.registerLazySingleton(() => Connectivity()); locator.registerLazySingleton(() => ChatService()); - locator.registerLazySingleton(() => ImageCropper()); + locator.registerLazySingleton(() => ImageService()); locator.registerLazySingleton(() => ImagePicker()); + locator.registerLazySingleton(() => ImageCropper()); //graphql locator.registerSingleton(GraphqlConfig()); diff --git a/lib/services/image_service.dart b/lib/services/image_service.dart new file mode 100644 index 000000000..6bdb12f63 --- /dev/null +++ b/lib/services/image_service.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:talawa/locator.dart'; + +/// ImageService class provides different functions as service in the context of Images. +/// +/// Services include: +/// * `cropImage` +/// * `convertToBase64` +class ImageService { + /// instance of ImageCropper. + final ImageCropper _imageCropper = locator(); + + /// This function is used to crop the image selected by the user. + /// + /// The function accepts a `File` type image and returns `File` type of cropped image. + /// + /// **params**: + /// * `imageFile`: the image file to be cropped. + /// + /// **returns**: + /// * `Future`: the image after been cropped. + Future cropImage({required File imageFile}) async { + // try, to crop the image and returns a File with cropped image path. + try { + final CroppedFile? croppedImage = await _imageCropper.cropImage( + sourcePath: imageFile.path, + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.original, + ], + uiSettings: [ + AndroidUiSettings( + toolbarTitle: 'Crop Image', + toolbarColor: const Color(0xff18191A), + toolbarWidgetColor: Colors.white, + backgroundColor: Colors.black, + cropGridColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false, + ), + IOSUiSettings( + minimumAspectRatio: 1.0, + ), + ], + ); + if (croppedImage != null) { + return File(croppedImage.path); + } + } catch (e) { + print( + "MultiMediaPickerService : Exception occurred while cropping Image", + ); + } + return null; + } + + /// This function is used to convert the image into Base64 format. + /// + /// **params**: + /// * `file`: Takes the image in format of file. + /// + /// **returns**: + /// * `Future`: image in string format + Future convertToBase64(File file) async { + try { + final List bytes = await file.readAsBytes(); + final String base64String = base64Encode(bytes); + print(base64String); + return base64String; + } catch (error) { + return ''; + } + } +} diff --git a/lib/services/third_party_service/multi_media_pick_service.dart b/lib/services/third_party_service/multi_media_pick_service.dart index b793f4b2d..9ebf128c0 100644 --- a/lib/services/third_party_service/multi_media_pick_service.dart +++ b/lib/services/third_party_service/multi_media_pick_service.dart @@ -6,12 +6,11 @@ Service usage: "add_post_view_model.dart" import 'dart:async'; import 'dart:io'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:talawa/locator.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/widgets/custom_alert_dialog.dart'; @@ -26,12 +25,14 @@ class MultiMediaPickerService { MultiMediaPickerService() { _picker = locator(); _fileStream = _fileStreamController.stream.asBroadcastStream(); + _imageService = locator(); } //Local Variables final StreamController _fileStreamController = StreamController(); late Stream _fileStream; late ImagePicker _picker; + late ImageService _imageService; //Getters /// This function returns the stream of files. @@ -63,23 +64,14 @@ class MultiMediaPickerService { ); // if image is selected or not null, call the cropImage function that provide service to crop the selected image. if (image != null) { - return await cropImage(imageFile: File(image.path)); + return await _imageService.cropImage(imageFile: File(image.path)); } } catch (e) { // if the permission denied or error occurs. if (e is PlatformException && e.code == 'camera_access_denied') { // push the dialog alert with the message. locator().pushDialog( - CustomAlertDialog( - success: () { - locator().pop(); - openAppSettings(); - }, - dialogTitle: 'Permission Denied', - successText: 'SETTINGS', - dialogSubTitle: - "Camera permission is required, to use this feature, give permission from app settings", - ), + permissionDeniedDialog(), ); } print( @@ -89,47 +81,23 @@ class MultiMediaPickerService { return null; } - /// This function is used to crop the image selected by the user. - /// - /// The function accepts a `File` type image and returns `File` type of cropped image. + /// This function returns the permission denied dialog. /// /// **params**: - /// * `imageFile`: the image file to be cropped. + /// None /// /// **returns**: - /// * `Future`: the image after been cropped. - Future cropImage({required File imageFile}) async { - // try, to crop the image and returns a File with cropped image path. - try { - final CroppedFile? croppedImage = await locator().cropImage( - sourcePath: imageFile.path, - aspectRatioPresets: [ - CropAspectRatioPreset.square, - CropAspectRatioPreset.original, - ], - uiSettings: [ - AndroidUiSettings( - toolbarTitle: 'Crop Image', - toolbarColor: const Color(0xff18191A), - toolbarWidgetColor: Colors.white, - backgroundColor: Colors.black, - cropGridColor: Colors.white, - initAspectRatio: CropAspectRatioPreset.original, - lockAspectRatio: false, - ), - IOSUiSettings( - minimumAspectRatio: 1.0, - ), - ], - ); - if (croppedImage != null) { - return File(croppedImage.path); - } - } catch (e) { - print( - "MultiMediaPickerService : Exception occurred while cropping Image", - ); - } - return null; + /// * `CustomAlertDialog`: Custom Alert Dialog widget. + CustomAlertDialog permissionDeniedDialog() { + return CustomAlertDialog( + success: () { + locator().pop(); + openAppSettings(); + }, + dialogTitle: 'Permission Denied', + successText: 'SETTINGS', + dialogSubTitle: + "Camera permission is required, to use this feature, give permission from app settings", + ); } } diff --git a/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart b/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart index 0bcc7ea1a..69697aa50 100644 --- a/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart +++ b/lib/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -7,6 +6,7 @@ import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/services/database_mutation_functions.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/services/user_config.dart'; @@ -22,6 +22,7 @@ class AddPostViewModel extends BaseModel { //Services late MultiMediaPickerService _multiMediaPickerService; late NavigationService _navigationService; + late ImageService _imageService; // ignore: unused_field late File? _imageFile; @@ -65,7 +66,7 @@ class AddPostViewModel extends BaseModel { /// **returns**: /// * `Future`: define_the_return Future setImageInBase64(File file) async { - _imageInBase64 = await convertToBase64(file); + _imageInBase64 = await _imageService.convertToBase64(file); notifyListeners(); } @@ -121,33 +122,15 @@ class AddPostViewModel extends BaseModel { void initialise() { _navigationService = locator(); _imageFile = null; + _imageInBase64 = null; _multiMediaPickerService = locator(); + _imageService = locator(); if (!demoMode) { _dbFunctions = locator(); _selectedOrg = locator().currentOrg; } } - /// to convert the image in base64. - /// - /// - /// **params**: - /// * `file`: file of image clicked. - /// - /// **returns**: - /// * `Future`: Future string containing the base 64 format image - Future convertToBase64(File file) async { - try { - final List bytes = await file.readAsBytes(); - final String base64String = base64Encode(bytes); - print(base64String); - _imageInBase64 = base64String; - return base64String; - } catch (error) { - return ''; - } - } - /// This function is used to get the image from gallery. /// /// The function uses the `_multiMediaPickerService` services. @@ -164,7 +147,7 @@ class AddPostViewModel extends BaseModel { if (image != null) { _imageFile = image; // convertImageToBase64(image.path); - convertToBase64(image); + _imageInBase64 = await _imageService.convertToBase64(image); // print(_imageInBase64); _navigationService.showTalawaErrorSnackBar( "Image is added", diff --git a/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart b/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart index be1a52fdb..ffb1baa6a 100644 --- a/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart +++ b/lib/view_model/after_auth_view_models/profile_view_models/edit_profile_view_model.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -63,25 +62,6 @@ class EditProfilePageViewModel extends BaseModel { } } - /// This function is used to convert the image into Base64 format. - /// - /// **params**: - /// * `file`: Takes the image in format of file. - /// - /// **returns**: - /// * `Future`: image in string format - Future convertToBase64(File file) async { - try { - final List bytes = await file.readAsBytes(); - final String base64String = base64Encode(bytes); - print(base64String); - imageFile = base64String as File?; - return base64String; - } catch (error) { - return ''; - } - } - /// This function remove the selected image. /// /// **params**: diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index 6bafa6645..5a406138f 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -25,6 +25,7 @@ import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/org_service.dart'; import 'package:talawa/services/post_service.dart'; @@ -53,6 +54,7 @@ import 'package:talawa/view_model/theme_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/custom_drawer_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/like_button_view_model.dart'; import 'package:talawa/view_model/widgets_view_models/progress_dialog_view_model.dart'; +import '../service_tests/image_service_test.dart'; import '../views/main_screen_test.dart'; import 'test_helpers.mocks.dart'; @@ -427,6 +429,13 @@ ImageCropper getAndRegisterImageCropper() { return service; } +ImageService getAndRegisterImageService() { + _removeRegistrationIfExists(); + final service = MockImageService(); + locator.registerLazySingleton(() => service); + return service; +} + ImagePicker getAndRegisterImagePicker() { _removeRegistrationIfExists(); final service = MockImagePicker(); diff --git a/test/helpers/test_locator.dart b/test/helpers/test_locator.dart index 1cfc71fdd..f11dcb1d9 100644 --- a/test/helpers/test_locator.dart +++ b/test/helpers/test_locator.dart @@ -9,6 +9,7 @@ import 'package:talawa/services/comment_service.dart'; import 'package:talawa/services/database_mutation_functions.dart'; import 'package:talawa/services/event_service.dart'; import 'package:talawa/services/graphql_config.dart'; +import 'package:talawa/services/image_service.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/org_service.dart'; import 'package:talawa/services/post_service.dart'; @@ -55,8 +56,9 @@ final eventService = locator(); final commentsService = locator(); final postService = locator(); final mainScreenViewModel = locator(); -final imageCropper = locator(); +final imageService = locator(); final imagePicker = locator(); +final imageCropper = locator(); void testSetupLocator() { //services @@ -74,8 +76,9 @@ void testSetupLocator() { locator.registerLazySingleton(() => EventService()); locator.registerLazySingleton(() => CommentService()); locator.registerLazySingleton(() => MultiMediaPickerService()); - locator.registerLazySingleton(() => ImageCropper()); + locator.registerLazySingleton(() => ImageService()); locator.registerLazySingleton(() => ImagePicker()); + locator.registerLazySingleton(() => ImageCropper()); locator.registerSingleton(() => OrganizationService()); //graphql diff --git a/test/service_tests/image_service_test.dart b/test/service_tests/image_service_test.dart new file mode 100644 index 000000000..8700ab8db --- /dev/null +++ b/test/service_tests/image_service_test.dart @@ -0,0 +1,106 @@ +// ignore_for_file: talawa_api_doc +// ignore_for_file: talawa_good_doc_comments + +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:mockito/mockito.dart'; +import 'package:talawa/services/image_service.dart'; + +import '../helpers/test_helpers.dart'; +import '../helpers/test_locator.dart'; + +class MockImageService extends Mock implements ImageService { + @override + Future convertToBase64(File file) async { + return ""; + } +} + +void main() { + testSetupLocator(); + + setUpAll(() { + registerServices(); + }); + + group('Tests for Crop Image', () { + test("test no image provided for the image cropper", () async { + const path = 'test'; + final file = await imageService.cropImage(imageFile: File(path)); + expect(file?.path, null); + }); + + test("crop image method", () async { + final mockImageCropper = imageCropper; + + const path = "test"; + final fakefile = File(path); + final croppedFile = CroppedFile("fakeCropped"); + + when( + mockImageCropper.cropImage( + sourcePath: "test", + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.original, + ], + uiSettings: anyNamed('uiSettings'), + ), + ).thenAnswer((realInvocation) async => croppedFile); + final file = await imageService.cropImage(imageFile: fakefile); + // verify(mockImageCropper.cropImage(sourcePath: fakefile.path)); + expect(file?.path, croppedFile.path); + }); + + test("error in crop image", () async { + final mockImageCropper = locator(); + const path = "test"; + final fakefile = File(path); + final printed = []; + when( + mockImageCropper.cropImage( + sourcePath: "test", + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.original, + ], + uiSettings: anyNamed('uiSettings'), + ), + ).thenThrow(Exception()); + runZoned( + () async { + await imageService.cropImage(imageFile: fakefile); + }, + zoneSpecification: ZoneSpecification( + print: (self, parent, zone, line) { + printed.add(line); + }, + ), + ); + expect( + printed[0], + "MultiMediaPickerService : Exception occurred while cropping Image", + ); + }); + }); + + group('Tests for convertToBase64', () { + test('convertToBase64 converts file to base64 string', () async { + //using this asset as the test asset + final file = File('assets/images/Group 8948.png'); + final fileString = await imageService.convertToBase64(file); + print(fileString); + }); + + test( + 'Check if convertToBase64 is working even if wrong file path is provided', + () async { + final file = File('fakePath'); + final fileString = await imageService.convertToBase64(file); + expect('', fileString); + }); + }); +} diff --git a/test/service_tests/multi_media_pick_service_test.dart b/test/service_tests/multi_media_pick_service_test.dart index d1f887d9f..1205bc509 100644 --- a/test/service_tests/multi_media_pick_service_test.dart +++ b/test/service_tests/multi_media_pick_service_test.dart @@ -2,50 +2,30 @@ // ignore_for_file: talawa_good_doc_comments import 'dart:async'; -import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:mockito/mockito.dart'; -import 'package:talawa/locator.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; +import 'package:talawa/utils/app_localization.dart'; import '../helpers/test_helpers.dart'; +import '../helpers/test_locator.dart'; void main() { + testSetupLocator(); setUp(() { registerServices(); }); + tearDown(() { + unregisterServices(); + }); + SizeConfig().test(); group('MultiMediaPickerService test', () { - test("test get fileStream", () async { - final model = MultiMediaPickerService(); - expect( - model.fileStream.toString(), - "Instance of '_AsBroadcastStream'", - ); - }); - test("crop image method", () async { - final mockImageCropper = imageCropper; - final model = MultiMediaPickerService(); - - const path = "test"; - final fakefile = File(path); - final croppedFile = CroppedFile("fakeCropped"); - - when( - mockImageCropper.cropImage( - sourcePath: "test", - aspectRatioPresets: [ - CropAspectRatioPreset.square, - CropAspectRatioPreset.original, - ], - uiSettings: anyNamed('uiSettings'), - ), - ).thenAnswer((realInvocation) async => croppedFile); - final file = await model.cropImage(imageFile: fakefile); - // verify(mockImageCropper.cropImage(sourcePath: fakefile.path)); - expect(file?.path, croppedFile.path); - }); test("test get photo from gallery method if camera option is false", () async { final mockImageCropper = locator(); @@ -99,12 +79,6 @@ void main() { final file = await model.getPhotoFromGallery(camera: false); expect(file?.path, null); }); - test("test no image provided for the image cropper", () async { - final model = MultiMediaPickerService(); - const path = 'test'; - final file = await model.cropImage(imageFile: File(path)); - expect(file?.path, null); - }); test("camera access denied", () async { final mockPicker = locator(); final model = MultiMediaPickerService(); @@ -127,35 +101,50 @@ void main() { "MultiMediaPickerService : Exception occurred while choosing photo from the gallery $error", ); }); - test("error in crop image", () async { - final mockImageCropper = locator(); - final model = MultiMediaPickerService(); - const path = "test"; - final fakefile = File(path); - final printed = []; - when( - mockImageCropper.cropImage( - sourcePath: "test", - aspectRatioPresets: [ - CropAspectRatioPreset.square, - CropAspectRatioPreset.original, - ], - uiSettings: anyNamed('uiSettings'), - ), - ).thenThrow(Exception()); - runZoned( - () async { - await model.cropImage(imageFile: fakefile); - }, - zoneSpecification: ZoneSpecification( - print: (self, parent, zone, line) { - printed.add(line); - }, - ), + + testWidgets('Test for permission_denied_dialog success action.', + (tester) async { + final service = MultiMediaPickerService(); + + final Widget app = MaterialApp( + navigatorKey: locator().navigatorKey, + navigatorObservers: [], + locale: const Locale('en'), + supportedLocales: [ + const Locale('en', 'US'), + const Locale('es', 'ES'), + const Locale('fr', 'FR'), + const Locale('hi', 'IN'), + const Locale('zh', 'CN'), + const Locale('de', 'DE'), + const Locale('ja', 'JP'), + const Locale('pt', 'PT'), + ], + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold(body: service.permissionDeniedDialog()), ); + + await tester.pumpWidget(app); + await tester.pumpAndSettle(); + + final settingsFinder = find.textContaining('SETTINGS'); + + expect(settingsFinder, findsOneWidget); + + await tester.tap(settingsFinder); + + verify(navigationService.pop()); + }); + + test("test get fileStream", () async { + final model = MultiMediaPickerService(); expect( - printed[0], - "MultiMediaPickerService : Exception occurred while cropping Image", + model.fileStream.toString(), + "Instance of '_AsBroadcastStream'", ); }); }); diff --git a/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart index 1c89d1dab..05dd72348 100644 --- a/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart +++ b/test/view_model_tests/after_auth_view_model_tests/add_post_view_model_test.dart @@ -36,6 +36,13 @@ void main() { userConfig.currentUser.firstName! + userConfig.currentUser.lastName!, ); }); + + test('Test for imageInBase64 getter', () async { + final model = AddPostViewModel(); + model.initialise(); + expect(model.imageInBase64, null); + }); + test("Check if getImageFromGallery() is working fine", () async { final model = AddPostViewModel(); model.initialise(); @@ -189,25 +196,5 @@ void main() { model.removeImage(); expect(model.imageFile, null); }); - test('convertToBase64 converts file to base64 string', () async { - final notifyListenerCallback = MockCallbackFunction(); - final model = AddPostViewModel()..addListener(notifyListenerCallback); - model.initialise(); - //using this asset as the test asset - final file = File('assets/images/Group 8948.png'); - final fileString = await model.convertToBase64(file); - expect(model.imageInBase64, fileString); - }); - - test( - 'Check if convertToBase64 is working even if wrong file path is provided', - () async { - final notifyListenerCallback = MockCallbackFunction(); - final model = AddPostViewModel()..addListener(notifyListenerCallback); - model.initialise(); - final file = File('fakePath'); - final fileString = await model.convertToBase64(file); - expect('', fileString); - }); }); } diff --git a/test/widget_tests/after_auth_screens/add_post_page_test.dart b/test/widget_tests/after_auth_screens/add_post_page_test.dart index f9f15117c..823921bc7 100644 --- a/test/widget_tests/after_auth_screens/add_post_page_test.dart +++ b/test/widget_tests/after_auth_screens/add_post_page_test.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:talawa/locator.dart'; import 'package:talawa/services/third_party_service/multi_media_pick_service.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/add_post_view_models/add_post_view_model.dart'; @@ -14,7 +15,6 @@ import 'package:talawa/view_model/main_screen_view_model.dart'; import 'package:talawa/views/after_auth_screens/add_post_page.dart'; import '../../helpers/test_helpers.dart'; -import '../../helpers/test_locator.dart'; final homeModel = locator(); bool removeImageCalled = false; @@ -86,11 +86,12 @@ Widget createAddPostScreen({ void main() { // SizeConfig().test(); - testSetupLocator(); + setupLocator(); // locator.registerSingleton(LikeButtonViewModel()); setUp(() { registerServices(); + getAndRegisterImageService(); }); group('createAddPostScreen Test', () { @@ -273,7 +274,7 @@ void main() { }); await tester.tap(finder); - await tester.pump(); + await tester.pumpAndSettle(); await tester.tap(cancelBtn); await tester.pump();