From 77b756cbb61917e66491fe93ab8d8777cee100c5 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 9 Jan 2025 05:04:55 +0700 Subject: [PATCH] TF-3413 Support `Reply to list` for email --- .../composer/domain/model/email_request.dart | 4 +- .../presentation/composer_controller.dart | 14 ++++-- .../create_email_request_extension.dart | 2 + .../email_action_type_extension.dart | 2 + .../view/mobile/mobile_editor_view.dart | 1 + .../view/web/web_editor_view.dart | 1 + .../controller/single_email_controller.dart | 14 ++++++ .../presentation_email_extension.dart | 49 +++++++++++++++++++ .../model/composer_arguments.dart | 24 +++++++++ .../email/presentation/utils/email_utils.dart | 36 ++++++++++++++ .../widgets/email_view_bottom_bar_widget.dart | 32 ++++++++++++ lib/l10n/intl_messages.arb | 8 ++- lib/main/localizations/app_localizations.dart | 8 +++ model/lib/email/email_action_type.dart | 1 + model/lib/email/email_property.dart | 1 + model/lib/extensions/email_extension.dart | 4 ++ .../list_email_header_extension.dart | 5 ++ .../presentation_email_extension.dart | 26 ---------- .../email/parsing_email_list_post_test.dart | 48 ++++++++++++++++++ .../presentation_email_extension_test.dart | 29 ++++++++++- 20 files changed, 277 insertions(+), 32 deletions(-) create mode 100644 lib/features/email/presentation/extensions/presentation_email_extension.dart create mode 100644 test/features/email/parsing_email_list_post_test.dart diff --git a/lib/features/composer/domain/model/email_request.dart b/lib/features/composer/domain/model/email_request.dart index 9528e6a7b..b5ba40d0e 100644 --- a/lib/features/composer/domain/model/email_request.dart +++ b/lib/features/composer/domain/model/email_request.dart @@ -40,7 +40,9 @@ class EmailRequest with EquatableMixin { ]; bool get isEmailAnswered => emailIdAnsweredOrForwarded != null && - (emailActionType == EmailActionType.reply || emailActionType == EmailActionType.replyAll); + (emailActionType == EmailActionType.reply + || emailActionType == EmailActionType.replyToList + || emailActionType == EmailActionType.replyAll); bool get isEmailForwarded => emailIdAnsweredOrForwarded != null && emailActionType == EmailActionType.forward; } \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 928190195..e5a0df67b 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -74,6 +74,7 @@ import 'package:tmail_ui_user/features/email/domain/state/get_email_content_stat import 'package:tmail_ui_user/features/email/domain/state/transform_html_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/extensions/presentation_email_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; @@ -640,11 +641,15 @@ class ComposerController extends BaseController _updateStatusEmailSendButton(); break; case EmailActionType.reply: + case EmailActionType.replyToList: case EmailActionType.replyAll: + log('ComposerController::_initEmail:listPost = ${arguments.listPost}'); _initEmailAddress( presentationEmail: arguments.presentationEmail!, actionType: arguments.emailActionType, - mailboxRole: arguments.presentationEmail!.mailboxContain?.role ?? mailboxDashBoardController.selectedMailbox.value?.role + mailboxRole: arguments.presentationEmail!.mailboxContain?.role + ?? mailboxDashBoardController.selectedMailbox.value?.role, + listPost: arguments.listPost, ); _initSubjectEmail( presentationEmail: arguments.presentationEmail!, @@ -798,12 +803,15 @@ class ComposerController extends BaseController required PresentationEmail presentationEmail, required EmailActionType actionType, Role? mailboxRole, + String? listPost, }) { + log('ComposerController::_initEmailAddress:listPost = $listPost'); final recipients = presentationEmail.generateRecipientsEmailAddressForComposer( emailActionType: actionType, - mailboxRole: mailboxRole + mailboxRole: mailboxRole, + listPost: listPost, ); - final userName = mailboxDashBoardController.sessionCurrent?.username; + final userName = mailboxDashBoardController.sessionCurrent?.username; if (userName != null) { final isSender = presentationEmail.from.asList().every((element) => element.email == userName.value); if (isSender) { diff --git a/lib/features/composer/presentation/extensions/create_email_request_extension.dart b/lib/features/composer/presentation/extensions/create_email_request_extension.dart index f6de928bb..e2dda9257 100644 --- a/lib/features/composer/presentation/extensions/create_email_request_extension.dart +++ b/lib/features/composer/presentation/extensions/create_email_request_extension.dart @@ -74,6 +74,7 @@ extension CreateEmailRequestExtension on CreateEmailRequest { MessageIdsHeaderValue? createInReplyTo() { if (emailActionType == EmailActionType.reply || + emailActionType == EmailActionType.replyToList || emailActionType == EmailActionType.replyAll ) { return messageId; @@ -83,6 +84,7 @@ extension CreateEmailRequestExtension on CreateEmailRequest { MessageIdsHeaderValue? createReferences() { if (emailActionType == EmailActionType.reply || + emailActionType == EmailActionType.replyToList || emailActionType == EmailActionType.replyAll || emailActionType == EmailActionType.forward ) { diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index b664c3717..2a5b5b510 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -12,6 +12,7 @@ extension EmailActionTypeExtension on EmailActionType { String getSubjectComposer(BuildContext? context, String subject) { switch(this) { case EmailActionType.reply: + case EmailActionType.replyToList: case EmailActionType.replyAll: if (subject.toLowerCase().startsWith('re:')) { return subject; @@ -60,6 +61,7 @@ extension EmailActionTypeExtension on EmailActionType { final languageTag = locale.toLanguageTag(); switch(this) { case EmailActionType.reply: + case EmailActionType.replyToList: case EmailActionType.replyAll: final receivedAt = presentationEmail.receivedAt; final emailAddress = presentationEmail.from.toEscapeHtmlStringUseCommaSeparator(); diff --git a/lib/features/composer/presentation/view/mobile/mobile_editor_view.dart b/lib/features/composer/presentation/view/mobile/mobile_editor_view.dart index 1859f9026..28c686066 100644 --- a/lib/features/composer/presentation/view/mobile/mobile_editor_view.dart +++ b/lib/features/composer/presentation/view/mobile/mobile_editor_view.dart @@ -80,6 +80,7 @@ class MobileEditorView extends StatelessWidget with EditorViewMixin { } ); case EmailActionType.reply: + case EmailActionType.replyToList: case EmailActionType.replyAll: case EmailActionType.forward: if (contentViewState == null) { diff --git a/lib/features/composer/presentation/view/web/web_editor_view.dart b/lib/features/composer/presentation/view/web/web_editor_view.dart index f7cd0cca8..519a8245d 100644 --- a/lib/features/composer/presentation/view/web/web_editor_view.dart +++ b/lib/features/composer/presentation/view/web/web_editor_view.dart @@ -145,6 +145,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { } ); case EmailActionType.reply: + case EmailActionType.replyToList: case EmailActionType.replyAll: case EmailActionType.forward: if (contentViewState == null) { diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 556c4a399..d32c7d614 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -1425,6 +1425,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ) ); break; + case EmailActionType.replyToList: + log('SingleEmailController::pressEmailAction:replyToList'); + mailboxDashBoardController.goToComposer( + ComposerArguments.replyToListEmail( + presentationEmail: presentationEmail, + content: currentEmailLoaded.value?.htmlContent ?? '', + inlineImages: currentEmailLoaded.value?.inlineImages ?? [], + mailboxRole: presentationEmail.mailboxContain?.role, + messageId: currentEmailLoaded.value?.emailCurrent?.messageId, + references: currentEmailLoaded.value?.emailCurrent?.references, + listPost: currentEmailLoaded.value?.emailCurrent?.listPost, + ) + ); + break; case EmailActionType.replyAll: mailboxDashBoardController.goToComposer( ComposerArguments.replyAllEmail( diff --git a/lib/features/email/presentation/extensions/presentation_email_extension.dart b/lib/features/email/presentation/extensions/presentation_email_extension.dart new file mode 100644 index 000000000..017c7a632 --- /dev/null +++ b/lib/features/email/presentation/extensions/presentation_email_extension.dart @@ -0,0 +1,49 @@ + +import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/email/email_action_type.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/list_email_address_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; + +extension PresentationEmailExtension on PresentationEmail { + Tuple3, List, List> generateRecipientsEmailAddressForComposer({ + required EmailActionType emailActionType, + Role? mailboxRole, + String? listPost, + }) { + switch (emailActionType) { + case EmailActionType.reply: + if (mailboxRole == PresentationMailbox.roleSent) { + return Tuple3(to.asList(), [], []); + } else { + final replyToAddress = replyTo.asList().isNotEmpty + ? replyTo.asList() + : from.asList(); + return Tuple3(replyToAddress, [], []); + } + case EmailActionType.replyToList: + final listEmailAddress = EmailUtils.parsingListPost(listPost ?? '') ?? []; + log('PresentationEmailExtension::generateRecipientsEmailAddressForComposer:listEmailAddress = $listEmailAddress'); + return Tuple3(listEmailAddress, [], []); + case EmailActionType.replyAll: + if (mailboxRole == PresentationMailbox.roleSent) { + return Tuple3(to.asList(), cc.asList(), bcc.asList()); + } else { + final senderReplyToAddress = replyTo.asList().isNotEmpty + ? replyTo.asList() + : from.asList(); + return Tuple3( + to.asList() + senderReplyToAddress, + cc.asList(), + bcc.asList(), + ); + } + default: + return Tuple3(to.asList(), cc.asList(), bcc.asList()); + } + } +} \ No newline at end of file diff --git a/lib/features/email/presentation/model/composer_arguments.dart b/lib/features/email/presentation/model/composer_arguments.dart index 479d0e617..8b5744619 100644 --- a/lib/features/email/presentation/model/composer_arguments.dart +++ b/lib/features/email/presentation/model/composer_arguments.dart @@ -32,6 +32,7 @@ class ComposerArguments extends RouterArguments { final ScreenDisplayMode displayMode; final List? cc; final List? bcc; + final String? listPost; ComposerArguments({ this.emailActionType = EmailActionType.compose, @@ -54,6 +55,7 @@ class ComposerArguments extends RouterArguments { this.displayMode = ScreenDisplayMode.normal, this.cc, this.bcc, + this.listPost, }); factory ComposerArguments.fromSendingEmail(SendingEmail sendingEmail) => @@ -130,6 +132,25 @@ class ComposerArguments extends RouterArguments { references: references, ); + factory ComposerArguments.replyToListEmail({ + required PresentationEmail presentationEmail, + required String content, + required List inlineImages, + Role? mailboxRole, + MessageIdsHeaderValue? messageId, + MessageIdsHeaderValue? references, + String? listPost, + }) => ComposerArguments( + emailActionType: EmailActionType.replyToList, + presentationEmail: presentationEmail, + emailContents: content, + inlineImages: inlineImages, + mailboxRole: mailboxRole, + messageId: messageId, + references: references, + listPost: listPost, + ); + factory ComposerArguments.replyAllEmail({ required PresentationEmail presentationEmail, required String content, @@ -206,6 +227,7 @@ class ComposerArguments extends RouterArguments { displayMode, cc, bcc, + listPost, ]; ComposerArguments copyWith({ @@ -229,6 +251,7 @@ class ComposerArguments extends RouterArguments { ScreenDisplayMode? displayMode, List? cc, List? bcc, + String? listPost, }) { return ComposerArguments( emailActionType: emailActionType ?? this.emailActionType, @@ -251,6 +274,7 @@ class ComposerArguments extends RouterArguments { displayMode: displayMode ?? this.displayMode, cc: cc ?? this.cc, bcc: bcc ?? this.bcc, + listPost: listPost ?? this.listPost, ); } } diff --git a/lib/features/email/presentation/utils/email_utils.dart b/lib/features/email/presentation/utils/email_utils.dart index d4eb18845..964af35c2 100644 --- a/lib/features/email/presentation/utils/email_utils.dart +++ b/lib/features/email/presentation/utils/email_utils.dart @@ -9,10 +9,12 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; +import 'package:tmail_ui_user/main/routes/route_utils.dart'; class EmailUtils { @@ -83,4 +85,38 @@ class EmailUtils { return false; } } + + static List? parsingListPost(String listPost) { + try { + if (listPost.isEmpty) { + return null; + } + + final regExpMailtoLinks = RegExp(r'mailto:([^>,]*)'); + final allMatchesMailtoLinks = regExpMailtoLinks.allMatches(listPost); + final listMailtoLinks = allMatchesMailtoLinks + .map((match) => match.group(0)) + .whereNotNull() + .toList(); + log('EmailUtils::parsingListPost:listMailtoLinks: $listMailtoLinks'); + + if (listMailtoLinks.isNotEmpty) { + return listMailtoLinks + .map((mailto) { + final mapMailto = RouteUtils.parseMapMailtoFromUri(mailto); + final emailAddress = mapMailto[RouteUtils.paramMailtoAddress]; + return emailAddress != null + ? EmailAddress(null, emailAddress) + : null; + }) + .whereNotNull() + .toList(); + } else { + return null; + } + } catch (e) { + logError('EmailUtils::parsingListPost:Exception = $e'); + return null; + } + } } \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/email_view_bottom_bar_widget.dart b/lib/features/email/presentation/widgets/email_view_bottom_bar_widget.dart index 381ab3f98..4343695aa 100644 --- a/lib/features/email/presentation/widgets/email_view_bottom_bar_widget.dart +++ b/lib/features/email/presentation/widgets/email_view_bottom_bar_widget.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; +import 'package:model/extensions/email_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/styles/email_view_bottom_bar_widget_styles.dart'; @@ -73,6 +74,37 @@ class EmailViewBottomBarWidget extends StatelessWidget { return const SizedBox.shrink(); } }), + Obx(() { + final currentEmailLoaded = _singleEmailController.currentEmailLoaded.value; + + if (currentEmailLoaded != null && + currentEmailLoaded.emailCurrent?.hasListPost == true) { + return Expanded( + child: TMailButtonWidget( + key: const Key('reply_to_list_email_button'), + text: AppLocalizations.of(context).replyToList, + icon: _imagePaths.icReply, + borderRadius: EmailViewBottomBarWidgetStyles.buttonRadius, + iconSize: EmailViewBottomBarWidgetStyles.buttonIconSize, + textAlign: TextAlign.center, + flexibleText: true, + padding: EmailViewBottomBarWidgetStyles.buttonPadding, + backgroundColor: EmailViewBottomBarWidgetStyles.buttonBackgroundColor, + textStyle: EmailViewBottomBarWidgetStyles.getButtonTextStyle( + context, + _responsiveUtils, + ), + verticalDirection: _responsiveUtils.isPortraitMobile(context), + onTapActionCallback: () => emailActionCallback.call( + EmailActionType.replyToList, + presentationEmail, + ), + ), + ); + } else { + return const SizedBox.shrink(); + } + }), Obx(() { if (_singleEmailController.currentEmailLoaded.value != null) { return Expanded( diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c86038fb0..61d952d41 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-12-31T12:11:05.777668", + "@@last_modified": "2025-01-09T04:35:09.125475", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4083,5 +4083,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "replyToList": "Reply to list", + "@replyToList": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 0af16a8f8..ffaf432c0 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4286,4 +4286,12 @@ class AppLocalizations { name: 'getHelpOrReportABug', ); } + + String get replyToList { + return Intl.message( + 'Reply to list', + name: 'replyToList', + ); + } + } diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index a125b4c3a..c158c2dc9 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -1,6 +1,7 @@ enum EmailActionType { reply, + replyToList, forward, replyAll, compose, diff --git a/model/lib/email/email_property.dart b/model/lib/email/email_property.dart index 28d3e4264..c8d0e4e3a 100644 --- a/model/lib/email/email_property.dart +++ b/model/lib/email/email_property.dart @@ -24,4 +24,5 @@ class EmailProperty { static const String references = 'references'; static const String headerUnsubscribeKey = 'List-Unsubscribe'; static const String headerSMimeStatusKey = 'X-SMIME-Status'; + static const String headerListPostKey = 'List-Post'; } \ No newline at end of file diff --git a/model/lib/extensions/email_extension.dart b/model/lib/extensions/email_extension.dart index dca4929de..e6002557f 100644 --- a/model/lib/extensions/email_extension.dart +++ b/model/lib/extensions/email_extension.dart @@ -36,6 +36,10 @@ extension EmailExtension on Email { String get sMimeStatusHeaderParsed => sMimeStatusHeader?[IndividualHeaderIdentifier.sMimeStatusHeader]?.trim() ?? ''; + String get listPost => headers.listPost; + + bool get hasListPost => listPost.isNotEmpty; + IdentityId? get identityIdFromHeader { final rawIdentityId = identityHeader?[IndividualHeaderIdentifier.identityHeader]; if (rawIdentityId == null) return null; diff --git a/model/lib/extensions/list_email_header_extension.dart b/model/lib/extensions/list_email_header_extension.dart index 8121e9b52..c3be89673 100644 --- a/model/lib/extensions/list_email_header_extension.dart +++ b/model/lib/extensions/list_email_header_extension.dart @@ -24,4 +24,9 @@ extension ListEmailHeaderExtension on Set? { logger.log('ListEmailHeaderExtension::sMimeStatus: $sMimeStatus'); return sMimeStatus?.value.trim() ?? ''; } + + String get listPost { + final listPost = this?.firstWhereOrNull((header) => header.name == EmailProperty.headerListPostKey); + return listPost?.value ?? ''; + } } \ No newline at end of file diff --git a/model/lib/extensions/presentation_email_extension.dart b/model/lib/extensions/presentation_email_extension.dart index f329e164a..09b482786 100644 --- a/model/lib/extensions/presentation_email_extension.dart +++ b/model/lib/extensions/presentation_email_extension.dart @@ -6,11 +6,9 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/utils/app_logger.dart'; import 'package:http_parser/http_parser.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.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_action_type.dart'; import 'package:model/email/eml_attachment.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/email_address_extension.dart'; @@ -133,30 +131,6 @@ extension PresentationEmailExtension on PresentationEmail { return allEmailAddress.isNotEmpty ? allEmailAddress.join(', ') : ''; } - Tuple3, List, List> generateRecipientsEmailAddressForComposer({ - required EmailActionType emailActionType, - Role? mailboxRole - }) { - switch(emailActionType) { - case EmailActionType.reply: - if (mailboxRole == PresentationMailbox.roleSent) { - return Tuple3(to.asList(), [], []); - } else { - final replyToAddress = replyTo.asList().isNotEmpty ? replyTo.asList() : from.asList(); - return Tuple3(replyToAddress, [], []); - } - case EmailActionType.replyAll: - if (mailboxRole == PresentationMailbox.roleSent) { - return Tuple3(to.asList(), cc.asList(), bcc.asList()); - } else { - final senderReplyToAddress = replyTo.asList().isNotEmpty ? replyTo.asList() : from.asList(); - return Tuple3(to.asList() + senderReplyToAddress, cc.asList(), bcc.asList()); - } - default: - return Tuple3(to.asList(), cc.asList(), bcc.asList()); - } - } - PresentationEmail toSearchPresentationEmail(Map mapMailboxes) { mailboxIds?.removeWhere((key, value) => !value); diff --git a/test/features/email/parsing_email_list_post_test.dart b/test/features/email/parsing_email_list_post_test.dart new file mode 100644 index 000000000..a0fec2b20 --- /dev/null +++ b/test/features/email/parsing_email_list_post_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:model/extensions/list_email_address_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; + +void main() { + group('parsing email list post test', () { + test('parsingListPost returns null for empty input', () { + expect(EmailUtils.parsingListPost(''), isNull); + }); + + test('parsingListPost returns null for input without links', () { + expect(EmailUtils.parsingListPost('Some text without links'), isNull); + }); + + test('parsingListPost parses mailto links', () { + final listEmailAddress = + EmailUtils.parsingListPost(''); + expect(listEmailAddress, isNotNull); + expect(listEmailAddress![0].email, contains('user@example.com')); + }); + + test('parsingListPost parses both mailto without <>', () { + final listEmailAddress = + EmailUtils.parsingListPost('mailto:support@example.com'); + expect(listEmailAddress, isNotNull); + expect(listEmailAddress![0].email, contains('support@example.com')); + }); + + test('parsingListPost parses more mailto', () { + final listEmailAddress = EmailUtils.parsingListPost( + ', , '); + expect(listEmailAddress, isNotNull); + expect(listEmailAddress!.length, equals(3)); + expect( + listEmailAddress.asSetAddress(), + containsAll({ + 'support@example.com', + 'support@example2.com', + 'support@example3.com' + }), + ); + }); + + test('parsingListPost returns null for input with invalid links', () { + expect(EmailUtils.parsingListPost('Invalid link: invalid'), isNull); + }); + }); +} diff --git a/test/model/lib/extensions/presentation_email_extension_test.dart b/test/model/lib/extensions/presentation_email_extension_test.dart index f8e964f8b..7c83980fc 100644 --- a/test/model/lib/extensions/presentation_email_extension_test.dart +++ b/test/model/lib/extensions/presentation_email_extension_test.dart @@ -3,8 +3,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; -import 'package:model/extensions/presentation_email_extension.dart'; +import 'package:model/extensions/email_address_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/presentation_email_extension.dart'; void main() { group('presentation email extension test', () { @@ -14,6 +15,7 @@ void main() { final userDEmailAddress = EmailAddress('User D', 'userD@domain.com'); final userEEmailAddress = EmailAddress('User E', 'userE@domain.com'); final replyToEmailAddress = EmailAddress('Reply To', 'replyToThis@domain.com'); + final replyToListEmailAddress = EmailAddress(null, 'replyToList@domain.com'); group('GIVEN user A is the sender AND send an email to user B and user E, cc to user C, bcc to user D', () { test('THEN user A click reply, generateRecipientsEmailAddressForComposer SHOULD return user B email + user E email to reply', () { @@ -141,6 +143,31 @@ void main() { expect(result.value2, containsAll(expectedResult.value2)); expect(result.value3, containsAll(expectedResult.value3)); }); + + test( + 'THEN user A click reply to list, generateRecipientsEmailAddressForComposer\n' + 'SHOULD return email address in mailto of List-Post to reply\n', + () { + final expectedResult = Tuple3([replyToListEmailAddress], [], []); + + final emailToReplyToList = PresentationEmail( + from: {userBEmailAddress}, + replyTo: {replyToEmailAddress}, + to: {userAEmailAddress, userEEmailAddress}, + cc: {userCEmailAddress}, + bcc: {userDEmailAddress}, + ); + + final result = emailToReplyToList.generateRecipientsEmailAddressForComposer( + emailActionType: EmailActionType.replyToList, + mailboxRole: PresentationMailbox.roleInbox, + listPost: '', + ); + + expect(result.value1, containsAll(expectedResult.value1)); + expect(result.value2, containsAll(expectedResult.value2)); + expect(result.value3, containsAll(expectedResult.value3)); + }); }); group('Given user A is the sender AND send an email to user B + user E, cc to user C, bcc to user D THEN user B click forward', () {