TF-3804 Thread Detail Load selected email in parallel with thread metadata
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
|
||||
import 'package:jmap_dart_client/jmap/core/state.dart' as jmap;
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/base/action/ui_action.dart';
|
||||
@@ -58,4 +59,23 @@ class RefreshThreadDetailAction extends EmailUIAction {
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailChangeResponse];
|
||||
}
|
||||
|
||||
class DisposePreviousExpandedEmailAction extends EmailUIAction {
|
||||
DisposePreviousExpandedEmailAction(this.emailId);
|
||||
|
||||
final EmailId emailId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailId];
|
||||
}
|
||||
|
||||
class UnsubscribeFromThreadAction extends EmailUIAction {
|
||||
UnsubscribeFromThreadAction(this.emailId, this.listUnsubscribe);
|
||||
|
||||
final EmailId emailId;
|
||||
final String listUnsubscribe;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailId, listUnsubscribe];
|
||||
}
|
||||
@@ -123,6 +123,7 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new
|
||||
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
|
||||
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart';
|
||||
@@ -397,6 +398,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
action.emailActionType,
|
||||
action.presentationEmail,
|
||||
);
|
||||
} else if (action is DisposePreviousExpandedEmailAction) {
|
||||
if (_currentEmailId == null || _currentEmailId != action.emailId) return;
|
||||
for (var worker in obxListeners) {
|
||||
worker.dispose();
|
||||
}
|
||||
Get.delete<SingleEmailController>(tag: action.emailId.id.value);
|
||||
} else if (action is CloseEmailInThreadDetailAction) {
|
||||
if (_currentEmailId == null) return;
|
||||
closeEmailView(context: currentContext);
|
||||
@@ -404,6 +411,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
worker.dispose();
|
||||
}
|
||||
Get.delete<SingleEmailController>(tag: _currentEmailId!.id.value);
|
||||
} else if (action is UnsubscribeFromThreadAction) {
|
||||
if (_currentEmailId == null || action.emailId != _currentEmailId) return;
|
||||
_handleUnsubscribe(action.listUnsubscribe);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -662,6 +672,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
emailUnsubscribe.value = null;
|
||||
}
|
||||
}
|
||||
if (currentEmail?.threadId != null &&
|
||||
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id) {
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
LoadThreadDetailAfterSelectedEmailAction(
|
||||
currentEmail!.threadId!,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _getEmailContentSuccess(GetEmailContentSuccess success) {
|
||||
@@ -727,6 +745,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
if ((_threadDetailController?.emailIdsPresentation.keys.length ?? 0) > 1 == true) {
|
||||
_jumpScrollViewToTopOfEmail();
|
||||
}
|
||||
if (currentEmail?.threadId != null &&
|
||||
currentEmail?.id == mailboxDashBoardController.selectedEmail.value?.id) {
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
LoadThreadDetailAfterSelectedEmailAction(
|
||||
currentEmail!.threadId!,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _jumpScrollViewToTopOfEmail() {
|
||||
|
||||
@@ -24,6 +24,10 @@ class GetEmailsByIdsSuccess extends UIState {
|
||||
List<Object?> get props => [presentationEmails, updateCurrentThreadDetail];
|
||||
}
|
||||
|
||||
class PreloadEmailsByIdsSuccess extends GetEmailsByIdsSuccess {
|
||||
PreloadEmailsByIdsSuccess(super.presentationEmails);
|
||||
}
|
||||
|
||||
class GetEmailsByIdsFailure extends FeatureFailure {
|
||||
GetEmailsByIdsFailure({
|
||||
super.exception,
|
||||
|
||||
@@ -21,6 +21,10 @@ class GetThreadByIdSuccess extends UIState {
|
||||
List<Object> get props => [emailIds, updateCurrentThreadDetail];
|
||||
}
|
||||
|
||||
class PreloadEmailIdsInThreadSuccess extends GetThreadByIdSuccess {
|
||||
PreloadEmailIdsInThreadSuccess(super.emailIds);
|
||||
}
|
||||
|
||||
class GetThreadByIdFailure extends FeatureFailure {
|
||||
GetThreadByIdFailure({
|
||||
super.exception,
|
||||
|
||||
@@ -38,4 +38,13 @@ class EmailMovedAction extends ThreadDetailUIAction {
|
||||
|
||||
@override
|
||||
List<Object?> get props => [emailId, originalMailboxId, targetMailboxId];
|
||||
}
|
||||
|
||||
class LoadThreadDetailAfterSelectedEmailAction extends ThreadDetailUIAction {
|
||||
LoadThreadDetailAfterSelectedEmailAction(this.threadId);
|
||||
|
||||
final ThreadId threadId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [threadId];
|
||||
}
|
||||
+24
-26
@@ -1,4 +1,3 @@
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
@@ -6,31 +5,30 @@ extension HandleGetEmailIdsByThreadIdSuccess on ThreadDetailController {
|
||||
void handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess success,
|
||||
) {
|
||||
final newEmailsInThreadDetail = <EmailId>[];
|
||||
if (success.emailIds.isNotEmpty) {
|
||||
if (success.updateCurrentThreadDetail) {
|
||||
newEmailsInThreadDetail.addAll(success
|
||||
.emailIds
|
||||
.where(
|
||||
(emailId) => !emailIdsPresentation.keys.contains(emailId),
|
||||
)
|
||||
);
|
||||
emailIdsPresentation
|
||||
..removeWhere(
|
||||
(key, value) => !success.emailIds.contains(key),
|
||||
)
|
||||
..addAll(Map.fromEntries(
|
||||
newEmailsInThreadDetail.map((emailId) => MapEntry(emailId, null)),
|
||||
));
|
||||
} else {
|
||||
emailIdsPresentation.value = Map.fromEntries(success.emailIds.map(
|
||||
(emailId) => MapEntry(emailId, null),
|
||||
));
|
||||
}
|
||||
} else if (mailboxDashBoardController.selectedEmail.value?.id != null) {
|
||||
emailIdsPresentation.value = {
|
||||
mailboxDashBoardController.selectedEmail.value!.id!: null,
|
||||
};
|
||||
if (success.emailIds.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final allEmailIds = success.emailIds;
|
||||
if (success.updateCurrentThreadDetail) {
|
||||
final newEmailIds = allEmailIds.where(
|
||||
(emailId) => !emailIdsPresentation.keys.contains(emailId),
|
||||
);
|
||||
emailIdsPresentation
|
||||
..removeWhere((key, _) => !allEmailIds.contains(key))
|
||||
..addEntries(
|
||||
newEmailIds.map((emailId) => MapEntry(emailId, null)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
|
||||
final selectedEmailId = selectedEmail?.id;
|
||||
emailIdsPresentation.value = {
|
||||
for (final id in allEmailIds)
|
||||
id: id == selectedEmailId
|
||||
? emailIdsPresentation[id] ?? selectedEmail
|
||||
: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -38,7 +38,7 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLoadMore) return;
|
||||
if (_skipScrollJump(isLoadMore)) return;
|
||||
|
||||
final currentExpandedEmailIndex = currentExpandedEmailId.value == null
|
||||
? -1
|
||||
@@ -72,4 +72,7 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool _skipScrollJump(bool isLoadMore) =>
|
||||
!isLoadMore || emailIdsPresentation.length == 1;
|
||||
}
|
||||
+60
-26
@@ -1,9 +1,18 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:model/extensions/email_extension.dart';
|
||||
import 'package:model/extensions/keyword_identifier_extension.dart';
|
||||
import 'package:model/extensions/list_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
void handleRefreshThreadDetailAction(
|
||||
@@ -30,33 +39,58 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
}
|
||||
|
||||
final currentThreadId = mailboxDashBoardController.selectedEmail.value?.threadId;
|
||||
final threadIdsEmailCreated = action
|
||||
.emailChangeResponse
|
||||
.created
|
||||
?.listThreadIds ?? [];
|
||||
final threadIdsEmailUpdated = action
|
||||
.emailChangeResponse
|
||||
.updated
|
||||
?.listThreadIds ?? [];
|
||||
final updatedThreadIds = threadIdsEmailCreated + threadIdsEmailUpdated;
|
||||
final destroyedEmailIds = action.emailChangeResponse.destroyed ?? [];
|
||||
if (currentThreadId == null) return;
|
||||
final emailIdsDestroyed = Set<EmailId>.from(
|
||||
action.emailChangeResponse.destroyed ?? const []);
|
||||
|
||||
if (session != null &&
|
||||
accountId != null &&
|
||||
sentMailboxId != null &&
|
||||
ownEmailAddress != null &&
|
||||
currentThreadId != null && (
|
||||
updatedThreadIds.contains(currentThreadId) ||
|
||||
emailIdsPresentation.keys.any(destroyedEmailIds.contains))) {
|
||||
consumeState(getThreadByIdInteractor.execute(
|
||||
currentThreadId,
|
||||
session!,
|
||||
accountId!,
|
||||
sentMailboxId!,
|
||||
ownEmailAddress!,
|
||||
updateCurrentThreadDetail: true,
|
||||
selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id,
|
||||
));
|
||||
final emailsCreated = action.emailChangeResponse.created
|
||||
?.where((email) => email.threadId == currentThreadId)
|
||||
.map((email) => email.toPresentationEmail().copyWith(
|
||||
emailInThreadStatus: EmailInThreadStatus.collapsed,
|
||||
))
|
||||
.toList(growable: false) ?? const [];
|
||||
|
||||
final emailsUpdated = action.emailChangeResponse.updated
|
||||
?.where((email) => email.threadId == currentThreadId)
|
||||
.map((email) => email.toPresentationEmail())
|
||||
.toList(growable: false) ?? const [];
|
||||
|
||||
final afterRefreshedEmailIds = ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: emailIdsPresentation.keys.toList(),
|
||||
created: emailsCreated.map((email) => email.id).whereNotNull().toList(),
|
||||
destroyed: emailIdsDestroyed.toList(),
|
||||
);
|
||||
final afterRefreshedEmails = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: emailIdsPresentation.values.whereNotNull().toList(),
|
||||
created: emailsCreated,
|
||||
updated: emailsUpdated,
|
||||
destroyed: emailIdsDestroyed.toList(),
|
||||
);
|
||||
|
||||
if (afterRefreshedEmailIds.isEmpty) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
return;
|
||||
}
|
||||
|
||||
consumeState(Stream.value(Right(GetThreadByIdSuccess(
|
||||
afterRefreshedEmailIds,
|
||||
updateCurrentThreadDetail: true,
|
||||
))));
|
||||
consumeState(Stream.value(Right(GetEmailsByIdsSuccess(
|
||||
afterRefreshedEmails,
|
||||
updateCurrentThreadDetail: true,
|
||||
))));
|
||||
|
||||
final updatedExpandedEmail = afterRefreshedEmails.firstWhereOrNull(
|
||||
(email) => email.id == currentExpandedEmailId.value,
|
||||
);
|
||||
if (updatedExpandedEmail?.keywords?[KeyWordIdentifierExtension.unsubscribeMail] == true) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
UnsubscribeFromThreadAction(
|
||||
currentExpandedEmailId.value!,
|
||||
updatedExpandedEmail?.listUnsubscribe ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-25
@@ -9,34 +9,18 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_d
|
||||
|
||||
extension InitializeThreadDetailEmails on ThreadDetailController {
|
||||
void initializeThreadDetailEmails(GetThreadByIdSuccess success) {
|
||||
final threadDetailEnabled = isThreadDetailEnabled;
|
||||
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
|
||||
if (!threadDetailEnabled &&
|
||||
selectedEmail != null &&
|
||||
!success.updateCurrentThreadDetail) {
|
||||
consumeState(Stream.value(Right(GetEmailsByIdsSuccess([selectedEmail]))));
|
||||
return;
|
||||
}
|
||||
|
||||
final existingEmailIds = emailIdsPresentation.keys.toList();
|
||||
final selectedEmailId = mailboxDashBoardController.selectedEmail.value?.id;
|
||||
if (skipLoadThreadMetaData(
|
||||
selectedEmailId: selectedEmailId,
|
||||
updateCurrentThreadDetail: success.updateCurrentThreadDetail,
|
||||
)) return;
|
||||
|
||||
List<EmailId> emailIdsToLoadMetaData = [];
|
||||
if (success.updateCurrentThreadDetail) {
|
||||
final nonNullEmailIds = emailIdsPresentation.entries
|
||||
.where((entry) => entry.value != null)
|
||||
.map((entry) => entry.key)
|
||||
.toList();
|
||||
final newEmailIds = success.emailIds.where(
|
||||
(emailId) => !existingEmailIds.contains(emailId),
|
||||
);
|
||||
emailIdsToLoadMetaData = [...nonNullEmailIds, ...newEmailIds];
|
||||
} else {
|
||||
emailIdsToLoadMetaData = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
|
||||
existingEmailIds,
|
||||
selectedEmailId: selectedEmailId,
|
||||
);
|
||||
}
|
||||
emailIdsToLoadMetaData = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
|
||||
emailIdsPresentation.keys.toList(),
|
||||
selectedEmailId: selectedEmailId,
|
||||
);
|
||||
emailIdsToLoadMetaData.remove(selectedEmailId);
|
||||
|
||||
if (accountId == null || session == null) {
|
||||
consumeState(Stream.value(Left(GetEmailsByIdsFailure(
|
||||
@@ -45,6 +29,8 @@ extension InitializeThreadDetailEmails on ThreadDetailController {
|
||||
))));
|
||||
return;
|
||||
}
|
||||
if (_currentThreadOnlyContainsSelectedEmail(selectedEmailId)) return;
|
||||
|
||||
consumeState(getEmailsByIdsInteractor.execute(
|
||||
session!,
|
||||
accountId!,
|
||||
@@ -56,4 +42,19 @@ extension InitializeThreadDetailEmails on ThreadDetailController {
|
||||
updateCurrentThreadDetail: success.updateCurrentThreadDetail,
|
||||
));
|
||||
}
|
||||
|
||||
bool _currentThreadOnlyContainsSelectedEmail(EmailId? selectedEmailId) {
|
||||
return selectedEmailId != null &&
|
||||
emailIdsPresentation.length == 1 &&
|
||||
emailIdsPresentation.keys.contains(selectedEmailId);
|
||||
}
|
||||
|
||||
bool skipLoadThreadMetaData({
|
||||
EmailId? selectedEmailId,
|
||||
bool updateCurrentThreadDetail = false,
|
||||
}) {
|
||||
return selectedEmailId == null ||
|
||||
!isThreadDetailEnabled ||
|
||||
updateCurrentThreadDetail;
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController {
|
||||
void onSelectedEmailUpdated(
|
||||
PresentationEmail? selectedEmail,
|
||||
GetThreadByIdInteractor getThreadByIdInteractor,
|
||||
BuildContext? context,
|
||||
) {
|
||||
if (selectedEmail?.id == null) {
|
||||
closeThreadDetailAction(context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentExpandedEmailId.value != null) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
DisposePreviousExpandedEmailAction(
|
||||
currentExpandedEmailId.value!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_preloadSelectedEmail(selectedEmail!);
|
||||
}
|
||||
|
||||
void _preloadSelectedEmail(PresentationEmail selectedEmail) {
|
||||
consumeState(Stream.fromIterable([
|
||||
Right(PreloadEmailIdsInThreadSuccess([selectedEmail.id!])),
|
||||
Right(PreloadEmailsByIdsSuccess([selectedEmail])),
|
||||
]));
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_email_moved_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart';
|
||||
@@ -49,6 +48,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/hand
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_refresh_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_setting_status.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
@@ -146,32 +146,11 @@ class ThreadDetailController extends BaseController {
|
||||
});
|
||||
downloadProgressState.stream.listen(handleDownloadProgressState);
|
||||
ever(mailboxDashBoardController.selectedEmail, (presentationEmail) async {
|
||||
if (presentationEmail?.threadId == null) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isThreadDetailEnabled && presentationEmail?.id != null) {
|
||||
consumeState(Stream.value(Right(GetThreadByIdSuccess([
|
||||
presentationEmail!.id!,
|
||||
]))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (session != null &&
|
||||
accountId != null &&
|
||||
sentMailboxId != null &&
|
||||
ownEmailAddress != null) {
|
||||
scrollController = ScrollController();
|
||||
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
||||
presentationEmail!.threadId!,
|
||||
session!,
|
||||
accountId!,
|
||||
sentMailboxId!,
|
||||
ownEmailAddress!,
|
||||
selectedEmailId: presentationEmail.id,
|
||||
));
|
||||
}
|
||||
onSelectedEmailUpdated(
|
||||
presentationEmail,
|
||||
_getEmailIdsByThreadIdInteractor,
|
||||
currentContext,
|
||||
);
|
||||
});
|
||||
ever(mailboxDashBoardController.threadDetailUIAction, (action) {
|
||||
if (action is UpdatedEmailKeywordsAction) {
|
||||
@@ -191,6 +170,18 @@ class ThreadDetailController extends BaseController {
|
||||
consumeState(_getThreadDetailStatusInteractor.execute());
|
||||
} else if (action is EmailMovedAction) {
|
||||
handleEmailMovedAction(action);
|
||||
} else if (action is LoadThreadDetailAfterSelectedEmailAction) {
|
||||
if (_validateLoadThread(action)) {
|
||||
scrollController = ScrollController();
|
||||
consumeState(_getEmailIdsByThreadIdInteractor.execute(
|
||||
action.threadId,
|
||||
session!,
|
||||
accountId!,
|
||||
sentMailboxId!,
|
||||
ownEmailAddress!,
|
||||
selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id,
|
||||
));
|
||||
}
|
||||
}
|
||||
// Reset [threadDetailUIAction] to original value
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
@@ -208,6 +199,16 @@ class ThreadDetailController extends BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
bool _validateLoadThread(LoadThreadDetailAfterSelectedEmailAction action) {
|
||||
return mailboxDashBoardController.selectedEmail.value?.threadId != null &&
|
||||
action.threadId == mailboxDashBoardController.selectedEmail.value?.threadId &&
|
||||
session != null &&
|
||||
accountId != null &&
|
||||
sentMailboxId != null &&
|
||||
ownEmailAddress != null &&
|
||||
isThreadDetailEnabled;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
emailIdsPresentation.clear();
|
||||
scrollController?.dispose();
|
||||
|
||||
@@ -164,6 +164,7 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
|
||||
return Obx(() {
|
||||
if (showLoadingView(controller.viewState.value) &&
|
||||
controller.emailIdsPresentation.isEmpty &&
|
||||
controller.responsiveUtils.isTabletLarge(context)) {
|
||||
return controller.getThreadDetailLoadingView(
|
||||
isResponsiveDesktop: false,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
|
||||
class ThreadDetailPresentationUtils {
|
||||
const ThreadDetailPresentationUtils._();
|
||||
@@ -54,4 +56,38 @@ class ThreadDetailPresentationUtils {
|
||||
emailIds.length,
|
||||
);
|
||||
}
|
||||
|
||||
static List<EmailId> refreshEmailIds({
|
||||
required List<EmailId> original,
|
||||
required List<EmailId> created,
|
||||
required List<EmailId> destroyed,
|
||||
}) {
|
||||
return [
|
||||
...original.whereNot(destroyed.contains),
|
||||
...created,
|
||||
];
|
||||
}
|
||||
|
||||
static List<PresentationEmail> refreshPresentationEmails({
|
||||
required List<PresentationEmail> original,
|
||||
required List<PresentationEmail> created,
|
||||
required List<PresentationEmail> updated,
|
||||
required List<EmailId> destroyed,
|
||||
}) {
|
||||
return [
|
||||
...original
|
||||
.whereNot((email) => destroyed.contains(email.id))
|
||||
.map((email) {
|
||||
final updatedInOriginal = updated.firstWhereOrNull(
|
||||
(updatedEmail) => updatedEmail.id == email.id,
|
||||
);
|
||||
|
||||
return email.copyWith(
|
||||
keywords: updatedInOriginal?.keywords,
|
||||
mailboxIds: updatedInOriginal?.mailboxIds,
|
||||
);
|
||||
}),
|
||||
...created,
|
||||
];
|
||||
}
|
||||
}
|
||||
+140
-39
@@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
@@ -18,55 +19,155 @@ import 'handle_get_email_ids_by_thread_id_success_test.mocks.dart';
|
||||
])
|
||||
void main() {
|
||||
final threadDetailController = MockThreadDetailController();
|
||||
final mailboxDashBoardController = MockMailboxDashBoardController();
|
||||
group('handle get email ids by thread id success test:', () {
|
||||
test(
|
||||
'should assign emailIds with result from success '
|
||||
'when result from success is not empty',
|
||||
'should not change emailIdsPresentation '
|
||||
'when GetThreadByIdSuccess.success.emailIds is empty',
|
||||
() {
|
||||
// arrange
|
||||
final success = GetThreadByIdSuccess([
|
||||
EmailId(Id('1')),
|
||||
EmailId(Id('2')),
|
||||
]);
|
||||
final mailboxDashBoardController = MockMailboxDashBoardController();
|
||||
when(threadDetailController.emailIdsPresentation).thenReturn(
|
||||
<EmailId, PresentationEmail?>{}.obs,
|
||||
);
|
||||
when(mailboxDashBoardController.selectedEmail).thenReturn(
|
||||
Rxn(PresentationEmail(id: EmailId(Id('1')))),
|
||||
);
|
||||
when(threadDetailController.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
|
||||
final initialEmailIdsPresentation = {
|
||||
EmailId(Id('1')): PresentationEmail(),
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(success);
|
||||
|
||||
// assert
|
||||
expect(threadDetailController.emailIdsPresentation.keys, success.emailIds);
|
||||
});
|
||||
|
||||
test(
|
||||
'should assign emailIds with result from mailbox dashboard controller '
|
||||
'when result from success is empty '
|
||||
'and mailbox dashboard controller selected email is not null',
|
||||
() {
|
||||
// arrange
|
||||
final mailboxDashBoardController = MockMailboxDashBoardController();
|
||||
final success = GetThreadByIdSuccess([]);
|
||||
when(mailboxDashBoardController.selectedEmail).thenReturn(
|
||||
Rxn(PresentationEmail(id: EmailId(Id('1')))),
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess([]),
|
||||
);
|
||||
when(threadDetailController.emailIdsPresentation).thenReturn(
|
||||
<EmailId, PresentationEmail?>{}.obs,
|
||||
);
|
||||
when(threadDetailController.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(success);
|
||||
|
||||
// assert
|
||||
expect(
|
||||
threadDetailController.emailIdsPresentation.keys,
|
||||
[mailboxDashBoardController.selectedEmail.value!.id!],
|
||||
threadDetailController.emailIdsPresentation,
|
||||
initialEmailIdsPresentation,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'should remove email id from emailIdsPresentation '
|
||||
'which is not in GetThreadByIdSuccess.success.emailIds '
|
||||
'and add new email ids from GetThreadByIdSuccess.success.emailIds '
|
||||
'when GetThreadByIdSuccess.success.emailIds is not empty '
|
||||
'and updateCurrentThreadDetail is true',
|
||||
() {
|
||||
// arrange
|
||||
final initialEmailIdsPresentation = {
|
||||
EmailId(Id('1')): PresentationEmail(),
|
||||
EmailId(Id('2')): null,
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess(
|
||||
[
|
||||
EmailId(Id('1')),
|
||||
EmailId(Id('3')),
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
updateCurrentThreadDetail: true,
|
||||
),
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(
|
||||
threadDetailController.emailIdsPresentation,
|
||||
{
|
||||
EmailId(Id('1')): PresentationEmail(),
|
||||
EmailId(Id('3')): null,
|
||||
EmailId(Id('4')): null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'should add all email ids from GetThreadByIdSuccess.success.emailIds '
|
||||
'except selected email id '
|
||||
'and keep selected email id in emailIdsPresentation '
|
||||
'when GetThreadByIdSuccess.success.emailIds is not empty '
|
||||
'and updateCurrentThreadDetail is false '
|
||||
'and selected email is available in emailIdsPresentation',
|
||||
() {
|
||||
// arrange
|
||||
final existedSelectedEmailId = PresentationEmail(
|
||||
id: EmailId(Id('1')),
|
||||
emailInThreadStatus: EmailInThreadStatus.expanded,
|
||||
);
|
||||
final initialEmailIdsPresentation = <EmailId, PresentationEmail?>{
|
||||
EmailId(Id('1')): existedSelectedEmailId,
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: EmailId(Id('1')))));
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess(
|
||||
[
|
||||
EmailId(Id('1')),
|
||||
EmailId(Id('3')),
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
updateCurrentThreadDetail: false,
|
||||
),
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(
|
||||
threadDetailController.emailIdsPresentation,
|
||||
{
|
||||
EmailId(Id('1')): existedSelectedEmailId,
|
||||
EmailId(Id('3')): null,
|
||||
EmailId(Id('4')): null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'should add all email ids from GetThreadByIdSuccess.success.emailIds '
|
||||
'except selected email id '
|
||||
'and take selected email from mailbox dashboard controller '
|
||||
'when GetThreadByIdSuccess.success.emailIds is not empty '
|
||||
'and updateCurrentThreadDetail is false '
|
||||
'and selected email is not available in emailIdsPresentation',
|
||||
() {
|
||||
// arrange
|
||||
final selectedEmailId = PresentationEmail(id: EmailId(Id('1')));
|
||||
final initialEmailIdsPresentation = <EmailId, PresentationEmail?>{
|
||||
EmailId(Id('1')): null,
|
||||
};
|
||||
when(threadDetailController.emailIdsPresentation)
|
||||
.thenReturn(initialEmailIdsPresentation.obs);
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: EmailId(Id('1')))));
|
||||
|
||||
// act
|
||||
threadDetailController.handleGetEmailIdsByThreadIdSuccess(
|
||||
GetThreadByIdSuccess(
|
||||
[
|
||||
EmailId(Id('1')),
|
||||
EmailId(Id('3')),
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
updateCurrentThreadDetail: false,
|
||||
),
|
||||
);
|
||||
|
||||
// assert
|
||||
expect(
|
||||
threadDetailController.emailIdsPresentation,
|
||||
{
|
||||
EmailId(Id('1')): selectedEmailId,
|
||||
EmailId(Id('3')): null,
|
||||
EmailId(Id('4')): null,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:get/get_rx/get_rx.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
import 'handle_get_thread_by_id_failure_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([MockSpec<ThreadDetailController>()])
|
||||
@GenerateNiceMocks([MockSpec<MailboxDashBoardController>()])
|
||||
void main() {
|
||||
late MockThreadDetailController controller;
|
||||
late MockMailboxDashBoardController mailboxDashBoardController;
|
||||
|
||||
setUp(() {
|
||||
controller = MockThreadDetailController();
|
||||
mailboxDashBoardController = MockMailboxDashBoardController();
|
||||
});
|
||||
|
||||
group('handle get thread by id failure test', () {
|
||||
test(
|
||||
'should not call consumeState '
|
||||
'when updateCurrentThreadDetail is true',
|
||||
() {
|
||||
// arrange
|
||||
final failure = GetThreadByIdFailure(updateCurrentThreadDetail: true);
|
||||
|
||||
// act
|
||||
controller.handleGetThreadByIdFailure(failure);
|
||||
|
||||
// assert
|
||||
verifyNever(controller.consumeState(any));
|
||||
});
|
||||
|
||||
test(
|
||||
'should consumeState GetEmailsByIdsSuccess with selected email '
|
||||
'when updateCurrentThreadDetail is false '
|
||||
'and selected email is not null',
|
||||
() {
|
||||
// arrange
|
||||
final failure = GetThreadByIdFailure(updateCurrentThreadDetail: false);
|
||||
final selectedEmail = PresentationEmail(id: EmailId(Id('1')));
|
||||
when(controller.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(selectedEmail));
|
||||
|
||||
// act
|
||||
controller.handleGetThreadByIdFailure(failure);
|
||||
|
||||
// assert
|
||||
final streamsConsumed = (verify(
|
||||
controller.consumeState(captureAny),
|
||||
).captured as List<Object?>).first as Stream?;
|
||||
expect(
|
||||
streamsConsumed,
|
||||
emitsInOrder([
|
||||
Right(GetEmailsByIdsSuccess([selectedEmail])),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'should show retry toast '
|
||||
'when updateCurrentThreadDetail is false '
|
||||
'and selected email is null',
|
||||
() {
|
||||
// arrange
|
||||
final failure = GetThreadByIdFailure(updateCurrentThreadDetail: false);
|
||||
when(controller.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());
|
||||
|
||||
// act
|
||||
controller.handleGetThreadByIdFailure(failure);
|
||||
|
||||
// assert
|
||||
verify(controller.showRetryToast(failure));
|
||||
verifyNever(controller.consumeState(any));
|
||||
});
|
||||
});
|
||||
}
|
||||
+85
-274
@@ -1,24 +1,20 @@
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:get/get.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/mailbox/mailbox.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/session_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_refresh_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
import '../../../../fixtures/account_fixtures.dart';
|
||||
import '../../../../fixtures/session_fixtures.dart';
|
||||
import 'handle_refresh_thread_detail_action_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([
|
||||
@@ -31,9 +27,6 @@ void main() {
|
||||
late MockMailboxDashBoardController mailboxDashBoardController;
|
||||
late MockGetThreadByIdInteractor getThreadByIdInteractor;
|
||||
|
||||
final sentMailboxId = MailboxId(Id('sent'));
|
||||
final ownEmailAddress = SessionFixtures.aliceSession.getOwnEmailAddress();
|
||||
|
||||
setUp(() {
|
||||
threadDetailController = MockThreadDetailController();
|
||||
mailboxDashBoardController = MockMailboxDashBoardController();
|
||||
@@ -42,275 +35,93 @@ void main() {
|
||||
|
||||
group('handle refresh thread detail action test', () {
|
||||
test(
|
||||
'should call getThreadByIdInteractor.execute '
|
||||
'when refreshThreadDetailAction is called '
|
||||
'and list email created contains currentThreadId '
|
||||
'and isThreadDetailEnabled is true',
|
||||
() {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final threadId = ThreadId(Id('some-id'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
created: [Email(id: emailId, threadId: threadId)],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailId, threadId: threadId)));
|
||||
when(threadDetailController.session)
|
||||
.thenReturn(SessionFixtures.aliceSession);
|
||||
when(threadDetailController.accountId)
|
||||
.thenReturn(AccountFixtures.aliceAccountId);
|
||||
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(true);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
verify(getThreadByIdInteractor.execute(
|
||||
threadId,
|
||||
SessionFixtures.aliceSession,
|
||||
AccountFixtures.aliceAccountId,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
updateCurrentThreadDetail: true,
|
||||
selectedEmailId: emailId,
|
||||
)).called(1);
|
||||
});
|
||||
'Should consume GetThreadByIdSuccess when isThreadDetailEnabled is false',
|
||||
() async {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
updated: [Email(id: emailId)],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailId)));
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(false);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
final capturedStates = verify(
|
||||
threadDetailController.consumeState(captureAny)
|
||||
).captured;
|
||||
|
||||
expect(capturedStates, hasLength(1));
|
||||
final stateStream = capturedStates.first as Stream;
|
||||
final state = await stateStream.last;
|
||||
expect(state, isA<Right>());
|
||||
expect((state as Right).value, isA<GetThreadByIdSuccess>());
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should call getThreadByIdInteractor.execute '
|
||||
'when refreshThreadDetailAction is called '
|
||||
'and list email updated contains currentThreadId '
|
||||
'and isThreadDetailEnabled is true',
|
||||
() {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final threadId = ThreadId(Id('some-id'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
updated: [Email(id: emailId, threadId: threadId)],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailId, threadId: threadId)));
|
||||
when(threadDetailController.session)
|
||||
.thenReturn(SessionFixtures.aliceSession);
|
||||
when(threadDetailController.accountId)
|
||||
.thenReturn(AccountFixtures.aliceAccountId);
|
||||
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(true);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
verify(getThreadByIdInteractor.execute(
|
||||
threadId,
|
||||
SessionFixtures.aliceSession,
|
||||
AccountFixtures.aliceAccountId,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
updateCurrentThreadDetail: true,
|
||||
selectedEmailId: emailId,
|
||||
)).called(1);
|
||||
});
|
||||
'Should consume both success states when isThreadDetailEnabled is true with mixed email changes',
|
||||
() async {
|
||||
// arrange
|
||||
final currentThreadId = ThreadId(Id('current-thread'));
|
||||
final createdEmailId = EmailId(Id('created-email'));
|
||||
final updatedEmailId = EmailId(Id('updated-email'));
|
||||
final destroyedEmailId = EmailId(Id('destroyed-email'));
|
||||
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
created: [Email(id: createdEmailId, threadId: currentThreadId)],
|
||||
updated: [Email(id: updatedEmailId, threadId: currentThreadId)],
|
||||
destroyed: [destroyedEmailId],
|
||||
));
|
||||
|
||||
test(
|
||||
'should call getThreadByIdInteractor.execute '
|
||||
'when refreshThreadDetailAction is called '
|
||||
'and list email destroyed contains any of emailId in current thread '
|
||||
'and isThreadDetailEnabled is true',
|
||||
() {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final threadId = ThreadId(Id('some-id'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
destroyed: [emailId],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailId, threadId: threadId)));
|
||||
when(threadDetailController.session)
|
||||
.thenReturn(SessionFixtures.aliceSession);
|
||||
when(threadDetailController.accountId)
|
||||
.thenReturn(AccountFixtures.aliceAccountId);
|
||||
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||
when(threadDetailController.emailIdsPresentation).thenReturn({
|
||||
emailId: null
|
||||
}.obs);
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(true);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
verify(getThreadByIdInteractor.execute(
|
||||
threadId,
|
||||
SessionFixtures.aliceSession,
|
||||
AccountFixtures.aliceAccountId,
|
||||
sentMailboxId,
|
||||
ownEmailAddress,
|
||||
updateCurrentThreadDetail: true,
|
||||
selectedEmailId: emailId,
|
||||
)).called(1);
|
||||
});
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(threadId: currentThreadId)));
|
||||
when(threadDetailController.emailIdsPresentation).thenReturn(RxMap<EmailId, PresentationEmail?>.of({
|
||||
destroyedEmailId: PresentationEmail(id: destroyedEmailId),
|
||||
updatedEmailId: PresentationEmail(id: updatedEmailId),
|
||||
}));
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(true);
|
||||
when(threadDetailController.currentExpandedEmailId).thenReturn(Rxn());
|
||||
|
||||
test(
|
||||
'should consume GetThreadByIdSuccess with selected email id '
|
||||
'when refreshThreadDetailAction is called '
|
||||
'and isThreadDetailEnabled is false '
|
||||
'and selected email id not null '
|
||||
'and list email updated contains selected email id',
|
||||
() async {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
updated: [Email(id: emailId)],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailId)));
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(false);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
verifyNever(getThreadByIdInteractor.execute(
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
updateCurrentThreadDetail: anyNamed('updateCurrentThreadDetail'),
|
||||
selectedEmailId: anyNamed('selectedEmailId'),
|
||||
));
|
||||
final args = verify(
|
||||
threadDetailController.consumeState(captureAny),
|
||||
).captured.first as Stream;
|
||||
final state = await args.last;
|
||||
expect(
|
||||
state,
|
||||
Right(GetThreadByIdSuccess(
|
||||
[emailId],
|
||||
updateCurrentThreadDetail: true,
|
||||
)),
|
||||
);
|
||||
});
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
test(
|
||||
'should not call getThreadByIdInteractor.execute '
|
||||
'and not consume GetThreadByIdSuccess with selected email id'
|
||||
'when refreshThreadDetailAction is called '
|
||||
'and list email created does not contain currentThreadId '
|
||||
'and list email updated does not contain currentThreadId '
|
||||
'and list email destroyed does not contain any of emailId in current thread '
|
||||
'and isThreadDetailEnabled is true',
|
||||
() async {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
created: [
|
||||
Email(id: EmailId(Id('2')), threadId: ThreadId(Id('some-id-2'))),
|
||||
],
|
||||
updated: [
|
||||
Email(id: EmailId(Id('3')), threadId: ThreadId(Id('some-id-3'))),
|
||||
],
|
||||
destroyed: [
|
||||
EmailId(Id('4')),
|
||||
],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail()));
|
||||
when(threadDetailController.session)
|
||||
.thenReturn(SessionFixtures.aliceSession);
|
||||
when(threadDetailController.accountId)
|
||||
.thenReturn(AccountFixtures.aliceAccountId);
|
||||
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||
when(threadDetailController.emailIdsPresentation).thenReturn({
|
||||
emailId: null,
|
||||
}.obs);
|
||||
when(threadDetailController.viewState).thenReturn(Rx(Right(UIState.idle)));
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(true);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
verifyNever(getThreadByIdInteractor.execute(
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
updateCurrentThreadDetail: anyNamed('updateCurrentThreadDetail'),
|
||||
selectedEmailId: anyNamed('selectedEmailId'),
|
||||
));
|
||||
verifyNever(threadDetailController.consumeState(any));
|
||||
});
|
||||
|
||||
test(
|
||||
'should not call getThreadByIdInteractor.execute '
|
||||
'and not consume GetThreadByIdSuccess with selected email id'
|
||||
'when refreshThreadDetailAction is called '
|
||||
'and isThreadDetailEnabled is false '
|
||||
'and updated email ids does not contain selected email id',
|
||||
() async {
|
||||
// arrange
|
||||
final emailId = EmailId(Id('1'));
|
||||
final action = RefreshThreadDetailAction(EmailChangeResponse(
|
||||
updated: [
|
||||
Email(id: EmailId(Id('3')), threadId: ThreadId(Id('some-id-3'))),
|
||||
],
|
||||
));
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashBoardController);
|
||||
when(mailboxDashBoardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailId)));
|
||||
when(threadDetailController.isThreadDetailEnabled).thenReturn(false);
|
||||
|
||||
// act
|
||||
threadDetailController.handleRefreshThreadDetailAction(
|
||||
action,
|
||||
getThreadByIdInteractor,
|
||||
);
|
||||
|
||||
// assert
|
||||
verifyNever(getThreadByIdInteractor.execute(
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
any,
|
||||
updateCurrentThreadDetail: anyNamed('updateCurrentThreadDetail'),
|
||||
selectedEmailId: anyNamed('selectedEmailId'),
|
||||
));
|
||||
verifyNever(threadDetailController.consumeState(any));
|
||||
});
|
||||
// assert
|
||||
final capturedStates = verify(
|
||||
threadDetailController.consumeState(captureAny)
|
||||
).captured;
|
||||
|
||||
expect(capturedStates, hasLength(2));
|
||||
|
||||
// Verify GetThreadByIdSuccess
|
||||
final firstState = await (capturedStates.first as Stream).last;
|
||||
expect(firstState, isA<Right>());
|
||||
final threadSuccess = (firstState as Right).value as GetThreadByIdSuccess;
|
||||
expect(threadSuccess.emailIds, containsAll([createdEmailId, updatedEmailId]));
|
||||
|
||||
// Verify GetEmailsByIdsSuccess
|
||||
final secondState = await (capturedStates[1] as Stream).last;
|
||||
expect(secondState, isA<Right>());
|
||||
final emailsSuccess = (secondState as Right).value as GetEmailsByIdsSuccess;
|
||||
expect(emailsSuccess.presentationEmails, hasLength(2));
|
||||
expect(emailsSuccess.presentationEmails.map((e) => e.id), containsAll([
|
||||
createdEmailId,
|
||||
updatedEmailId,
|
||||
]));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -33,7 +33,7 @@ void main() {
|
||||
mailboxDashboardController = MockMailboxDashBoardController();
|
||||
});
|
||||
|
||||
test('initializeThreadDetailEmails should call getEmailsByIdsInteractor with 3 elements', () async {
|
||||
test('initializeThreadDetailEmails should call getEmailsByIdsInteractor with 2 elements', () async {
|
||||
// Arrange
|
||||
final emailIds = [
|
||||
EmailId(Id('email1')),
|
||||
@@ -63,7 +63,7 @@ void main() {
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashboardController);
|
||||
when(mailboxDashboardController.selectedEmail)
|
||||
.thenReturn(Rxn(PresentationEmail()));
|
||||
.thenReturn(Rxn(PresentationEmail(id: emailIds[0])));
|
||||
|
||||
// Act
|
||||
threadDetailController.initializeThreadDetailEmails(
|
||||
@@ -78,7 +78,7 @@ void main() {
|
||||
properties: captureAnyNamed('properties'),
|
||||
)).captured;
|
||||
|
||||
expect((captured[2] as List<EmailId>).length, 3);
|
||||
expect((captured[2] as List<EmailId>).length, 2);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:get/get_rx/get_rx.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
import '../../../../fixtures/account_fixtures.dart';
|
||||
import '../../../../fixtures/session_fixtures.dart';
|
||||
import 'thread_detail_on_selected_email_updated_test.mocks.dart';
|
||||
|
||||
@GenerateNiceMocks([
|
||||
MockSpec<ThreadDetailController>(),
|
||||
MockSpec<GetThreadByIdInteractor>(),
|
||||
MockSpec<MailboxDashBoardController>(),
|
||||
])
|
||||
void main() {
|
||||
late MockThreadDetailController threadDetailController;
|
||||
late MockGetThreadByIdInteractor getThreadByIdInteractor;
|
||||
late MockMailboxDashBoardController mailboxDashboardController;
|
||||
|
||||
setUp(() {
|
||||
threadDetailController = MockThreadDetailController();
|
||||
getThreadByIdInteractor = MockGetThreadByIdInteractor();
|
||||
mailboxDashboardController = MockMailboxDashBoardController();
|
||||
when(threadDetailController.session)
|
||||
.thenReturn(SessionFixtures.aliceSession);
|
||||
when(threadDetailController.accountId)
|
||||
.thenReturn(AccountFixtures.aliceAccountId);
|
||||
when(threadDetailController.additionalProperties)
|
||||
.thenReturn(Properties.empty());
|
||||
when(threadDetailController.mailboxDashBoardController)
|
||||
.thenReturn(mailboxDashboardController);
|
||||
});
|
||||
|
||||
group('thread detail on selected email updated test:', () {
|
||||
test(
|
||||
'should reset thread detail controller '
|
||||
'when selected email is null',
|
||||
() {
|
||||
// act
|
||||
threadDetailController.onSelectedEmailUpdated(
|
||||
null,
|
||||
getThreadByIdInteractor,
|
||||
null,
|
||||
);
|
||||
|
||||
// assert
|
||||
verify(threadDetailController.reset()).called(1);
|
||||
});
|
||||
|
||||
test(
|
||||
'should not reset thread detail controller '
|
||||
'and consume PreloadEmailIdsInThreadSuccess and PreloadEmailsByIdsSuccess '
|
||||
'when selected email and its id is not null',
|
||||
() async {
|
||||
// arrange
|
||||
final selectedEmail = PresentationEmail(
|
||||
id: EmailId(Id('1')),
|
||||
threadId: ThreadId(Id('1')),
|
||||
);
|
||||
when(threadDetailController.currentExpandedEmailId).thenReturn(Rxn());
|
||||
|
||||
// act
|
||||
threadDetailController.onSelectedEmailUpdated(
|
||||
selectedEmail,
|
||||
getThreadByIdInteractor,
|
||||
null,
|
||||
);
|
||||
|
||||
// assert
|
||||
verifyNever(threadDetailController.reset());
|
||||
final streamsConsumed = (verify(
|
||||
threadDetailController.consumeState(captureAny),
|
||||
).captured as List<Object?>).first as Stream?;
|
||||
expect(
|
||||
streamsConsumed,
|
||||
emitsInOrder([
|
||||
Right(PreloadEmailIdsInThreadSuccess([selectedEmail.id!])),
|
||||
Right(PreloadEmailsByIdsSuccess([selectedEmail])),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
+170
@@ -1,6 +1,9 @@
|
||||
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/keyword_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||
|
||||
void main() {
|
||||
@@ -322,5 +325,172 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('refreshEmailIds', () {
|
||||
test('should return original emails when no created or destroyed', () {
|
||||
final original = [EmailId(Id('1')), EmailId(Id('2'))];
|
||||
expect(
|
||||
ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: original,
|
||||
created: [],
|
||||
destroyed: [],
|
||||
),
|
||||
original,
|
||||
);
|
||||
});
|
||||
|
||||
test('should remove destroyed emails from original', () {
|
||||
final original = [EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('3'))];
|
||||
final destroyed = [EmailId(Id('2'))];
|
||||
expect(
|
||||
ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: original,
|
||||
created: [],
|
||||
destroyed: destroyed,
|
||||
),
|
||||
[EmailId(Id('1')), EmailId(Id('3'))],
|
||||
);
|
||||
});
|
||||
|
||||
test('should add created emails to original', () {
|
||||
final original = [EmailId(Id('1'))];
|
||||
final created = [EmailId(Id('2')), EmailId(Id('3'))];
|
||||
expect(
|
||||
ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: original,
|
||||
created: created,
|
||||
destroyed: [],
|
||||
),
|
||||
[EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('3'))],
|
||||
);
|
||||
});
|
||||
|
||||
test('should combine created and destroyed operations', () {
|
||||
final original = [EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('3'))];
|
||||
final created = [EmailId(Id('4'))];
|
||||
final destroyed = [EmailId(Id('2'))];
|
||||
expect(
|
||||
ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: original,
|
||||
created: created,
|
||||
destroyed: destroyed,
|
||||
),
|
||||
[EmailId(Id('1')), EmailId(Id('3')), EmailId(Id('4'))],
|
||||
);
|
||||
});
|
||||
|
||||
test('should ignore destroyed emails not present in original', () {
|
||||
final original = [EmailId(Id('1'))];
|
||||
final destroyed = [EmailId(Id('99'))];
|
||||
expect(
|
||||
ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: original,
|
||||
created: [],
|
||||
destroyed: destroyed,
|
||||
),
|
||||
original,
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle duplicate created emails', () {
|
||||
final original = [EmailId(Id('1'))];
|
||||
final created = [EmailId(Id('2')), EmailId(Id('2'))];
|
||||
expect(
|
||||
ThreadDetailPresentationUtils.refreshEmailIds(
|
||||
original: original,
|
||||
created: created,
|
||||
destroyed: [],
|
||||
),
|
||||
[EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('2'))],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('refreshPresentationEmails', () {
|
||||
final email1 = PresentationEmail(id: EmailId(Id('1')), keywords: {}, mailboxIds: {});
|
||||
final email2 = PresentationEmail(id: EmailId(Id('2')), keywords: {}, mailboxIds: {});
|
||||
final email3 = PresentationEmail(id: EmailId(Id('3')), keywords: {}, mailboxIds: {});
|
||||
|
||||
final updatedEmail2 = PresentationEmail(
|
||||
id: EmailId(Id('2')),
|
||||
keywords: {KeyWordIdentifier('updated'): true},
|
||||
mailboxIds: {MailboxId(Id('mailbox')): true}
|
||||
);
|
||||
|
||||
test('should combine non-destroyed originals with updates and created emails', () {
|
||||
final result = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: [email1, email2],
|
||||
created: [email3],
|
||||
updated: [updatedEmail2],
|
||||
destroyed: [EmailId(Id('1'))],
|
||||
);
|
||||
|
||||
expect(result, [
|
||||
email2.copyWith(
|
||||
keywords: updatedEmail2.keywords,
|
||||
mailboxIds: updatedEmail2.mailboxIds,
|
||||
),
|
||||
email3,
|
||||
]);
|
||||
});
|
||||
|
||||
test('should handle empty destroyed and updated lists', () {
|
||||
final result = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: [email1],
|
||||
created: [email2],
|
||||
updated: [],
|
||||
destroyed: [],
|
||||
);
|
||||
|
||||
expect(result, [email1, email2]);
|
||||
});
|
||||
|
||||
test('should exclude all destroyed emails', () {
|
||||
final result = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: [email1, email2],
|
||||
created: [email3],
|
||||
updated: [],
|
||||
destroyed: [EmailId(Id('1')), EmailId(Id('2'))],
|
||||
);
|
||||
|
||||
expect(result, [email3]);
|
||||
});
|
||||
|
||||
test('should apply updates to remaining original emails', () {
|
||||
final result = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: [email1, email2],
|
||||
created: [],
|
||||
updated: [updatedEmail2],
|
||||
destroyed: [],
|
||||
);
|
||||
|
||||
expect(result, [
|
||||
email1,
|
||||
updatedEmail2,
|
||||
]);
|
||||
});
|
||||
|
||||
test('should handle empty original list', () {
|
||||
final result = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: [],
|
||||
created: [email1, email2],
|
||||
updated: [],
|
||||
destroyed: [],
|
||||
);
|
||||
|
||||
expect(result, [email1, email2]);
|
||||
});
|
||||
|
||||
test('should handle emails being both destroyed and updated', () {
|
||||
final result = ThreadDetailPresentationUtils.refreshPresentationEmails(
|
||||
original: [email1],
|
||||
created: [],
|
||||
updated: [email1.copyWith(keywords: {KeyWordIdentifier('test'): true})],
|
||||
destroyed: [EmailId(Id('1'))],
|
||||
);
|
||||
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user