diff --git a/integration_test/mixin/scenario_utils_mixin.dart b/integration_test/mixin/scenario_utils_mixin.dart index 10b460c99..0efeb1faf 100644 --- a/integration_test/mixin/scenario_utils_mixin.dart +++ b/integration_test/mixin/scenario_utils_mixin.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io' hide HttpClient; import 'package:collection/collection.dart'; @@ -33,6 +34,7 @@ import 'package:uuid/uuid.dart'; import '../models/provisioning_email.dart'; import '../models/provisioning_identity.dart'; +import '../resources/test_images.dart'; mixin ScenarioUtilsMixin { Future provisionEmail( @@ -44,7 +46,8 @@ mixin ScenarioUtilsMixin { ComposerBindings().dependencies(); final mailboxDashBoardController = Get.find(); - final createNewAndSendEmailInteractor = Get.find(); + final createNewAndSendEmailInteractor = + Get.find(); final threadController = Get.find(); final identity = await getIdentity(); @@ -53,27 +56,33 @@ mixin ScenarioUtilsMixin { // Provision emails await Future.wait(provisioningEmails.map((provisioningEmail) async { - final attachments = await uploadAttachments(provisioningEmail.attachmentPaths); + final attachments = + await uploadAttachments(provisioningEmail.attachmentPaths); // Create and send email - return await createNewAndSendEmailInteractor.execute( - createEmailRequest: CreateEmailRequest( - session: mailboxDashBoardController.sessionCurrent!, - accountId: mailboxDashBoardController.accountId.value!, - emailActionType: EmailActionType.compose, - ownEmailAddress: mailboxDashBoardController.ownEmailAddress.value, - subject: provisioningEmail.subject, - emailContent: provisioningEmail.content, - toRecipients: {EmailAddress(null, provisioningEmail.toEmail)}, - outboxMailboxId: mailboxDashBoardController.outboxMailbox?.mailboxId, - sentMailboxId: folderLocationRole != null - ? mailboxDashBoardController.mapDefaultMailboxIdByRole[folderLocationRole] - : mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent], - identity: identity, - attachments: attachments, - hasRequestReadReceipt: requestReadReceipt, - ), - ).last; + return await createNewAndSendEmailInteractor + .execute( + createEmailRequest: CreateEmailRequest( + session: mailboxDashBoardController.sessionCurrent!, + accountId: mailboxDashBoardController.accountId.value!, + emailActionType: EmailActionType.compose, + ownEmailAddress: mailboxDashBoardController.ownEmailAddress.value, + subject: provisioningEmail.subject, + emailContent: provisioningEmail.content, + toRecipients: {EmailAddress(null, provisioningEmail.toEmail)}, + outboxMailboxId: + mailboxDashBoardController.outboxMailbox?.mailboxId, + sentMailboxId: folderLocationRole != null + ? mailboxDashBoardController + .mapDefaultMailboxIdByRole[folderLocationRole] + : mailboxDashBoardController + .mapDefaultMailboxIdByRole[PresentationMailbox.roleSent], + identity: identity, + attachments: attachments, + hasRequestReadReceipt: requestReadReceipt, + ), + ) + .last; })); // Refresh view after provisioning emails @@ -90,12 +99,10 @@ mixin ScenarioUtilsMixin { bool? isStar, }) async { final mailboxDashBoardController = Get.find(); - final emails = mailboxDashBoardController - .emailsInCurrentMailbox - .where((presentationEmail) => subjects.contains( - presentationEmail.subject - )) - .toList(); + final emails = mailboxDashBoardController.emailsInCurrentMailbox + .where( + (presentationEmail) => subjects.contains(presentationEmail.subject)) + .toList(); final session = mailboxDashBoardController.sessionCurrent; final accountId = mailboxDashBoardController.accountId.value; if (session == null || accountId == null) return; @@ -110,26 +117,22 @@ mixin ScenarioUtilsMixin { if (isRead != null) { final markEmailAsReadMethod = SetEmailMethod(accountId) ..addUpdates(emails.listEmailIds.generateMapUpdateObjectMarkAsRead( - isRead ? ReadActions.markAsRead : ReadActions.markAsUnread - )); + isRead ? ReadActions.markAsRead : ReadActions.markAsUnread)); requestBuilder.invocation(markEmailAsReadMethod); - capabilities.addAll(markEmailAsReadMethod - .requiredCapabilities - .toCapabilitiesSupportTeamMailboxes(session, accountId)); + capabilities.addAll(markEmailAsReadMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId)); } - + // Mark as star/unstar if (isStar != null) { final markEmailAsStarMethod = SetEmailMethod(accountId) ..addUpdates(emails.listEmailIds.generateMapUpdateObjectMarkAsStar( - isStar ? MarkStarAction.markStar : MarkStarAction.unMarkStar - )); + isStar ? MarkStarAction.markStar : MarkStarAction.unMarkStar)); requestBuilder.invocation(markEmailAsStarMethod); - capabilities.addAll(markEmailAsStarMethod - .requiredCapabilities - .toCapabilitiesSupportTeamMailboxes(session, accountId)); + capabilities.addAll(markEmailAsStarMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId)); } - + await (requestBuilder..usings(capabilities)).build().execute(); } @@ -139,9 +142,15 @@ mixin ScenarioUtilsMixin { return file.writeAsString(content); } + Future preparingPngWithName(String pngName) async { + final bytes = base64Decode(TestImages.base64); + final directory = await getTemporaryDirectory(); + final file = File('${directory.path}/$pngName.png'); + return file.writeAsBytes(bytes); + } + Future> uploadAttachments( - List attachmentPaths - ) async { + List attachmentPaths) async { final mailboxDashBoardController = Get.find(); final attachments = []; @@ -156,20 +165,22 @@ mixin ScenarioUtilsMixin { ); try { - final uploadUri = mailboxDashBoardController.sessionCurrent!.getUploadUri( + final uploadUri = + mailboxDashBoardController.sessionCurrent!.getUploadUri( mailboxDashBoardController.accountId.value!, jmapUrl: mailboxDashBoardController.dynamicUrlInterceptors.jmapUrl, ); - final uploadAttachmentInteractor = Get.find(); - final uploadAttachmentState = await uploadAttachmentInteractor - .execute(fileInfo, uploadUri) - .last; + final uploadAttachmentInteractor = + Get.find(); + final uploadAttachmentState = + await uploadAttachmentInteractor.execute(fileInfo, uploadUri).last; final attachment = await uploadAttachmentState.fold( (failure) => null, (success) async { if (success is UploadAttachmentSuccess) { - final uploadAttachment = await success.uploadAttachment.progressState.last; + final uploadAttachment = + await success.uploadAttachment.progressState.last; return uploadAttachment.fold( (failure) => null, (success) { @@ -196,10 +207,12 @@ mixin ScenarioUtilsMixin { Future getIdentity() async { final mailboxDashBoardController = Get.find(); final getAllIdentitiesInteractor = Get.find(); - final getAllIdentities = await getAllIdentitiesInteractor.execute( - mailboxDashBoardController.sessionCurrent!, - mailboxDashBoardController.accountId.value!, - ).last; + final getAllIdentities = await getAllIdentitiesInteractor + .execute( + mailboxDashBoardController.sessionCurrent!, + mailboxDashBoardController.accountId.value!, + ) + .last; return getAllIdentities.fold( (failure) => null, (success) { @@ -211,45 +224,50 @@ mixin ScenarioUtilsMixin { ); } - bool isMatchingEmailList(List emailList, Set allowedEmails) { + bool isMatchingEmailList( + List emailList, Set allowedEmails) { if (emailList.length != allowedEmails.length) { return false; } - final Set emails = emailList.map((e) => e.email).whereType().toSet(); + final Set emails = + emailList.map((e) => e.email).whereType().toSet(); - return emails.length == allowedEmails.length && emails.containsAll(allowedEmails); + return emails.length == allowedEmails.length && + emails.containsAll(allowedEmails); } - Future provisionIdentities(List provisioningIdentities) async { + Future provisionIdentities( + List provisioningIdentities) async { IdentityInteractorsBindings().dependencies(); final mailboxDashBoardController = Get.find(); final createNewIdentityInteractor = Get.find(); - final createNewDefaultIdentityInteractor = Get.find(); + final createNewDefaultIdentityInteractor = + Get.find(); const uuid = Uuid(); final session = mailboxDashBoardController.sessionCurrent; final accountId = mailboxDashBoardController.accountId.value; final listIdentityRequest = provisioningIdentities - .map((provisioningIdentity) => CreateNewIdentityRequest( - Id(uuid.v1()), - provisioningIdentity.identity, - isDefaultIdentity: provisioningIdentity.isDefault, - )); + .map((provisioningIdentity) => CreateNewIdentityRequest( + Id(uuid.v1()), + provisioningIdentity.identity, + isDefaultIdentity: provisioningIdentity.isDefault, + )); for (var identityRequest in listIdentityRequest) { if (identityRequest.isDefaultIdentity) { await createNewDefaultIdentityInteractor - .execute(session!, accountId!, identityRequest) - .last; + .execute(session!, accountId!, identityRequest) + .last; } else { await createNewIdentityInteractor - .execute(session!, accountId!, identityRequest) - .last; + .execute(session!, accountId!, identityRequest) + .last; } } IdentityInteractorsBindings().dispose(); } -} \ No newline at end of file +} diff --git a/integration_test/resources/test_images.dart b/integration_test/resources/test_images.dart new file mode 100644 index 000000000..a503a8392 --- /dev/null +++ b/integration_test/resources/test_images.dart @@ -0,0 +1,5 @@ +class TestImages { + const TestImages._(); + + static const base64 = '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'; +} diff --git a/integration_test/robots/composer_robot.dart b/integration_test/robots/composer_robot.dart index 930e4535e..c75f432cf 100644 --- a/integration_test/robots/composer_robot.dart +++ b/integration_test/robots/composer_robot.dart @@ -1,8 +1,13 @@ +import 'dart:io'; + import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; import 'package:model/email/prefix_email_address.dart'; +import 'package:model/extensions/session_extension.dart'; +import 'package:model/upload/file_info.dart'; import 'package:rich_text_composer/rich_text_composer.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; @@ -12,6 +17,7 @@ import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_composer_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/recipient_suggestion_item_widget.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/subject_composer_widget.dart'; +import 'package:tmail_ui_user/features/upload/domain/state/local_image_picker_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import '../base/core_robot.dart'; @@ -24,26 +30,29 @@ class ComposerRobot extends CoreRobot { required PrefixEmailAddress prefixEmailAddress, required String email, }) async { - final finder = $(RecipientComposerWidget) - .which((widget) => widget.prefix == prefixEmailAddress); + final finder = $(RecipientComposerWidget).which( + (widget) => widget.prefix == prefixEmailAddress); final isTextFieldFocused = finder - .which((view) => view.focusNode?.hasFocus ?? false) - .exists; + .which( + (view) => view.focusNode?.hasFocus ?? false) + .exists; if (!isTextFieldFocused) { await finder.tap(); } await finder.enterTextWithoutTapAction(email); await $(RecipientSuggestionItemWidget) - .which((widget) => widget.emailAddress.email?.contains(email) ?? false) - .tap(); + .which( + (widget) => widget.emailAddress.email?.contains(email) ?? false) + .tap(); } Future addSubject(String subject) async { final finder = $(SubjectComposerWidget); final isTextFieldFocused = finder - .which((view) => view.focusNode?.hasFocus ?? false) - .exists; + .which( + (view) => view.focusNode?.hasFocus ?? false) + .exists; if (!isTextFieldFocused) { await finder.tap(); } @@ -53,38 +62,45 @@ class ComposerRobot extends CoreRobot { Future addContent(String content) async { ComposerController? composerController; await $(ComposerView) - .which((widget) { - composerController = widget.controller; - return true; - }) - .$(MobileEditorView).$(HtmlEditor).$(InAppWebView).tap(); + .which((widget) { + composerController = widget.controller; + return true; + }) + .$(MobileEditorView) + .$(HtmlEditor) + .$(InAppWebView) + .tap(); await composerController?.htmlEditorApi?.requestFocusLastChild(); - await composerController!.htmlEditorApi!.insertHtml('$content

'); + await composerController!.htmlEditorApi!.insertHtml('$content

'); } Future sendEmail(ImagePaths imagePaths) async { await $(AppBarComposerWidget) - .$(TMailButtonWidget) - .which((widget) => widget.icon == imagePaths.icSendMobile) - .tap(); + .$(TMailButtonWidget) + .which( + (widget) => widget.icon == imagePaths.icSendMobile) + .tap(); } Future grantContactPermission() async { - if (await $.native.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) { + if (await $.native + .isPermissionDialogVisible(timeout: const Duration(seconds: 5))) { await $.native.grantPermissionWhenInUse(); } } Future tapCloseComposer(ImagePaths imagePaths) async { await $(AppBarComposerWidget) - .$(TMailButtonWidget) - .which((widget) => widget.icon == imagePaths.icCancel) - .tap(); + .$(TMailButtonWidget) + .which( + (widget) => widget.icon == imagePaths.icCancel) + .tap(); } - Future tapSaveButtonOnSaveDraftConfirmDialog(AppLocalizations appLocalizations) async { + Future tapSaveButtonOnSaveDraftConfirmDialog( + AppLocalizations appLocalizations) async { await $(find.text(appLocalizations.save)).tap(); } @@ -114,9 +130,37 @@ class ComposerRobot extends CoreRobot { Future saveAsTemplate() async { await $(AppBarComposerWidget) - .$(TMailButtonWidget) - .which((widget) => widget.icon == ImagePaths().icMore) - .tap(); + .$(TMailButtonWidget) + .which( + (widget) => widget.icon == ImagePaths().icMore) + .tap(); await $(AppLocalizations().saveAsTemplate).tap(); } -} \ No newline at end of file + + Future addAttachment(File file) async { + final controller = Get.find(); + controller.uploadController.justUploadAttachmentsAction( + uploadFiles: [ + FileInfo( + fileName: file.path.split('/').last, + fileSize: await file.length(), + filePath: file.path, + ) + ], + uploadUri: + controller.mailboxDashBoardController.sessionCurrent!.getUploadUri( + controller.mailboxDashBoardController.accountId.value!, + jmapUrl: controller.dynamicUrlInterceptors.jmapUrl, + ), + ); + } + + Future addInline(File file) async { + final controller = Get.find(); + controller.handleSuccessViewState(LocalImagePickerSuccess(FileInfo( + filePath: file.path, + fileSize: await file.length(), + fileName: file.path.split('/').last, + ))); + } +} diff --git a/integration_test/scenarios/composer/upload_attachment_and_inline_image_scenario.dart b/integration_test/scenarios/composer/upload_attachment_and_inline_image_scenario.dart new file mode 100644 index 000000000..cd21cf583 --- /dev/null +++ b/integration_test/scenarios/composer/upload_attachment_and_inline_image_scenario.dart @@ -0,0 +1,71 @@ + +import 'package:flutter_test/flutter_test.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_item_composer_widget.dart'; + +import '../../base/base_test_scenario.dart'; +import '../../resources/test_images.dart'; +import '../../robots/composer_robot.dart'; +import '../../robots/thread_robot.dart'; + +class ComposerUploadAttachmentAndInlineImageScenario extends BaseTestScenario { + const ComposerUploadAttachmentAndInlineImageScenario(super.$); + + @override + Future runTestLogic() async { + const pngName = 'composer-png'; + final threadRobot = ThreadRobot($); + final composerRobot = ComposerRobot($); + + await threadRobot.openComposer(); + await _expectComposerViewVisible(); + + await composerRobot.grantContactPermission(); + + final png = await preparingPngWithName(pngName); + + await composerRobot.addAttachment(png); + await composerRobot.addInline(png); + await $.pumpAndSettle(); + + _expectAttachment(pngName); + await _expectInline(); + + await composerRobot.addAttachment(png); + await composerRobot.addAttachment(png); + await $.pumpAndSettle(); + + _expectMultipleAttachments(3); + } + + Future _expectInline() async { + final currentHtmlContent = await Get + .find() + .getContentInEditor(); + expect( + currentHtmlContent.contains(TestImages.base64), + true, + ); + } + + void _expectAttachment(String pngName) { + return expect( + $(AttachmentItemComposerWidget) + .which( + (widget) => widget.fileName.contains(pngName) + ), + findsOneWidget, + ); + } + + void _expectMultipleAttachments(int numberOfAttachments) { + return expect( + $(AttachmentItemComposerWidget), + findsNWidgets(numberOfAttachments), + ); + } + + Future _expectComposerViewVisible() => expectViewVisible($(ComposerView)); +} diff --git a/integration_test/tests/compose/composer_upload_attachment_and_inline_image_test.dart b/integration_test/tests/compose/composer_upload_attachment_and_inline_image_test.dart new file mode 100644 index 000000000..853b2dc89 --- /dev/null +++ b/integration_test/tests/compose/composer_upload_attachment_and_inline_image_test.dart @@ -0,0 +1,10 @@ +import '../../base/test_base.dart'; +import '../../scenarios/composer/upload_attachment_and_inline_image_scenario.dart'; + +void main() { + TestBase().runPatrolTest( + description: 'Should see attachment and inline image ' + 'when upload success in composer', + scenarioBuilder: ($) => ComposerUploadAttachmentAndInlineImageScenario($), + ); +}