From 12d52fc56c6a159c24b30b190989803ef14202e6 Mon Sep 17 00:00:00 2001 From: DatDang Date: Thu, 31 Oct 2024 11:17:14 +0700 Subject: [PATCH 1/3] TF-3235 Update jmap-dart-client dependencies --- contact/pubspec.lock | 4 ++-- email_recovery/pubspec.lock | 4 ++-- fcm/pubspec.lock | 4 ++-- forward/pubspec.lock | 4 ++-- model/pubspec.lock | 4 ++-- pubspec.lock | 4 ++-- rule_filter/pubspec.lock | 4 ++-- server_settings/pubspec.lock | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/contact/pubspec.lock b/contact/pubspec.lock index 9cf2644136..3123b881fd 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -652,10 +652,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/email_recovery/pubspec.lock b/email_recovery/pubspec.lock index bbe1cb845b..9977a0fb8c 100644 --- a/email_recovery/pubspec.lock +++ b/email_recovery/pubspec.lock @@ -296,10 +296,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/fcm/pubspec.lock b/fcm/pubspec.lock index cef6ac7756..92848214ba 100644 --- a/fcm/pubspec.lock +++ b/fcm/pubspec.lock @@ -296,10 +296,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/forward/pubspec.lock b/forward/pubspec.lock index cef6ac7756..92848214ba 100644 --- a/forward/pubspec.lock +++ b/forward/pubspec.lock @@ -296,10 +296,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/model/pubspec.lock b/model/pubspec.lock index 195b271e43..7d67129308 100644 --- a/model/pubspec.lock +++ b/model/pubspec.lock @@ -644,10 +644,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/pubspec.lock b/pubspec.lock index 613d0ce91a..9d5abf3eac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1236,10 +1236,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/rule_filter/pubspec.lock b/rule_filter/pubspec.lock index cef6ac7756..92848214ba 100644 --- a/rule_filter/pubspec.lock +++ b/rule_filter/pubspec.lock @@ -296,10 +296,10 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 url: "https://github.com/linagora/jmap-dart-client.git" source: git - version: "0.2.3" + version: "0.3.0" js: dependency: transitive description: diff --git a/server_settings/pubspec.lock b/server_settings/pubspec.lock index 59b5687278..8c87a464ed 100644 --- a/server_settings/pubspec.lock +++ b/server_settings/pubspec.lock @@ -288,7 +288,7 @@ packages: description: path: "." ref: main - resolved-ref: b75666ba1ac351c7e7be7a1a8f95e58a860505fc + resolved-ref: e457e97732af70a6f068448fb5b5121b75ee2464 url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.2.3" From 990f16261f7fceed8b2bea76dded8e18d0e4dcb1 Mon Sep 17 00:00:00 2001 From: DatDang Date: Thu, 31 Oct 2024 11:17:48 +0700 Subject: [PATCH 2/3] TF-3235 Create seach emails in thread API, datasource and repository --- .../data/datasource/thread_datasource.dart | 13 + .../local_thread_datasource_impl.dart | 16 ++ .../thread_datasource_impl.dart | 25 ++ .../thread/data/network/thread_api.dart | 97 ++++++++ .../repository/thread_repository_impl.dart | 7 +- .../thread/domain/model/email_response.dart | 2 +- .../thread/domain/model/search_email.dart | 91 +++++++ .../domain/model/search_emails_response.dart | 35 +++ .../domain/repository/thread_repository.dart | 3 +- .../thread/data/network/thread_api_test.dart | 228 ++++++++++++++++++ .../model/search_emails_response_test.dart | 93 +++++++ 11 files changed, 605 insertions(+), 5 deletions(-) create mode 100644 lib/features/thread/domain/model/search_email.dart create mode 100644 lib/features/thread/domain/model/search_emails_response.dart create mode 100644 test/features/thread/data/network/thread_api_test.dart create mode 100644 test/features/thread/domain/model/search_emails_response_test.dart diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index 6c324c855e..ccd7f46d89 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -14,6 +14,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; abstract class ThreadDataSource { Future getAllEmail( @@ -28,6 +29,18 @@ abstract class ThreadDataSource { } ); + Future searchEmails( + Session session, + AccountId accountId, + { + UnsignedInt? limit, + int? position, + Set? sort, + Filter? filter, + Properties? properties + } + ); + Future getChanges( Session session, AccountId accountId, diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index b18202dabc..769df93776 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/thread/data/local/email_cache_manager.dar import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class LocalThreadDataSourceImpl extends ThreadDataSource { @@ -40,6 +41,21 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { throw UnimplementedError(); } + @override + Future searchEmails( + Session session, + AccountId accountId, + { + UnsignedInt? limit, + int? position, + Set? sort, + Filter? filter, + Properties? properties + } + ) { + throw UnimplementedError(); + } + @override Future getChanges( Session session, diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index 1f1974c2a3..48c317357a 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_isolate_worker.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class ThreadDataSourceImpl extends ThreadDataSource { @@ -56,6 +57,30 @@ class ThreadDataSourceImpl extends ThreadDataSource { }).catchError(_exceptionThrower.throwException); } + @override + Future searchEmails( + Session session, + AccountId accountId, + { + UnsignedInt? limit, + int? position, + Set? sort, + Filter? filter, + Properties? properties, + } + ) { + return Future.sync(() async { + return await threadAPI.searchEmails( + session, + accountId, + limit: limit, + position: position, + sort: sort, + filter: filter, + properties: properties); + }).catchError(_exceptionThrower.throwException); + } + @override Future getChanges( Session session, diff --git a/lib/features/thread/data/network/thread_api.dart b/lib/features/thread/data/network/thread_api.dart index 93cc0ff5c3..72a2b2ffbb 100644 --- a/lib/features/thread/data/network/thread_api.dart +++ b/lib/features/thread/data/network/thread_api.dart @@ -1,10 +1,13 @@ import 'dart:async'; +import 'package:core/utils/app_logger.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/request/reference_path.dart'; +import 'package:jmap_dart_client/jmap/core/request/request_invocation.dart'; +import 'package:jmap_dart_client/jmap/core/response/response_object.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; import 'package:jmap_dart_client/jmap/core/state.dart'; @@ -16,8 +19,12 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/query/query_email_method.dart'; +import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart'; +import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_method.dart'; +import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_response.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; import 'package:tmail_ui_user/main/error/capability_validator.dart'; class ThreadAPI { @@ -83,6 +90,96 @@ class ThreadAPI { return EmailsResponse(emailList: resultList?.list, state: resultList?.state); } + Future searchEmails( + Session session, + AccountId accountId, + { + UnsignedInt? limit, + int? position, + Set? sort, + Filter? filter, + Properties? properties + } + ) async { + final processingInvocation = ProcessingInvocation(); + + final jmapRequestBuilder = JmapRequestBuilder(httpClient, processingInvocation); + + // Email/query + final queryEmailMethod = QueryEmailMethod(accountId); + + if (limit != null) queryEmailMethod.addLimit(limit); + + if (position != null) queryEmailMethod.addPosition(position); + + if (sort != null) queryEmailMethod.addSorts(sort); + + if (filter != null) queryEmailMethod.addFilters(filter); + + final queryEmailInvocation = jmapRequestBuilder.invocation(queryEmailMethod); + + // SearchSnippet/get + final getSearchSnippetMethod = SearchSnippetGetMethod(accountId); + if (filter != null) getSearchSnippetMethod.addFilters(filter); + getSearchSnippetMethod.addReferenceEmailIds( + processingInvocation.createResultReference( + queryEmailInvocation.methodCallId, + ReferencePath.idsPath)); + final getSearchSnippetInvocation = jmapRequestBuilder.invocation( + getSearchSnippetMethod); + + // Email/get + final getEmailMethod = GetEmailMethod(accountId); + + if (properties != null) getEmailMethod.addProperties(properties); + + getEmailMethod.addReferenceIds(processingInvocation.createResultReference( + queryEmailInvocation.methodCallId, + ReferencePath.idsPath)); + + final getEmailInvocation = jmapRequestBuilder.invocation(getEmailMethod); + + final capabilities = getEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final result = await (jmapRequestBuilder + ..usings(capabilities)) + .build() + .execute(); + + final emailResultList = result.parse( + getEmailInvocation.methodCallId, GetEmailResponse.deserialize); + + if (sort != null && emailResultList != null) { + for (var comparator in sort) { + emailResultList.sortEmails(comparator); + } + } + + final searchSnippets = _getSearchSnippetsFromResponse( + result, + getSearchSnippetMethodCallId: getSearchSnippetInvocation.methodCallId, + ); + return SearchEmailsResponse( + emailList: emailResultList?.list, + state: emailResultList?.state, + searchSnippets: searchSnippets); + } + + List? _getSearchSnippetsFromResponse( + ResponseObject response, + {required MethodCallId getSearchSnippetMethodCallId} + ) { + try { + return response.parse( + getSearchSnippetMethodCallId, + SearchSnippetGetResponse.fromJson)?.list; + } catch (e) { + logError('ThreadAPI::searchEmails:getSearchSnippetsFromResponse: Exception = $e'); + return null; + } + } + Future getChanges( Session session, AccountId accountId, diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index b7a5482e9a..3541a07f63 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; class ThreadRepositoryImpl extends ThreadRepository { @@ -287,7 +288,7 @@ class ThreadRepositoryImpl extends ThreadRepository { } @override - Future> searchEmails( + Future> searchEmails( Session session, AccountId accountId, { @@ -298,7 +299,7 @@ class ThreadRepositoryImpl extends ThreadRepository { Properties? properties } ) async { - final emailResponse = await mapDataSource[DataSourceType.network]!.getAllEmail( + final searchEmailsResponse = await mapDataSource[DataSourceType.network]!.searchEmails( session, accountId, limit: limit, @@ -307,7 +308,7 @@ class ThreadRepositoryImpl extends ThreadRepository { filter: filter, properties: properties); - return emailResponse.emailList ?? List.empty(); + return searchEmailsResponse.toSearchEmails ?? []; } @override diff --git a/lib/features/thread/domain/model/email_response.dart b/lib/features/thread/domain/model/email_response.dart index 87c054e417..dd72318e8e 100644 --- a/lib/features/thread/domain/model/email_response.dart +++ b/lib/features/thread/domain/model/email_response.dart @@ -7,7 +7,7 @@ class EmailsResponse with EquatableMixin { final List? emailList; final State? state; - EmailsResponse({ + const EmailsResponse({ this.emailList, this.state }); diff --git a/lib/features/thread/domain/model/search_email.dart b/lib/features/thread/domain/model/search_email.dart new file mode 100644 index 0000000000..4b995ac08b --- /dev/null +++ b/lib/features/thread/domain/model/search_email.dart @@ -0,0 +1,91 @@ +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; + +class SearchEmail extends Email { + final String? searchSnippetSubject; + final String? searchSnippetPreview; + + SearchEmail({ + super.id, + super.blobId, + super.threadId, + super.mailboxIds, + super.keywords, + super.size, + super.receivedAt, + super.headers, + super.messageId, + super.inReplyTo, + super.references, + super.subject, + super.sentAt, + super.hasAttachment, + super.preview, + super.sender, + super.from, + super.to, + super.cc, + super.bcc, + super.replyTo, + super.textBody, + super.htmlBody, + super.attachments, + super.bodyStructure, + super.bodyValues, + super.headerUserAgent, + super.headerMdn, + super.headerCalendarEvent, + super.sMimeStatusHeader, + super.identityHeader, + required this.searchSnippetSubject, + required this.searchSnippetPreview + }); + + @override + List get props => [ + ...super.props, + searchSnippetSubject, + searchSnippetPreview, + ]; + + factory SearchEmail.fromEmail( + Email email, { + String? searchSnippetSubject, + String? searchSnippetPreview, + }) { + return SearchEmail( + id: email.id, + blobId: email.blobId, + threadId: email.threadId, + mailboxIds: email.mailboxIds, + keywords: email.keywords, + size: email.size, + receivedAt: email.receivedAt, + headers: email.headers, + messageId: email.messageId, + inReplyTo: email.inReplyTo, + references: email.references, + subject: email.subject, + sentAt: email.sentAt, + hasAttachment: email.hasAttachment, + preview: email.preview, + sender: email.sender, + from: email.from, + to: email.to, + cc: email.cc, + bcc: email.bcc, + replyTo: email.replyTo, + textBody: email.textBody, + htmlBody: email.htmlBody, + attachments: email.attachments, + bodyStructure: email.bodyStructure, + bodyValues: email.bodyValues, + headerUserAgent: email.headerUserAgent, + headerMdn: email.headerMdn, + headerCalendarEvent: email.headerCalendarEvent, + sMimeStatusHeader: email.sMimeStatusHeader, + identityHeader: email.identityHeader, + searchSnippetSubject: searchSnippetSubject, + searchSnippetPreview: searchSnippetPreview, + ); + } +} \ No newline at end of file diff --git a/lib/features/thread/domain/model/search_emails_response.dart b/lib/features/thread/domain/model/search_emails_response.dart new file mode 100644 index 0000000000..f17a110881 --- /dev/null +++ b/lib/features/thread/domain/model/search_emails_response.dart @@ -0,0 +1,35 @@ +import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; + +class SearchEmailsResponse extends EmailsResponse { + const SearchEmailsResponse({ + super.emailList, + super.state, + required this.searchSnippets, + }); + + final List? searchSnippets; + + @override + List get props => [...super.props, searchSnippets]; + + List? get toSearchEmails { + if (searchSnippets == null) { + return emailList?.map(SearchEmail.fromEmail).toList(); + } + + final mapSearchSnippet = Map.fromEntries( + searchSnippets!.map((searchSnippet) => MapEntry( + searchSnippet.emailId, + searchSnippet))); + + return emailList?.map((email) { + final searchSnippet = mapSearchSnippet[email.id]; + return SearchEmail.fromEmail( + email, + searchSnippetSubject: searchSnippet?.subject, + searchSnippetPreview: searchSnippet?.preview); + }).toList(); + } +} \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index c1ba1e1808..87e6ea37bb 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -13,6 +13,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; abstract class ThreadRepository { Stream getAllEmail( @@ -41,7 +42,7 @@ abstract class ThreadRepository { Stream loadMoreEmails(GetEmailRequest emailRequest); - Future> searchEmails( + Future> searchEmails( Session session, AccountId accountId, { diff --git a/test/features/thread/data/network/thread_api_test.dart b/test/features/thread/data/network/thread_api_test.dart new file mode 100644 index 0000000000..67b2a759f8 --- /dev/null +++ b/test/features/thread/data/network/thread_api_test.dart @@ -0,0 +1,228 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:http_mock_adapter/http_mock_adapter.dart'; +import 'package:jmap_dart_client/http/http_client.dart'; +import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart'; +import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart'; +import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; + +import '../../../../fixtures/account_fixtures.dart'; +import '../../../../fixtures/session_fixtures.dart'; + +void main() { + final baseOption = BaseOptions(method: 'POST'); + final dio = Dio(baseOption)..options.baseUrl = 'http://domain.com/jmap'; + final dioAdapter = DioAdapter(dio: dio); + final httpClient = HttpClient(dio); + final threadApi = ThreadAPI(httpClient); + + final sessionState = State('some-session-state'); + final state = State('some-state'); + final filter = EmailFilterCondition(text: 'some-text'); + + group('thread api test:', () { + group('searchEmails:', () { + Map generateRequest({required Filter filter}) => { + "using": [ + "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail", + "urn:apache:james:params:jmap:mail:shares", + ], + "methodCalls": [ + [ + "Email/query", + { + "accountId": AccountFixtures.aliceAccountId.id.value, + "filter": filter.toJson(), + }, + "c0" + ], + [ + "SearchSnippet/get", + { + "accountId": AccountFixtures.aliceAccountId.id.value, + "filter": filter.toJson(), + "#emailIds": { + "resultOf": "c0", + "name": "Email/query", + "path": "/ids/*" + }, + }, + "c1" + ], + [ + "Email/get", + { + "accountId": AccountFixtures.aliceAccountId.id.value, + "#ids": { + "resultOf": "c0", + "name": "Email/query", + "path": "/ids/*" + }, + }, + "c2" + ] + ] + }; + + Map generateResponse({ + required List foundSearchEmails, + required List notFoundEmailIds, + ErrorMethodResponse? searchSnippetError, + }) => { + "sessionState": sessionState.value, + "methodResponses": [ + [ + "Email/query", + { + "accountId": AccountFixtures.aliceAccountId.id.value, + "ids": foundSearchEmails + .map((searchEmail) => searchEmail.id?.id.value) + .toList() + ..addAll(notFoundEmailIds.map((emailId) => emailId.id.value)), + }, + "c0" + ], + if (searchSnippetError == null) + [ + "SearchSnippet/get", + { + "accountId": AccountFixtures.aliceAccountId.id.value, + "notFound": notFoundEmailIds + .map((emailId) => emailId.id.value) + .toList(), + "state": state.value, + "list": foundSearchEmails + .map((searchEmail) => SearchSnippet( + emailId: searchEmail.id!, + subject: searchEmail.searchSnippetSubject, + preview: searchEmail.searchSnippetPreview).toJson()) + .toList(), + }, + "c1" + ] + else + [ + "error", + { + "type": searchSnippetError.type.value, + }, + "c1" + ], + [ + "Email/get", + { + "accountId": AccountFixtures.aliceAccountId.id.value, + "state": state.value, + "list": foundSearchEmails + .map((searchEmail) => searchEmail.toJson()) + .toList(), + "notFound": notFoundEmailIds + .map((emailId) => emailId.id.value) + .toList(), + }, + "c2" + ] + ] + }; + + test( + 'should return SearchEmailResponse including search snippets ' + 'when search snippet method return values', + () async { + // arrange + final searchEmail = SearchEmail( + id: EmailId(Id('someEmailId')), + searchSnippetSubject: 'searchSnippetSubject', + searchSnippetPreview: 'searchSnippetPreview', + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + generateResponse( + foundSearchEmails: [searchEmail], + notFoundEmailIds: [], + ), + ), + data: generateRequest(filter: filter), + ); + + // act + final result = await threadApi.searchEmails( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: filter, + ); + + // assert + expect( + result, + equals( + SearchEmailsResponse( + searchSnippets: [ + SearchSnippet( + emailId: searchEmail.id!, + subject: searchEmail.searchSnippetSubject, + preview: searchEmail.searchSnippetPreview, + ), + ], + emailList: [Email(id: searchEmail.id)], + state: state + ), + ), + ); + }); + + test( + 'should return SearchEmailResponse without search snippets ' + 'when search snippet method return error', + () async { + // arrange + final searchEmail = SearchEmail( + id: EmailId(Id('someEmailId')), + searchSnippetSubject: 'searchSnippetSubject', + searchSnippetPreview: 'searchSnippetPreview', + ); + dioAdapter.onPost( + '', + (server) => server.reply( + 200, + generateResponse( + foundSearchEmails: [searchEmail], + notFoundEmailIds: [], + searchSnippetError: UnknownMethodResponse(), + ), + ), + data: generateRequest(filter: filter), + ); + + // act + final result = await threadApi.searchEmails( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: filter, + ); + + // assert + expect( + result, + equals( + SearchEmailsResponse( + searchSnippets: null, + emailList: [Email(id: searchEmail.id)], + state: state + ), + ), + ); + }); + }); + }); +} \ No newline at end of file diff --git a/test/features/thread/domain/model/search_emails_response_test.dart b/test/features/thread/domain/model/search_emails_response_test.dart new file mode 100644 index 0000000000..7acff7d1e7 --- /dev/null +++ b/test/features/thread/domain/model/search_emails_response_test.dart @@ -0,0 +1,93 @@ +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/search_snippet/search_snippet.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; + +void main() { + group('search emails response test:', () { + test( + 'should return list of search emails ' + 'when toSearchEmails is called', + () { + // arrange + final emailId = EmailId(Id('someId')); + const searchSnippetSubject = 'search-snippet-subject'; + const searchSnippetPreview = 'search-snippet-preview'; + final email = Email(id: emailId); + final searchSnippet = SearchSnippet( + emailId: emailId, + subject: searchSnippetSubject, + preview: searchSnippetPreview, + ); + final searchEmailsResponse = SearchEmailsResponse( + emailList: [email], + searchSnippets: [searchSnippet], + ); + + // act + final result = searchEmailsResponse.toSearchEmails; + + // assert + expect( + result, + [SearchEmail( + id: emailId, + searchSnippetSubject: searchSnippetSubject, + searchSnippetPreview: searchSnippetPreview)] + ); + }); + + test( + 'should map list of search emails according to emailId ' + 'when toSearchEmails is called', + () { + // arrange + final emailId1 = EmailId(Id('someId1')); + final emailId2 = EmailId(Id('someId2')); + const searchSnippetSubject1 = 'search-snippet-subject-1'; + const searchSnippetPreview1 = 'search-snippet-preview-1'; + const searchSnippetSubject2 = 'search-snippet-subject-2'; + const searchSnippetPreview2 = 'search-snippet-preview-2'; + final email1 = Email(id: emailId1, subject: 'subject-1'); + final searchSnippet1 = SearchSnippet( + emailId: emailId1, + subject: searchSnippetSubject1, + preview: searchSnippetPreview1, + ); + final email2 = Email(id: emailId2, subject: 'subject-2'); + final searchSnippet2 = SearchSnippet( + emailId: emailId2, + subject: searchSnippetSubject2, + preview: searchSnippetPreview2, + ); + final searchEmailsResponse = SearchEmailsResponse( + emailList: [email1, email2], + searchSnippets: [searchSnippet1, searchSnippet2], + ); + + // act + final result = searchEmailsResponse.toSearchEmails; + + // assert + expect( + result, + [ + SearchEmail( + id: emailId1, + subject: email1.subject, + searchSnippetSubject: searchSnippetSubject1, + searchSnippetPreview: searchSnippetPreview1, + ), + SearchEmail( + id: emailId2, + subject: email2.subject, + searchSnippetSubject: searchSnippetSubject2, + searchSnippetPreview: searchSnippetPreview2, + ), + ] + ); + }); + }); +} \ No newline at end of file From ec406945ccee6a438c6bdfdbf010bc28fbbb1cd6 Mon Sep 17 00:00:00 2001 From: DatDang Date: Fri, 1 Nov 2024 10:42:14 +0700 Subject: [PATCH 3/3] TF-3235 Create SeachSnippets for presentation emails and update at interactors --- .../quick_search_email_interactor.dart | 5 +- ...fresh_changes_search_email_interactor.dart | 5 +- .../usecases/search_email_interactor.dart | 5 +- .../search_more_email_interactor.dart | 5 +- model/lib/email/presentation_email.dart | 5 +- model/lib/extensions/email_extension.dart | 10 +- .../presentation_email_extension.dart | 24 +++- model/lib/mixin/search_snippet_mixin.dart | 4 + .../quick_search_email_interactor_test.dart | 93 ++++++++++++ ..._changes_search_email_interactor_test.dart | 98 +++++++++++++ .../search_email_interactor_test.dart | 135 ++++++++++++++++++ .../search_more_email_interactor_test.dart | 100 +++++++++++++ 12 files changed, 476 insertions(+), 13 deletions(-) create mode 100644 model/lib/mixin/search_snippet_mixin.dart create mode 100644 test/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor_test.dart create mode 100644 test/features/search/email/domain/usecases/refresh_changes_search_email_interactor_test.dart create mode 100644 test/features/thread/domain/usecases/search_email_interactor_test.dart create mode 100644 test/features/thread/domain/usecases/search_more_email_interactor_test.dart diff --git a/lib/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart b/lib/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart index 6e363114b8..e219e43d18 100644 --- a/lib/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart +++ b/lib/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart @@ -37,7 +37,10 @@ class QuickSearchEmailInteractor { properties: properties); final presentationEmailList = emailList - .map((email) => email.toPresentationEmail()) + .map((email) => email.toPresentationEmail( + searchSnippetSubject: email.searchSnippetSubject, + searchSnippetPreview: email.searchSnippetPreview, + )) .toList(); return Right(QuickSearchEmailSuccess(presentationEmailList)); diff --git a/lib/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart b/lib/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart index 75d4f579be..9ee4f19d4d 100644 --- a/lib/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart +++ b/lib/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart @@ -42,7 +42,10 @@ class RefreshChangesSearchEmailInteractor { properties: properties); final presentationEmailList = emailList - .map((email) => email.toPresentationEmail()) + .map((email) => email.toPresentationEmail( + searchSnippetSubject: email.searchSnippetSubject, + searchSnippetPreview: email.searchSnippetPreview, + )) .toList(); yield Right(RefreshChangesSearchEmailSuccess(presentationEmailList)); diff --git a/lib/features/thread/domain/usecases/search_email_interactor.dart b/lib/features/thread/domain/usecases/search_email_interactor.dart index 383256ad92..6d75a963b7 100644 --- a/lib/features/thread/domain/usecases/search_email_interactor.dart +++ b/lib/features/thread/domain/usecases/search_email_interactor.dart @@ -47,7 +47,10 @@ class SearchEmailInteractor { properties: properties); final presentationEmailList = emailList - .map((email) => email.toPresentationEmail()) + .map((email) => email.toPresentationEmail( + searchSnippetSubject: email.searchSnippetSubject, + searchSnippetPreview: email.searchSnippetPreview, + )) .toList(); yield Right(SearchEmailSuccess(presentationEmailList)); diff --git a/lib/features/thread/domain/usecases/search_more_email_interactor.dart b/lib/features/thread/domain/usecases/search_more_email_interactor.dart index 20163320ff..4d97b4775b 100644 --- a/lib/features/thread/domain/usecases/search_more_email_interactor.dart +++ b/lib/features/thread/domain/usecases/search_more_email_interactor.dart @@ -44,7 +44,10 @@ class SearchMoreEmailInteractor { final presentationEmailList = emailList .where((email) => email.id != lastEmailId) - .map((email) => email.toPresentationEmail()) + .map((email) => email.toPresentationEmail( + searchSnippetSubject: email.searchSnippetSubject, + searchSnippetPreview: email.searchSnippetPreview, + )) .toList(); yield Right(SearchMoreEmailSuccess(presentationEmailList)); diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index 5622a6de6f..1989247b83 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -18,8 +18,9 @@ import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/media_type_nullable_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/select_mode.dart'; +import 'package:model/mixin/search_snippet_mixin.dart'; -class PresentationEmail with EquatableMixin { +class PresentationEmail with EquatableMixin, SearchSnippetMixin { final EmailId? id; final Id? blobId; @@ -172,5 +173,7 @@ class PresentationEmail with EquatableMixin { htmlBody, bodyValues, headerCalendarEvent, + searchSnippetSubject, + searchSnippetPreview, ]; } \ No newline at end of file diff --git a/model/lib/extensions/email_extension.dart b/model/lib/extensions/email_extension.dart index 4fbaf6dfc3..dca4929de6 100644 --- a/model/lib/extensions/email_extension.dart +++ b/model/lib/extensions/email_extension.dart @@ -94,7 +94,11 @@ extension EmailExtension on Email { ); } - PresentationEmail toPresentationEmail({SelectMode selectMode = SelectMode.INACTIVE}) { + PresentationEmail toPresentationEmail({ + SelectMode selectMode = SelectMode.INACTIVE, + String? searchSnippetSubject, + String? searchSnippetPreview, + }) { return PresentationEmail( id: id, blobId: blobId, @@ -114,7 +118,9 @@ extension EmailExtension on Email { selectMode: selectMode, emailHeader: headers?.toList(), headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } Email combineEmail(Email newEmail, Properties updatedProperties) { diff --git a/model/lib/extensions/presentation_email_extension.dart b/model/lib/extensions/presentation_email_extension.dart index fc3bf0b6f8..d1b92731ee 100644 --- a/model/lib/extensions/presentation_email_extension.dart +++ b/model/lib/extensions/presentation_email_extension.dart @@ -64,7 +64,9 @@ extension PresentationEmailExtension on PresentationEmail { routeWeb: routeWeb, mailboxContain: mailboxContain, headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } PresentationEmail toSelectedEmail({required SelectMode selectMode}) { @@ -88,7 +90,9 @@ extension PresentationEmailExtension on PresentationEmail { routeWeb: routeWeb, mailboxContain: mailboxContain, headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } Email toEmail() { @@ -169,7 +173,9 @@ extension PresentationEmailExtension on PresentationEmail { routeWeb: routeWeb, mailboxContain: matchedMailbox, headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } PresentationMailbox? findMailboxContain(Map mapMailbox) { @@ -206,7 +212,9 @@ extension PresentationEmailExtension on PresentationEmail { routeWeb: routeWeb, mailboxContain: mailboxContain, headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } PresentationEmail updateKeywords(Map? newKeywords) { @@ -230,7 +238,9 @@ extension PresentationEmailExtension on PresentationEmail { routeWeb: routeWeb, mailboxContain: mailboxContain, headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } PresentationEmail syncPresentationEmail({PresentationMailbox? mailboxContain, Uri? routeWeb}) { @@ -254,7 +264,9 @@ extension PresentationEmailExtension on PresentationEmail { routeWeb: routeWeb, mailboxContain: mailboxContain, headerCalendarEvent: headerCalendarEvent - ); + ) + ..searchSnippetSubject = searchSnippetSubject + ..searchSnippetPreview = searchSnippetPreview; } bool isBelongToOneOfTheMailboxes(List mailboxIdsSource) { diff --git a/model/lib/mixin/search_snippet_mixin.dart b/model/lib/mixin/search_snippet_mixin.dart new file mode 100644 index 0000000000..94d0f0f266 --- /dev/null +++ b/model/lib/mixin/search_snippet_mixin.dart @@ -0,0 +1,4 @@ +mixin SearchSnippetMixin { + String? searchSnippetSubject; + String? searchSnippetPreview; +} \ No newline at end of file diff --git a/test/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor_test.dart b/test/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor_test.dart new file mode 100644 index 0000000000..8cde336fd7 --- /dev/null +++ b/test/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor_test.dart @@ -0,0 +1,93 @@ +import 'package:dartz/dartz.dart'; +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_filter_condition.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/extensions/email_extension.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/quick_search_email_state.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; + +import '../../../../fixtures/account_fixtures.dart'; +import '../../../../fixtures/session_fixtures.dart'; +import 'quick_search_email_interactor_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final threadRepository = MockThreadRepository(); + final quickSearchEmailInteractor = QuickSearchEmailInteractor(threadRepository); + + group('quick search email interactor test:', () { + test( + 'should return list of presentation emails ' + 'when threadRepository.searchEmails returns list of emails', + () async { + // arrange + final searchEmail = SearchEmail( + id: EmailId(Id('someId')), + searchSnippetSubject: 'searchSnippetSubject', + searchSnippetPreview: 'searchSnippetPreview', + ); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenAnswer((_) async => [searchEmail]); + + // act + final result = await quickSearchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + equals(Right(QuickSearchEmailSuccess([searchEmail.toPresentationEmail( + searchSnippetSubject: searchEmail.searchSnippetSubject, + searchSnippetPreview: searchEmail.searchSnippetPreview, + )]))), + ); + }); + + test( + 'should return failure ' + 'when threadRepository.searchEmails throws exception', + () async { + // arrange + final exception = Exception(); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenThrow(exception); + + // act + final result = await quickSearchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + equals(Left(QuickSearchEmailFailure(exception))), + ); + }); + }); +} \ No newline at end of file diff --git a/test/features/search/email/domain/usecases/refresh_changes_search_email_interactor_test.dart b/test/features/search/email/domain/usecases/refresh_changes_search_email_interactor_test.dart new file mode 100644 index 0000000000..01d351a026 --- /dev/null +++ b/test/features/search/email/domain/usecases/refresh_changes_search_email_interactor_test.dart @@ -0,0 +1,98 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/extensions/email_extension.dart'; +import 'package:tmail_ui_user/features/search/email/domain/state/refresh_changes_search_email_state.dart'; +import 'package:tmail_ui_user/features/search/email/domain/usecases/refresh_changes_search_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; + +import '../../../../../fixtures/account_fixtures.dart'; +import '../../../../../fixtures/session_fixtures.dart'; +import 'refresh_changes_search_email_interactor_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final threadRepository = MockThreadRepository(); + final refreshChangesSearchEmailInteractor = RefreshChangesSearchEmailInteractor( + threadRepository); + + group('refresh changes search email interactor test:', () { + test( + 'should return list of presentation emails ' + 'when threadRepository.searchEmails returns list of emails', + () { + // arrange + final searchEmail = SearchEmail( + searchSnippetSubject: 'searchSnippetSubject', + searchSnippetPreview: 'searchSnippetPreview', + ); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenAnswer((_) async => [searchEmail]); + + // act + final result = refreshChangesSearchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(RefreshingChangeSearchEmailState()), + Right(RefreshChangesSearchEmailSuccess([searchEmail.toPresentationEmail( + searchSnippetSubject: searchEmail.searchSnippetSubject, + searchSnippetPreview: searchEmail.searchSnippetPreview, + )])), + ]) + ); + }); + + test( + 'should return failure ' + 'when threadRepository.searchEmails throw exception', + () { + // arrange + final exception = Exception(); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenThrow(exception); + + // act + final result = refreshChangesSearchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(RefreshingChangeSearchEmailState()), + Left(RefreshChangesSearchEmailFailure(exception)), + ]) + ); + }, + ); + }); +} \ No newline at end of file diff --git a/test/features/thread/domain/usecases/search_email_interactor_test.dart b/test/features/thread/domain/usecases/search_email_interactor_test.dart new file mode 100644 index 0000000000..507f2b8d3c --- /dev/null +++ b/test/features/thread/domain/usecases/search_email_interactor_test.dart @@ -0,0 +1,135 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; + +import '../../../../fixtures/account_fixtures.dart'; +import '../../../../fixtures/session_fixtures.dart'; +import 'search_email_interactor_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final threadRepository = MockThreadRepository(); + final searchEmailInteractor = SearchEmailInteractor(threadRepository); + + group('search email interactor test:', () { + test( + 'should return list of presentation emails ' + 'when threadRepository.searchEmails returns list of emails with search snippets', + () { + // arrange + final searchEmail = SearchEmail( + searchSnippetSubject: 'searchSnippetSubject', + searchSnippetPreview: 'searchSnippetPreview', + ); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenAnswer((_) async => [searchEmail]); + + // act + final result = searchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(SearchingState()), + Right(SearchEmailSuccess( + [searchEmail.toPresentationEmail( + searchSnippetSubject: searchEmail.searchSnippetSubject, + searchSnippetPreview: searchEmail.searchSnippetPreview, + )], + )) + ]), + ); + }); + + test( + 'should return list of presentation emails ' + 'when threadRepository.searchEmails returns list of emails without search snippets', + () { + // arrange + final searchEmail = SearchEmail( + searchSnippetSubject: null, + searchSnippetPreview: null, + ); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenAnswer((_) async => [searchEmail]); + + // act + final result = searchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(SearchingState()), + Right(SearchEmailSuccess([searchEmail.toPresentationEmail()])) + ]), + ); + }); + + test( + 'should return Failure when threadRepository.searchEmails returns Failure', + () { + // arrange + final exception = Exception(); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenThrow(exception); + + // act + final result = searchEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(SearchingState()), + Left(SearchEmailFailure(exception)), + ]), + ); + }, + ); + }); +} \ No newline at end of file diff --git a/test/features/thread/domain/usecases/search_more_email_interactor_test.dart b/test/features/thread/domain/usecases/search_more_email_interactor_test.dart new file mode 100644 index 0000000000..976c675c26 --- /dev/null +++ b/test/features/thread/domain/usecases/search_more_email_interactor_test.dart @@ -0,0 +1,100 @@ +import 'package:dartz/dartz.dart'; +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_filter_condition.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_email.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/search_more_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; + +import '../../../../fixtures/account_fixtures.dart'; +import '../../../../fixtures/session_fixtures.dart'; +import 'search_more_email_interactor_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + final threadRepository = MockThreadRepository(); + final searchMoreEmailInteractor = SearchMoreEmailInteractor(threadRepository); + group('search more email interactor test:', () { + test( + 'should return list of presentation emails ' + 'when threadRepository.searchEmails returns list of emails', + () { + // arrange + final searchEmail = SearchEmail( + id: EmailId(Id('someId')), + searchSnippetSubject: 'searchSnippetSubject', + searchSnippetPreview: 'searchSnippetPreview', + ); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenAnswer((_) async => [searchEmail]); + + // act + final result = searchMoreEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(SearchingMoreState()), + Right(SearchMoreEmailSuccess( + [searchEmail.toPresentationEmail( + searchSnippetSubject: searchEmail.searchSnippetSubject, + searchSnippetPreview: searchEmail.searchSnippetPreview, + )] + )), + ]), + ); + }); + + test( + 'should return failure ' + 'when threadRepository.searchEmails throw exception', + () { + // arrange + final exception = Exception(); + when( + threadRepository.searchEmails( + any, + any, + limit: anyNamed('limit'), + sort: anyNamed('sort'), + filter: anyNamed('filter'), + properties: anyNamed('properties'), + ), + ).thenThrow(exception); + + // act + final result = searchMoreEmailInteractor.execute( + SessionFixtures.aliceSession, + AccountFixtures.aliceAccountId, + filter: EmailFilterCondition(text: 'test'), + ); + + // assert + expect( + result, + emitsInOrder([ + Right(SearchingMoreState()), + Left(SearchMoreEmailFailure(exception)), + ]), + ); + }); + }); +} \ No newline at end of file