From 4c1fd74a938cfae6bb3cc87f92db11d6303e13a3 Mon Sep 17 00:00:00 2001 From: DatDang Date: Fri, 1 Nov 2024 10:42:14 +0700 Subject: [PATCH] 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 | 90 ++++++++++++ ..._changes_search_email_interactor_test.dart | 98 +++++++++++++ .../search_email_interactor_test.dart | 135 ++++++++++++++++++ .../search_more_email_interactor_test.dart | 100 +++++++++++++ 12 files changed, 473 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..07e1bb5e23 --- /dev/null +++ b/test/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor_test.dart @@ -0,0 +1,90 @@ +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/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( + 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