import 'package:core/data/network/config/dynamic_url_interceptors.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/utils/application_manager.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.dart'; import 'package:html_editor_enhanced/html_editor.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_header.dart'; import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:model/email/attachment.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/email_property.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:rich_text_composer/rich_text_composer.dart'; import 'package:tmail_ui_user/features/base/before_reconnect_manager.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart'; import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/formatting_options_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/saved_email_draft.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/transform_html_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'; import 'package:tmail_ui_user/features/server_settings/domain/state/get_always_read_receipt_setting_state.dart'; import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_always_read_receipt_setting_interactor.dart'; import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart'; import 'package:tmail_ui_user/features/upload/domain/usecases/local_image_picker_interactor.dart'; import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.dart'; import 'package:tmail_ui_user/features/upload/presentation/model/upload_file_state.dart'; import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; import 'package:tmail_ui_user/main/utils/toast_manager.dart'; import 'package:uuid/uuid.dart'; import '../../../fixtures/account_fixtures.dart'; import '../../../fixtures/session_fixtures.dart'; import '../../../fixtures/widget_fixtures.dart'; import '../../../mocks/mock_web_view_platform.dart'; import 'composer_controller_test.mocks.dart'; mockControllerCallback() => InternalFinalCallback(callback: () {}); const fallbackGenerators = { #onStart: mockControllerCallback, #onDelete: mockControllerCallback, }; class MockRichTextWebController extends Mock implements RichTextWebController { @override Rx get formattingOptionsState => FormattingOptionsState.disabled.obs; @override bool get isFormattingOptionsEnabled => formattingOptionsState.value == FormattingOptionsState.enabled; @override HtmlEditorController get editorController => MockHtmlEditorController(); @override bool get codeViewEnabled => false; } class MockMailboxDashBoardController extends Mock implements MailboxDashBoardController { @override InternalFinalCallback get onStart => mockControllerCallback(); @override InternalFinalCallback get onDelete => mockControllerCallback(); @override Rxn get accountId => Rxn(AccountFixtures.aliceAccountId); @override Session? get sessionCurrent => SessionFixtures.aliceSession; @override Rxn get attachmentDraggableAppState => Rxn(DraggableAppState.inActive); @override bool get isAttachmentDraggableAppActive => attachmentDraggableAppState.value == DraggableAppState.active; @override Rxn get localFileDraggableAppState => Rxn(DraggableAppState.inActive); @override bool get isLocalFileDraggableAppActive => localFileDraggableAppState.value == DraggableAppState.active; @override Map get mapDefaultMailboxIdByRole => {PresentationMailbox.roleDrafts: MailboxId(Id('value'))}; @override String get baseDownloadUrl => ''; } @GenerateNiceMocks([ // Base controller mock specs MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), // Composer controller mock specs MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), MockSpec(), // Additional Getx dependencies mock specs MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(), MockSpec(fallbackGenerators: fallbackGenerators), // Additional misc dependencies mock specs MockSpec(), MockSpec(), ]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); // Declaration base controller late MockCachingManager mockCachingManager; late MockLanguageCacheManager mockLanguageCacheManager; late MockAuthorizationInterceptors mockAuthorizationInterceptors; late MockDynamicUrlInterceptors mockDynamicUrlInterceptors; late MockDeleteCredentialInteractor mockDeleteCredentialInteractor; late MockLogoutOidcInteractor mockLogoutOidcInteractor; late MockDeleteAuthorityOidcInteractor mockDeleteAuthorityOidcInteractor; late MockAppToast mockAppToast; late MockUuid mockUuid; late MockApplicationManager mockApplicationManager; late MockToastManager mockToastManager; // Declaration composer controller late ComposerController? composerController; late MockLocalFilePickerInteractor mockLocalFilePickerInteractor; late MockLocalImagePickerInteractor mockLocalImagePickerInteractor; late MockGetEmailContentInteractor mockGetEmailContentInteractor; late MockGetAllIdentitiesInteractor mockGetAllIdentitiesInteractor; late MockUploadController mockUploadController; late MockRemoveComposerCacheOnWebInteractor mockRemoveComposerCacheOnWebInteractor; late MockSaveComposerCacheOnWebInteractor mockSaveComposerCacheOnWebInteractor; late MockDownloadImageAsBase64Interactor mockDownloadImageAsBase64Interactor; late MockTransformHtmlEmailContentInteractor mockTransformHtmlEmailContentInteractor; late MockGetAlwaysReadReceiptSettingInteractor mockGetAlwaysReadReceiptSettingInteractor; late MockCreateNewAndSendEmailInteractor mockCreateNewAndSendEmailInteractor; late MockCreateNewAndSaveEmailToDraftsInteractor mockCreateNewAndSaveEmailToDraftsInteractor; // Declaration Getx dependencies final mockMailboxDashBoardController = MockMailboxDashBoardController(); final mockNetworkConnectionController = MockNetworkConnectionController(); final mockBeforeReconnectManager = MockBeforeReconnectManager(); final mockRichTextMobileTabletController = MockRichTextMobileTabletController(); final mockRichTextWebController = MockRichTextWebController(); // Declaration misc dependencies late MockHtmlEditorApi mockHtmlEditorApi; setUp(() { Get.testMode = true; // Mock base controller mockCachingManager = MockCachingManager(); mockLanguageCacheManager = MockLanguageCacheManager(); mockAuthorizationInterceptors = MockAuthorizationInterceptors(); mockDynamicUrlInterceptors = MockDynamicUrlInterceptors(); mockDeleteCredentialInteractor = MockDeleteCredentialInteractor(); mockLogoutOidcInteractor = MockLogoutOidcInteractor(); mockDeleteAuthorityOidcInteractor = MockDeleteAuthorityOidcInteractor(); mockAppToast = MockAppToast(); mockUuid = MockUuid(); mockApplicationManager = MockApplicationManager(); mockToastManager = MockToastManager(); Get.put(mockCachingManager); Get.put(mockLanguageCacheManager); Get.put(mockAuthorizationInterceptors); Get.put( mockAuthorizationInterceptors, tag: BindingTag.isolateTag, ); Get.put(mockDynamicUrlInterceptors); Get.put(mockDeleteCredentialInteractor); Get.put(mockLogoutOidcInteractor); Get.put(mockDeleteAuthorityOidcInteractor); Get.put(mockAppToast); Get.put(ImagePaths()); Get.put(ResponsiveUtils()); Get.put(mockUuid); Get.put(mockApplicationManager); Get.put(mockToastManager); // Mock Getx controllers Get.put(mockMailboxDashBoardController); Get.put(mockNetworkConnectionController); Get.put(mockBeforeReconnectManager); Get.put(mockRichTextMobileTabletController); // Mock composer controller mockLocalFilePickerInteractor = MockLocalFilePickerInteractor(); mockLocalImagePickerInteractor = MockLocalImagePickerInteractor(); mockGetEmailContentInteractor = MockGetEmailContentInteractor(); mockGetAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); mockUploadController = MockUploadController(); mockRemoveComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); mockSaveComposerCacheOnWebInteractor = MockSaveComposerCacheOnWebInteractor(); mockDownloadImageAsBase64Interactor = MockDownloadImageAsBase64Interactor(); mockTransformHtmlEmailContentInteractor = MockTransformHtmlEmailContentInteractor(); mockGetAlwaysReadReceiptSettingInteractor = MockGetAlwaysReadReceiptSettingInteractor(); mockCreateNewAndSendEmailInteractor = MockCreateNewAndSendEmailInteractor(); mockCreateNewAndSaveEmailToDraftsInteractor = MockCreateNewAndSaveEmailToDraftsInteractor(); composerController = ComposerController( mockLocalFilePickerInteractor, mockLocalImagePickerInteractor, mockGetEmailContentInteractor, mockGetAllIdentitiesInteractor, mockUploadController, mockRemoveComposerCacheOnWebInteractor, mockSaveComposerCacheOnWebInteractor, mockDownloadImageAsBase64Interactor, mockTransformHtmlEmailContentInteractor, mockGetAlwaysReadReceiptSettingInteractor, mockCreateNewAndSendEmailInteractor, mockCreateNewAndSaveEmailToDraftsInteractor ); mockHtmlEditorApi = MockHtmlEditorApi(); }); tearDown(() { Get.reset(); composerController = null; }); group('ComposerController test:', () { group('hash draft email test:', () { const emailContent = 'some email content'; const emailSubject = 'some email subject'; final toRecipient = EmailAddress('to', 'to@linagora.com'); final ccRecipient = EmailAddress('cc', 'cc@linagora.com'); final bccRecipient = EmailAddress('bcc', 'bcc@linagora.com'); final identity = Identity(); final attachment = Attachment(); const alwaysReadReceiptEnabled = true; group('email action type is EmailActionType.compose:', () { setUp(() { composerController?.composerArguments.value = ComposerArguments( emailActionType: EmailActionType.compose); }); test( 'should update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetAlwaysReadReceiptSettingSuccess', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; composerController?.identitySelected.value = identity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final state = GetAlwaysReadReceiptSettingSuccess( alwaysReadReceiptEnabled: alwaysReadReceiptEnabled); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: alwaysReadReceiptEnabled ); // act composerController?.handleSuccessViewState(state); await untilCalled(mockHtmlEditorApi.getText()); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); }); test( 'should update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetAlwaysReadReceiptSettingFailure', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; composerController?.identitySelected.value = identity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final state = GetAlwaysReadReceiptSettingFailure(Exception()); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.handleFailureViewState(state); await untilCalled(mockHtmlEditorApi.getText()); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is not empty ' 'and selectedIdentity is available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; final selectedIdentity = Identity(id: IdentityId(Id('alice'))); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( selectedIdentityId: selectedIdentity.id, identities: [selectedIdentity])); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is not empty ' 'and selectedIdentity is not available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments(identities: [identity])); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is empty ' 'and selectedIdentity is available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final selectedIdentity = Identity( id: IdentityId(Id('alice')), mayDelete: true); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments(selectedIdentityId: selectedIdentity.id)); when(mockGetAllIdentitiesInteractor.execute(any, any)).thenAnswer( (_) => Stream.value( Right(GetAllIdentitiesSuccess([selectedIdentity], null)))); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is empty ' 'and selectedIdentity is not available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final identity = Identity( id: IdentityId(Id('alice')), mayDelete: true); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments()); when(mockGetAllIdentitiesInteractor.execute(any, any)).thenAnswer( (_) => Stream.value( Right(GetAllIdentitiesSuccess([identity], null)))); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); testWidgets( 'should update _savedEmailDraftHash ' 'when user click save draft button ' 'and SaveEmailAsDraftsSuccess is returned', (tester) async { await tester.runAsync(() async { // arrange PlatformInfo.isTestingForWeb = true; InAppWebViewPlatform.instance = MockWebViewPlatform(); when(mockUploadController.uploadInlineViewState).thenReturn( Rx(Right(UIState.idle))); when(mockUploadController.listUploadAttachments).thenReturn( RxList()); Get.put(composerController!); composerController?.richTextWebController = mockRichTextWebController; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final selectedIdentity = Identity(id: IdentityId(Id('alice'))); composerController?.identitySelected.value = selectedIdentity; composerController?.composerArguments.value = ComposerArguments(); when( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))) .thenAnswer((_) => Stream.value( Right(SaveEmailAsDraftsSuccess(EmailId(Id('123')))))); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); await tester.pumpWidget(WidgetFixtures.makeTestableWidget( child: const Stack(children: [ComposerView()]))); await tester.pump(); // act final saveAsDraftButton = find.ancestor( of: find.byType(InkWell), matching: find.byWidgetPredicate( (widget) => widget is TMailButtonWidget && widget.icon == ImagePaths().icSaveToDraft)); await tester.tap(saveAsDraftButton); await tester.pump(); await untilCalled( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); }); testWidgets( 'should update _savedEmailDraftHash ' 'when user click save draft button ' 'and UpdateEmailDraftsSuccess is returned', (tester) async { await tester.runAsync(() async { // arrange PlatformInfo.isTestingForWeb = true; InAppWebViewPlatform.instance = MockWebViewPlatform(); when(mockUploadController.uploadInlineViewState).thenReturn( Rx(Right(UIState.idle))); when(mockUploadController.listUploadAttachments).thenReturn( RxList()); Get.put(composerController!); composerController?.richTextWebController = mockRichTextWebController; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final selectedIdentity = Identity(id: IdentityId(Id('alice'))); composerController?.identitySelected.value = selectedIdentity; composerController?.composerArguments.value = ComposerArguments(); when( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))) .thenAnswer((_) => Stream.value( Right(UpdateEmailDraftsSuccess(EmailId(Id('123')))))); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); await tester.pumpWidget(WidgetFixtures.makeTestableWidget( child: const Stack(children: [ComposerView()]))); await tester.pump(); // act final saveAsDraftButton = find.ancestor( of: find.byType(InkWell), matching: find.byWidgetPredicate( (widget) => widget is TMailButtonWidget && widget.icon == ImagePaths().icSaveToDraft)); await tester.tap(saveAsDraftButton); await tester.pump(); await untilCalled( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); }); }); group('email action type is EmailActionType.editDraft:', () { setUp(() { composerController?.composerArguments.value = ComposerArguments( emailActionType: EmailActionType.editDraft); }); test( 'should update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetAlwaysReadReceiptSettingSuccess', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; composerController?.identitySelected.value = identity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); const alwaysReadReceiptEnabled = true; final state = GetAlwaysReadReceiptSettingSuccess( alwaysReadReceiptEnabled: alwaysReadReceiptEnabled); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: alwaysReadReceiptEnabled ); // act composerController?.handleSuccessViewState(state); await untilCalled(mockHtmlEditorApi.getText()); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); }); test( 'should update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetAlwaysReadReceiptSettingFailure', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; composerController?.identitySelected.value = identity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final state = GetAlwaysReadReceiptSettingFailure(Exception()); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.handleFailureViewState(state); await untilCalled(mockHtmlEditorApi.getText()); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is not empty ' 'and selectedIdentity is available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); final selectedIdentity = Identity(id: IdentityId(Id('alice'))); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.editDraft, emailContents: emailContent, presentationEmail: PresentationEmail( id: EmailId(Id('some-email-id')), subject: emailSubject, to: {toRecipient}, cc: {ccRecipient}, bcc: {bccRecipient}, mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)), selectedIdentityId: selectedIdentity.id, identities: [selectedIdentity])); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is not empty ' 'and selectedIdentity is not available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); final identity = Identity(id: IdentityId(Id('alice'))); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.editDraft, emailContents: emailContent, presentationEmail: PresentationEmail( id: EmailId(Id('some-email-id')), subject: emailSubject, to: {toRecipient}, cc: {ccRecipient}, bcc: {bccRecipient}, mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)), identities: [identity])); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is empty ' 'and selectedIdentity is available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); final selectedIdentity = Identity( id: IdentityId(Id('alice')), mayDelete: true); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.editDraft, emailContents: emailContent, presentationEmail: PresentationEmail( id: EmailId(Id('some-email-id')), subject: emailSubject, to: {toRecipient}, cc: {ccRecipient}, bcc: {bccRecipient}, mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)), selectedIdentityId: selectedIdentity.id)); when(mockGetAllIdentitiesInteractor.execute(any, any)).thenAnswer( (_) => Stream.value( Right(GetAllIdentitiesSuccess([selectedIdentity], null)))); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is empty ' 'and selectedIdentity is not available', () async { // arrange PlatformInfo.isTestingForWeb = true; composerController?.onChangeTextEditorWeb(emailContent); final identity = Identity( id: IdentityId(Id('alice')), mayDelete: true); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.editDraft, emailContents: emailContent, presentationEmail: PresentationEmail( id: EmailId(Id('some-email-id')), subject: emailSubject, to: {toRecipient}, cc: {ccRecipient}, bcc: {bccRecipient}, mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)),)); when(mockGetAllIdentitiesInteractor.execute(any, any)).thenAnswer( (_) => Stream.value( Right(GetAllIdentitiesSuccess([identity], null)))); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: false ); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); testWidgets( 'should update _savedEmailDraftHash ' 'when user click save draft button ' 'and SaveEmailAsDraftsSuccess is returned', (tester) async { await tester.runAsync(() async { // arrange PlatformInfo.isTestingForWeb = true; InAppWebViewPlatform.instance = MockWebViewPlatform(); when(mockUploadController.uploadInlineViewState).thenReturn( Rx(Right(UIState.idle))); when(mockUploadController.listUploadAttachments).thenReturn( RxList()); Get.put(composerController!); composerController?.richTextWebController = mockRichTextWebController; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; final selectedIdentity = Identity(id: IdentityId(Id('alice'))); composerController?.identitySelected.value = selectedIdentity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); composerController?.composerArguments.value = ComposerArguments( emailActionType: EmailActionType.editDraft); when( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))) .thenAnswer((_) => Stream.value( Right(SaveEmailAsDraftsSuccess(EmailId(Id('123')))))); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); await tester.pumpWidget(WidgetFixtures.makeTestableWidget( child: const Stack(children: [ComposerView()]))); await tester.pump(); // act final saveAsDraftButton = find.ancestor( of: find.byType(InkWell), matching: find.byWidgetPredicate( (widget) => widget is TMailButtonWidget && widget.icon == ImagePaths().icSaveToDraft)); await tester.tap(saveAsDraftButton); await tester.pump(); await untilCalled( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); }); testWidgets( 'should update _savedEmailDraftHash ' 'when user click save draft button ' 'and UpdateEmailDraftsSuccess is returned', (tester) async { await tester.runAsync(() async { // arrange PlatformInfo.isTestingForWeb = true; InAppWebViewPlatform.instance = MockWebViewPlatform(); when(mockUploadController.uploadInlineViewState).thenReturn( Rx(Right(UIState.idle))); when(mockUploadController.listUploadAttachments).thenReturn( RxList()); Get.put(composerController!); composerController?.richTextWebController = mockRichTextWebController; composerController?.onChangeTextEditorWeb(emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; final selectedIdentity = Identity(id: IdentityId(Id('alice'))); composerController?.identitySelected.value = selectedIdentity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); composerController?.composerArguments.value = ComposerArguments( emailActionType: EmailActionType.editDraft); when( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))) .thenAnswer((_) => Stream.value( Right(UpdateEmailDraftsSuccess(EmailId(Id('123')))))); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: selectedIdentity, attachments: [attachment], hasReadReceipt: false ); await tester.pumpWidget(WidgetFixtures.makeTestableWidget( child: const Stack(children: [ComposerView()]))); await tester.pump(); // act final saveAsDraftButton = find.ancestor( of: find.byType(InkWell), matching: find.byWidgetPredicate( (widget) => widget is TMailButtonWidget && widget.icon == ImagePaths().icSaveToDraft)); await tester.tap(saveAsDraftButton); await tester.pump(); await untilCalled( mockCreateNewAndSaveEmailToDraftsInteractor.execute( createEmailRequest: anyNamed('createEmailRequest'), cancelToken: anyNamed('cancelToken'))); // assert expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); }); test( 'should update _savedEmailDraftHash with the same value' 'and call _updateSavedEmailDraftHash twice ' 'when there is a new view state ' 'and the state is GetEmailContentSuccess', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContent); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; composerController?.hasRequestReadReceipt.value = alwaysReadReceiptEnabled; const idenityId = 'some-identity-id'; final identity = Identity(id: IdentityId(Id(idenityId))); composerController?.identitySelected.value = identity; composerController?.listFromIdentities.add(identity); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); final state = GetEmailContentSuccess( htmlEmailContent: '', emailCurrent: Email( identityHeader: {IndividualHeaderIdentifier.identityHeader: idenityId}, headers: {EmailHeader(EmailProperty.headerMdnKey, 'value')})); final savedEmailDraft = SavedEmailDraft( content: emailContent, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: alwaysReadReceiptEnabled ); // act composerController?.handleSuccessViewState(state); await untilCalled(mockHtmlEditorApi.getText()); // assert verify(mockHtmlEditorApi.getText()).called(2); expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); }); test( 'should update _savedEmailDraftHash ' 'when restoring signature button finished', () async { // arrange PlatformInfo.isTestingForWeb = true; const emailContentWithSignature = '
'; const emailContentWithSignatureButton = '
'; when(mockHtmlEditorApi.getText()).thenAnswer((_) async => emailContentWithSignatureButton); composerController?.subjectEmail.value = emailSubject; composerController?.listToEmailAddress = [toRecipient]; composerController?.listCcEmailAddress = [ccRecipient]; composerController?.listBccEmailAddress = [bccRecipient]; composerController?.identitySelected.value = identity; when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); composerController?.hasRequestReadReceipt.value = alwaysReadReceiptEnabled; final savedEmailDraft = SavedEmailDraft( content: emailContentWithSignatureButton, subject: emailSubject, toRecipients: {toRecipient}, ccRecipients: {ccRecipient}, bccRecipients: {bccRecipient}, identity: identity, attachments: [attachment], hasReadReceipt: alwaysReadReceiptEnabled ); // act await composerController?.restoreCollapsibleButton(emailContentWithSignature); expect(composerController?.restoringSignatureButton, true); composerController?.onChangeTextEditorWeb(emailContentWithSignatureButton); await Future.delayed(Duration.zero); // assert expect(composerController?.restoringSignatureButton, false); expect(composerController?.savedEmailDraftHash, savedEmailDraft.hashCode); // tear down PlatformInfo.isTestingForWeb = false; }); }); group('email action type is neither EmailActionType.compose nor EmailActionType.editDraft:', () { setUp(() { composerController?.composerArguments.value = ComposerArguments( emailActionType: EmailActionType.reply); }); test( 'should not update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetAlwaysReadReceiptSettingSuccess', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); final state = GetAlwaysReadReceiptSettingSuccess( alwaysReadReceiptEnabled: true); // act composerController?.handleSuccessViewState(state); // assert verifyNever(mockHtmlEditorApi.getText()); expect(composerController?.savedEmailDraftHash, isNull); }); test( 'should not update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetAlwaysReadReceiptSettingFailure', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); final state = GetAlwaysReadReceiptSettingFailure(Exception()); // act composerController?.handleFailureViewState(state); // assert verifyNever(mockHtmlEditorApi.getText()); expect(composerController?.savedEmailDraftHash, isNull); }); test( 'should not update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is not empty ' 'and selectedIdentity is available', () async { // arrange PlatformInfo.isTestingForWeb = true; final selectedIdentity = Identity(id: IdentityId(Id('alice'))); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.reply, emailContents: emailContent, presentationEmail: PresentationEmail( mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)), selectedIdentityId: selectedIdentity.id, identities: [selectedIdentity])); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, isNull); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should not update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is not empty ' 'and selectedIdentity is not available', () async { // arrange PlatformInfo.isTestingForWeb = true; final identity = Identity(id: IdentityId(Id('alice'))); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.reply, emailContents: emailContent, presentationEmail: PresentationEmail( mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)), identities: [identity])); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, isNull); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should not update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is empty ' 'and selectedIdentity is available', () async { // arrange PlatformInfo.isTestingForWeb = true; final selectedIdentity = Identity( id: IdentityId(Id('alice')), mayDelete: true); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.reply, emailContents: emailContent, presentationEmail: PresentationEmail( mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)), selectedIdentityId: selectedIdentity.id)); when(mockGetAllIdentitiesInteractor.execute(any, any)).thenAnswer( (_) => Stream.value( Right(GetAllIdentitiesSuccess([selectedIdentity], null)))); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, isNull); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should not update _savedEmailDraftHash ' 'when _initIdentities is called ' 'and listFromIdentities is empty ' 'and selectedIdentity is not available', () async { // arrange PlatformInfo.isTestingForWeb = true; final identity = Identity( id: IdentityId(Id('alice')), mayDelete: true); when(mockMailboxDashBoardController.composerArguments).thenReturn( ComposerArguments( emailActionType: EmailActionType.reply, emailContents: emailContent, presentationEmail: PresentationEmail( mailboxContain: PresentationMailbox( MailboxId(Id('some-mailbox-id')), role: PresentationMailbox.roleJunk)),)); when(mockGetAllIdentitiesInteractor.execute(any, any)).thenAnswer( (_) => Stream.value( Right(GetAllIdentitiesSuccess([identity], null)))); when(mockUploadController.attachmentsUploaded).thenReturn([attachment]); // act composerController?.onReady(); await Future.delayed(Duration.zero); // assert expect(composerController?.savedEmailDraftHash, isNull); // tear down PlatformInfo.isTestingForWeb = false; }); test( 'should not update _savedEmailDraftHash ' 'when there is a new view state ' 'and the state is GetEmailContentSuccess', () async { // arrange composerController?.richTextMobileTabletController = mockRichTextMobileTabletController; when(mockRichTextMobileTabletController.htmlEditorApi).thenReturn( mockHtmlEditorApi); const idenityId = 'some-identity-id'; final identity = Identity(id: IdentityId(Id(idenityId))); composerController?.identitySelected.value = identity; composerController?.listFromIdentities.add(identity); final state = GetEmailContentSuccess( htmlEmailContent: emailContent, emailCurrent: Email( identityHeader: {IndividualHeaderIdentifier.identityHeader: idenityId})); // act composerController?.handleSuccessViewState(state); // assert verifyNever(mockHtmlEditorApi.getText()); expect(composerController?.savedEmailDraftHash, isNull); }); }); }); }); }