diff --git a/integration_test/robots/composer_robot.dart b/integration_test/robots/composer_robot.dart index 918b4da37..64a0ddaf6 100644 --- a/integration_test/robots/composer_robot.dart +++ b/integration_test/robots/composer_robot.dart @@ -93,4 +93,8 @@ class ComposerRobot extends CoreRobot { Future tapMarkAsImportantPopupItemOnMenu() async { await $(#mark_as_important_popup_item).tap(); } + + Future tapReadReceiptPopupItemOnMenu() async { + await $(#read_receipt_popup_item).tap(); + } } \ No newline at end of file diff --git a/integration_test/scenarios/send_email_with_read_receipt_enabled_scenario.dart b/integration_test/scenarios/send_email_with_read_receipt_enabled_scenario.dart new file mode 100644 index 000000000..419be6a34 --- /dev/null +++ b/integration_test/scenarios/send_email_with_read_receipt_enabled_scenario.dart @@ -0,0 +1,91 @@ +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:duration/duration.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:model/email/prefix_email_address.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +import '../base/base_test_scenario.dart'; +import '../robots/composer_robot.dart'; +import '../robots/email_robot.dart'; +import '../robots/thread_robot.dart'; + +class SendEmailWithReadReceiptEnabledScenario extends BaseTestScenario { + const SendEmailWithReadReceiptEnabledScenario(super.$); + + @override + Future runTestLogic() async { + const emailUser = String.fromEnvironment('BASIC_AUTH_EMAIL'); + const emailSubject = 'Email with read receipt enabled'; + + final threadRobot = ThreadRobot($); + final composerRobot = ComposerRobot($); + final emailRobot = EmailRobot($); + final imagePaths = ImagePaths(); + final appLocalizations = AppLocalizations(); + + await threadRobot.openComposer(); + await _expectComposerViewVisible(); + + await composerRobot.grantContactPermission(); + + await composerRobot.addRecipientIntoField( + prefixEmailAddress: PrefixEmailAddress.to, + email: emailUser, + ); + + await composerRobot.addSubject(emailSubject); + await composerRobot.addContent(emailSubject); + + await composerRobot.tapMoreOptionOnAppBar(); + await $.pump(const Duration(seconds: 2)); + await _expectMoreOptionPopupMenuVisible(); + + await composerRobot.tapReadReceiptPopupItemOnMenu(); + await $.pump(const Duration(seconds: 2)); + await _expectToastDisplayWithMessageReadReceiptEnabled(appLocalizations); + + await composerRobot.sendEmail(imagePaths); + await $.pumpAndSettle(duration: seconds(5)); + await _expectEmailWithReadReceiptVisible(emailSubject); + + await threadRobot.openEmailWithSubject(emailSubject); + await _expectReadReceiptRequestDialog(appLocalizations); + + await $.native.pressBack(); + await emailRobot.onTapBackButton(); + + await $.pumpAndSettle(duration: seconds(5)); + + // Try opening it again when the email is cached + await threadRobot.openEmailWithSubject(emailSubject); + await _expectReadReceiptRequestDialog(appLocalizations); + } + + Future _expectComposerViewVisible() => expectViewVisible($(ComposerView)); + + Future _expectMoreOptionPopupMenuVisible() async { + await expectViewVisible($(#read_receipt_popup_item)); + } + + Future _expectToastDisplayWithMessageReadReceiptEnabled( + AppLocalizations appLocalizations, + ) async { + await expectViewVisible( + $(find.text(appLocalizations.requestReadReceiptHasBeenEnabled)), + ); + } + + Future _expectReadReceiptRequestDialog( + AppLocalizations appLocalizations + ) async { + await expectViewVisible( + $(appLocalizations.titleReadReceiptRequestNotificationMessage) + ); + } + + Future _expectEmailWithReadReceiptVisible(String subject) async { + await expectViewVisible($(EmailTileBuilder).$(find.text(subject))); + } +} \ No newline at end of file diff --git a/integration_test/tests/compose/send_email_with_read_receipt_enabled_test.dart b/integration_test/tests/compose/send_email_with_read_receipt_enabled_test.dart new file mode 100644 index 000000000..37e7291c1 --- /dev/null +++ b/integration_test/tests/compose/send_email_with_read_receipt_enabled_test.dart @@ -0,0 +1,9 @@ +import '../../base/test_base.dart'; +import '../../scenarios/send_email_with_read_receipt_enabled_scenario.dart'; + +void main() { + TestBase().runPatrolTest( + description: 'Should see read receipt dialog when user toggle read receipt and open read receipt email', + scenarioBuilder: ($) => SendEmailWithReadReceiptEnabledScenario($), + ); +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 763ee282a..fbd703b71 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -517,6 +517,7 @@ class ComposerView extends GetWidget { PopupMenuItem( padding: EdgeInsets.zero, child: PopupItemWidget( + key: const Key('read_receipt_popup_item'), iconAction: controller.imagePaths.icReadReceipt, nameAction: AppLocalizations.of(context).requestReadReceipt, styleName: ComposerStyle.popupItemTextStyle, diff --git a/model/test/extensions/email_extension_test.dart b/model/test/extensions/email_extension_test.dart new file mode 100644 index 000000000..d89be348b --- /dev/null +++ b/model/test/extensions/email_extension_test.dart @@ -0,0 +1,117 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_header.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/email_property.dart'; +import 'package:model/extensions/email_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; + +void main() { + group('EmailExtension::hasReadReceipt:', () { + late Email email; + late Map mailboxMap; + + setUp(() { + mailboxMap = {}; + }); + + test('Should return true when the email contains a Disposition-Notification-To header, has no \$mdnsent keyword, and is not in the sent mailbox', () { + email = Email( + headers: { + EmailHeader( + EmailProperty.headerMdnKey, + 'user@example.com', + ), + }, + keywords: {}, + mailboxIds: { + MailboxId(Id('inbox')): true + } + ); + mailboxMap[MailboxId(Id('inbox'))] = PresentationMailbox( + MailboxId(Id('inbox')), + role: PresentationMailbox.roleInbox, + ); + + expect(email.hasReadReceipt(mailboxMap), isTrue); + }); + + test('Should return false when the email contains the \$mdnsent keyword', () { + email = Email( + headers: { + EmailHeader( + EmailProperty.headerMdnKey, + 'user@example.com', + ), + }, + keywords: { + KeyWordIdentifier.mdnSent: true, + }, + mailboxIds: { + MailboxId(Id('inbox')): true + } + ); + mailboxMap[MailboxId(Id('inbox'))] = PresentationMailbox( + MailboxId(Id('inbox')), + role: PresentationMailbox.roleInbox, + ); + + expect(email.hasReadReceipt(mailboxMap), isFalse); + }); + + test('Should return false when the email does not contain a Disposition-Notification-To header', () { + email = Email( + headers: {}, + keywords: {}, + mailboxIds: { + MailboxId(Id('inbox')): true + } + ); + mailboxMap[MailboxId(Id('inbox'))] = PresentationMailbox( + MailboxId(Id('inbox')), + role: PresentationMailbox.roleInbox, + ); + + expect(email.hasReadReceipt(mailboxMap), isFalse); + }); + + test('Should return false when the email is in the sent mailbox', () { + email = Email( + headers: { + EmailHeader( + EmailProperty.headerMdnKey, + 'user@example.com', + ), + }, + keywords: {}, + mailboxIds: { + MailboxId(Id('sent')): true + } + ); + mailboxMap[MailboxId(Id('sent'))] = PresentationMailbox( + MailboxId(Id('sent')), + role: PresentationMailbox.roleSent, + ); + + expect(email.hasReadReceipt(mailboxMap), isFalse); + }); + + test('Should return true when mailboxCurrent is null, the email contains a Disposition-Notification-To header, and has no \$mdnsent keyword', () { + email = Email( + headers: { + EmailHeader( + EmailProperty.headerMdnKey, + 'user@example.com', + ), + }, + keywords: {}, + mailboxIds: {}, + ); + mailboxMap = {}; + + expect(email.hasReadReceipt(mailboxMap), isTrue); + }); + }); +} \ No newline at end of file