diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 4be78937c..b33c7b298 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -778,6 +778,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin { return; } + final emailId = email.id; + if (emailId == null) { + mailboxDashBoardController.emitMoveToTrashFailure( + NotFoundEmailIdException(), + ); + return; + } + final moveActionRequest = emailActionReactor.buildMoveToTrashRequest( email, trashMailboxId: trashId, diff --git a/lib/features/mailbox/domain/exceptions/mailbox_exception.dart b/lib/features/mailbox/domain/exceptions/mailbox_exception.dart index 380b819c8..ee256b009 100644 --- a/lib/features/mailbox/domain/exceptions/mailbox_exception.dart +++ b/lib/features/mailbox/domain/exceptions/mailbox_exception.dart @@ -40,4 +40,11 @@ class NotFoundTrashMailboxException extends AppBaseException { @override String get exceptionName => 'NotFoundTrashMailboxException'; -} \ No newline at end of file +} + +class NotFoundEmailIdException extends AppBaseException { + NotFoundEmailIdException([super.message]); + + @override + String get exceptionName => 'NotFoundEmailIdException'; +} diff --git a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart index f55ab71f2..4f506515c 100644 --- a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart +++ b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart @@ -87,20 +87,20 @@ mixin MailboxWidgetMixin { return [ if (PlatformInfo.isWeb && mailbox.isSubscribedMailbox) MailboxActions.openInNewTab, - if (mailbox.myRights?.mayCreateChild == true) + if (mailbox.myRights?.mayCreateChild != false) MailboxActions.newSubfolder, if (mailbox.countUnReadEmailsAsString.isNotEmpty) MailboxActions.markAsRead, - if (mailbox.myRights?.mayRename == true) + if (mailbox.myRights?.mayRename != false) MailboxActions.rename, if (mailbox.isTeamMailboxes) if (mailbox.isSubscribedMailbox) MailboxActions.disableMailbox else MailboxActions.enableMailbox, - if (mailbox.isTrash && mailbox.myRights?.mayRemoveItems == true) + if (mailbox.isTrash && mailbox.myRights?.mayRemoveItems != false) MailboxActions.emptyTrash, - if (mailbox.myRights?.mayDelete == true) + if (mailbox.myRights?.mayDelete != false) MailboxActions.delete, ]; } diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 01f65d46f..10ebc47f2 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -1864,7 +1864,7 @@ class MailboxDashBoardController extends ReloadableController } if (CapabilityIdentifier.jmapMailboxClear.isSupported(session, accountId) && - trashFolder.isTrashTeamMailbox != true) { + !trashFolder.isFirstLevelTeamSystemFolder(mapMailboxById, PresentationMailbox.trashRole)) { clearMailbox( session, accountId, @@ -2406,8 +2406,7 @@ class MailboxDashBoardController extends ReloadableController PresentationMailbox? mailbox ) { return mailbox != null && - mailbox.isTrash && - mailbox.myRights?.mayRemoveItems == true && + (mailbox.isTrash || (mailbox.isTrashTeamMailbox && mailbox.myRights?.mayRemoveItems != false)) && mailbox.countTotalEmails > 0 && !searchController.isSearchActive() && responsiveUtils.isWebDesktop(context); @@ -2418,8 +2417,7 @@ class MailboxDashBoardController extends ReloadableController PresentationMailbox? mailbox ) { return mailbox != null && - mailbox.isTrash && - mailbox.myRights?.mayRemoveItems == true && + (mailbox.isTrash || (mailbox.isTrashTeamMailbox && mailbox.myRights?.mayRemoveItems != false)) && mailbox.countTotalEmails > 0 && !searchController.isSearchActive() && !responsiveUtils.isWebDesktop(context); diff --git a/lib/features/mailbox_dashboard/presentation/extensions/handle_action_type_for_email_selection.dart b/lib/features/mailbox_dashboard/presentation/extensions/handle_action_type_for_email_selection.dart index 67e0a8d81..eb7e5c9c8 100644 --- a/lib/features/mailbox_dashboard/presentation/extensions/handle_action_type_for_email_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/extensions/handle_action_type_for_email_selection.dart @@ -23,6 +23,17 @@ extension HandleActionTypeForEmailSelection on MailboxDashBoardController { EmailActionType actionType, {MailboxId? selectedMailboxId, String? destinationFolderPath} ) { + final isInSearchOrVirtualFolder = + searchController.isSearchEmailRunning || + selectedMailbox.value?.isVirtualFolder == true; + + // In search / virtual-folder mode the selected mailbox does not represent + // the emails' actual home namespace, so trash must be resolved per email. + if (actionType == EmailActionType.moveToTrash && isInSearchOrVirtualFolder) { + _moveEmailsToTrashAcrossNamespaces(emails); + return; + } + MailboxId? destinationMailboxId; if (actionType == EmailActionType.moveToMailbox) { @@ -58,8 +69,7 @@ extension HandleActionTypeForEmailSelection on MailboxDashBoardController { final mapEmailIdsByMailboxId = >{}; - if (searchController.isSearchEmailRunning || - selectedMailbox.value?.isVirtualFolder == true) { + if (isInSearchOrVirtualFolder) { for (final email in emails) { final mailboxId = email.firstMailboxIdAvailable; final emailId = email.id; @@ -115,4 +125,74 @@ extension HandleActionTypeForEmailSelection on MailboxDashBoardController { emailIdsWithReadStatus, ); } + + void _moveEmailsToTrashAcrossNamespaces(List emails) { + if (accountId.value == null || sessionCurrent == null) { + consumeState(Stream.value(Left(MoveMultipleEmailToMailboxFailure( + EmailActionType.moveToTrash, + MoveAction.moving, + ParametersIsNullException(), + )))); + return; + } + + // Group emails by resolved trash destination: trashId → (path, sourceId → [emailIds]) + final groups = > sourceToEmails})>{}; + + for (final email in emails) { + final sourceMailboxId = email.firstMailboxIdAvailable; + final emailId = email.id; + if (sourceMailboxId == null || emailId == null) continue; + + final sourceMailbox = mapMailboxById[sourceMailboxId]; + if (sourceMailbox == null) continue; + + final (:trashId, :trashPath) = getTrashMailboxIdAndPath(sourceMailbox); + if (trashId == null || sourceMailboxId == trashId) continue; + + groups + .putIfAbsent(trashId, () => (trashPath: trashPath, sourceToEmails: {})) + .sourceToEmails + .putIfAbsent(sourceMailboxId, () => []) + .add(emailId); + } + + if (groups.isEmpty) { + consumeState(Stream.value(Left(MoveMultipleEmailToMailboxFailure( + EmailActionType.moveToTrash, + MoveAction.moving, + ParametersIsNullException(), + )))); + return; + } + + final emailIdsWithReadStatus = Map.fromEntries( + emails + .where((e) => e.id != null) + .map((e) => MapEntry(e.id!, e.hasRead)), + ); + + for (final entry in groups.entries) { + final trashId = entry.key; + final resolvedPath = entry.value.trashPath ?? + (currentContext != null + ? mapMailboxById[trashId]?.getDisplayName(currentContext!) + : null); + + log('$runtimeType::_moveEmailsToTrashAcrossNamespaces: trashId=$trashId sources=${entry.value.sourceToEmails.keys}'); + moveSelectedEmailMultipleToMailboxAction( + sessionCurrent!, + accountId.value!, + MoveToMailboxRequest( + entry.value.sourceToEmails, + trashId, + MoveAction.moving, + EmailActionType.moveToTrash, + destinationPath: resolvedPath, + ), + emailIdsWithReadStatus, + ); + } + } } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index d88938539..c7f748f8e 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -506,7 +506,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } }), Obx(() { - if (controller.selectedMailbox.value?.isTrash == true) { + if (controller.selectedMailbox.value?.isTrashPersonal == true) { return TMailButtonWidget.fromIcon( key: const Key('recover_deleted_messages_button'), icon: controller.imagePaths.icRecoverDeletedMessages, diff --git a/lib/features/thread/presentation/extensions/handle_press_email_selection_action.dart b/lib/features/thread/presentation/extensions/handle_press_email_selection_action.dart index ab5aa5485..ef53bb937 100644 --- a/lib/features/thread/presentation/extensions/handle_press_email_selection_action.dart +++ b/lib/features/thread/presentation/extensions/handle_press_email_selection_action.dart @@ -48,7 +48,8 @@ extension HandlePressEmailSelectionActionExtension on ThreadController { EmailSelectionActionType.markAsNotSpam else EmailSelectionActionType.markAsSpam, - if (selectedMailbox?.isArchive != true) + if (selectedMailbox?.isArchive != true && + selectedMailbox?.isChildOfTeamMailboxes != true) EmailSelectionActionType.archiveMessage, if (selectedMailbox?.isDeletePermanentlyEnabled == true) EmailSelectionActionType.deletePermanently, diff --git a/lib/features/thread/presentation/mixin/email_action_controller.dart b/lib/features/thread/presentation/mixin/email_action_controller.dart index a4168ee44..8d0fe2104 100644 --- a/lib/features/thread/presentation/mixin/email_action_controller.dart +++ b/lib/features/thread/presentation/mixin/email_action_controller.dart @@ -107,17 +107,25 @@ mixin EmailActionController { return; } + final emailId = email.id; + if (emailId == null) { + mailboxDashBoardController.emitMoveToTrashFailure( + NotFoundEmailIdException(), + ); + return; + } + _moveToTrashAction( session, accountId, MoveToMailboxRequest( - {mailboxContain.id: email.id != null ? [email.id!] : []}, + {mailboxContain.id: [emailId]}, trashId, MoveAction.moving, EmailActionType.moveToTrash, destinationPath: trashPath, ), - email.id != null ? {email.id!: email.hasRead} : {}, + {emailId: email.hasRead}, ); } diff --git a/lib/features/thread/presentation/mixin/email_more_action_context_menu_mixin.dart b/lib/features/thread/presentation/mixin/email_more_action_context_menu_mixin.dart index 1ae3b25d5..48fc7bbca 100644 --- a/lib/features/thread/presentation/mixin/email_more_action_context_menu_mixin.dart +++ b/lib/features/thread/presentation/mixin/email_more_action_context_menu_mixin.dart @@ -78,7 +78,7 @@ mixin EmailMoreActionContextMenu on LabelSubMenuMixin { if (PlatformInfo.isWeb) EmailActionType.openInNewTab, if (!isDrafts && !isChildOfTeam) isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam, - if (!isArchive) EmailActionType.archiveMessage, + if (!isArchive && !isChildOfTeam) EmailActionType.archiveMessage, if (!isDrafts && !isTemplates) EmailActionType.editAsNewEmail, ]; } diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 13b8f466e..1f3655a91 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1563,9 +1563,11 @@ class ThreadController extends BaseController with EmailActionController { return DismissDirection.none; } - return isInArchiveMailbox(email) || !hasArchiveMailbox() - ? DismissDirection.startToEnd - : DismissDirection.horizontal; + return isInArchiveMailbox(email) || + !hasArchiveMailbox() || + email.mailboxContain?.isChildOfTeamMailboxes == true + ? DismissDirection.startToEnd + : DismissDirection.horizontal; } bool isInArchiveMailbox(PresentationEmail email) => email.mailboxContain?.isArchive == true; diff --git a/lib/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart b/lib/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart index 59d23ec40..896d4d443 100644 --- a/lib/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart +++ b/lib/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart @@ -1,5 +1,6 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; @@ -26,16 +27,22 @@ extension GetThreadDetailActionStatus on ThreadDetailController { } bool get threadDetailIsArchived { - final archiveMailboxId = getMailboxIdByRole( - PresentationMailbox.roleArchive, - ); + final archiveMailboxId = getMailboxIdByRole(PresentationMailbox.roleArchive); return emailsInThreadDetailInfo.every( - (email) { - return email.mailboxIds?[archiveMailboxId] == true; - }, + (email) => email.mailboxIds?[archiveMailboxId] == true, ); } + bool get threadDetailIsTeamMailbox { + return emailsInThreadDetailInfo.any((email) { + final mailboxId = email.mailboxIdContain; + if (mailboxId == null) return false; + return mailboxDashBoardController.mapMailboxById[mailboxId] + ?.isChildOfTeamMailboxes == + true; + }); + } + bool get threadDetailIsSpam { final spamMailboxId = getMailboxIdByRole(PresentationMailbox.roleJunk) ?? diff --git a/lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart b/lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart index 1d01818c6..d696d764d 100644 --- a/lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart +++ b/lib/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart @@ -147,7 +147,7 @@ extension OnThreadDetailActionClick on ThreadDetailController { if (mailboxDashBoardController.isLabelAvailable && mailboxDashBoardController.labelController.labels.isNotEmpty) EmailActionType.labelAs, - if (!threadDetailIsArchived) EmailActionType.archiveMessage, + if (!threadDetailIsArchived && !threadDetailIsTeamMailbox) EmailActionType.archiveMessage, threadDetailIsSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam, threadDetailIsTrashed ? EmailActionType.deletePermanently diff --git a/model/lib/extensions/list_presentation_email_extension.dart b/model/lib/extensions/list_presentation_email_extension.dart index da712ea7a..8554018d7 100644 --- a/model/lib/extensions/list_presentation_email_extension.dart +++ b/model/lib/extensions/list_presentation_email_extension.dart @@ -56,7 +56,8 @@ extension ListPresentationEmailExtension on List { ) { return any((email) { final mailboxContain = email.findMailboxContain(mapMailbox); - return mailboxContain?.isArchive != true; + return mailboxContain?.isArchive != true && + mailboxContain?.isChildOfTeamMailboxes != true; }); } diff --git a/model/lib/extensions/presentation_mailbox_extension.dart b/model/lib/extensions/presentation_mailbox_extension.dart index a0f977725..fd7277655 100644 --- a/model/lib/extensions/presentation_mailbox_extension.dart +++ b/model/lib/extensions/presentation_mailbox_extension.dart @@ -40,18 +40,29 @@ extension PresentationMailboxExtension on PresentationMailbox { bool get isVirtualFolder => isFavorite || isActionRequired; - bool get isTrash { - if (isPersonal) { - return role == PresentationMailbox.roleTrash; - } else { - return isTrashTeamMailbox; - } + bool get isTrash => isTrashPersonal || isTrashTeamMailbox; + + bool get isTrashPersonal => + isPersonal && role == PresentationMailbox.roleTrash; + + bool _isTeamMailboxWithRole(String role) => + isChildOfTeamMailboxes && + name?.name.toLowerCase() == role.toLowerCase(); + + /// Stricter version of [_isTeamMailboxWithRole] that additionally checks + /// the parent is a team-root node (no parentId), ensuring only first-level + /// system folders match — not nested user subfolders like Team/Project/Trash. + bool isFirstLevelTeamSystemFolder( + Map mailboxMap, + String role, + ) { + if (!_isTeamMailboxWithRole(role)) return false; + final parent = parentId != null ? mailboxMap[parentId] : null; + return parent?.isTeamMailboxes == true; } - bool get isTrashTeamMailbox { - return isChildOfTeamMailboxes && - name?.name.toLowerCase() == PresentationMailbox.trashRole.toLowerCase(); - } + bool get isTrashTeamMailbox => + _isTeamMailboxWithRole(PresentationMailbox.trashRole); bool get isDrafts { if (isPersonal) { @@ -61,11 +72,8 @@ extension PresentationMailboxExtension on PresentationMailbox { } } - bool get isDraftsTeamMailbox { - return isChildOfTeamMailboxes && - name?.name.toLowerCase() == - PresentationMailbox.draftsRole.toLowerCase(); - } + bool get isDraftsTeamMailbox => + _isTeamMailboxWithRole(PresentationMailbox.draftsRole); bool get isTemplates { if (isPersonal) { @@ -75,11 +83,8 @@ extension PresentationMailboxExtension on PresentationMailbox { } } - bool get isTemplatesTeamMailbox { - return isChildOfTeamMailboxes && - name?.name.toLowerCase() == - PresentationMailbox.templatesRole.toLowerCase(); - } + bool get isTemplatesTeamMailbox => + _isTeamMailboxWithRole(PresentationMailbox.templatesRole); bool get isSent => role == PresentationMailbox.roleSent; diff --git a/test/features/mailbox/presentation/extensions/handle_team_mailbox_extension_test.dart b/test/features/mailbox/presentation/extensions/handle_team_mailbox_extension_test.dart deleted file mode 100644 index b5c79eb46..000000000 --- a/test/features/mailbox/presentation/extensions/handle_team_mailbox_extension_test.dart +++ /dev/null @@ -1,301 +0,0 @@ -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/mailbox/mailbox.dart'; -import 'package:jmap_dart_client/jmap/mail/mailbox/namespace.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:model/mailbox/presentation_mailbox.dart'; -import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart'; -import 'package:tmail_ui_user/features/mailbox/presentation/mixin/handle_team_mailbox_mixin.dart'; -import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; -import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; - -import 'handle_team_mailbox_extension_test.mocks.dart'; - -class TestHandleTeamMailboxMixin with HandleTeamMailboxMixin {} - -@GenerateNiceMocks([ - MockSpec(), -]) -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - late TestHandleTeamMailboxMixin testMixin; - late MockMailboxController mockMailboxController; - - final teamNamespace = Namespace('#TeamMailbox'); - final teamMailboxId = MailboxId(Id('team-root')); - final teamTrashId = MailboxId(Id('team-trash')); - final teamInboxId = MailboxId(Id('team-inbox')); - final teamSentId = MailboxId(Id('team-sent')); - - MailboxTree buildTeamMailboxTree({ - List? teamChildren, - }) { - return MailboxTree( - MailboxNode( - MailboxNode.rootItem(), - childrenItems: [ - MailboxNode( - PresentationMailbox( - teamMailboxId, - name: MailboxName('Team'), - namespace: teamNamespace, - ), - childrenItems: teamChildren ?? - [ - MailboxNode( - PresentationMailbox( - teamTrashId, - name: MailboxName('Trash'), - namespace: teamNamespace, - parentId: teamMailboxId, - ), - ), - MailboxNode( - PresentationMailbox( - teamInboxId, - name: MailboxName('Inbox'), - namespace: teamNamespace, - parentId: teamMailboxId, - ), - ), - MailboxNode( - PresentationMailbox( - teamSentId, - name: MailboxName('Sent'), - namespace: teamNamespace, - parentId: teamMailboxId, - ), - ), - ], - ), - ], - ), - ); - } - - setUp(() { - Get.testMode = true; - - mockMailboxController = MockMailboxController(); - when(mockMailboxController.onStart) - .thenReturn(InternalFinalCallback(callback: () {})); - when(mockMailboxController.onDelete) - .thenReturn(InternalFinalCallback(callback: () {})); - - Get.put(mockMailboxController); - - testMixin = TestHandleTeamMailboxMixin(); - }); - - tearDown(() { - Get.reset(); - }); - - group('HandleTeamMailboxMixin::findDefaultMailboxIdInTeamMailbox', () { - test( - 'SHOULD return trash mailbox id ' - 'WHEN namespace matches and trash mailbox exists', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - ); - - expect(result, equals(teamTrashId)); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN namespace does not match any team mailbox', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: Namespace('#UnknownTeam'), - mailboxName: PresentationMailbox.trashRole, - ); - - expect(result, isNull); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN mailbox name does not match any child', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: 'archive', - ); - - expect(result, isNull); - }, - ); - - test( - 'SHOULD match mailbox name case-insensitively', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: 'TRASH', - ); - - expect(result, equals(teamTrashId)); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN team mailbox tree is empty', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(MailboxTree(MailboxNode.root()).obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - ); - - expect(result, isNull); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN team mailbox has no children', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree(teamChildren: []).obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - ); - - expect(result, isNull); - }, - ); - - test( - 'SHOULD return correct mailbox id ' - 'WHEN searching for inbox in team mailbox', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.inboxRole, - ); - - expect(result, equals(teamInboxId)); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN MailboxController is not registered', - () { - Get.delete(); - - final result = testMixin.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - ); - - expect(result, isNull); - }, - ); - }); - - group('HandleTeamMailboxMixin::getTeamMailboxNodePathWithSeparator', () { - test( - 'SHOULD return path with parent separator ' - 'WHEN mailbox has a parent', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.getTeamMailboxNodePathWithSeparator( - mailboxId: teamTrashId, - ); - - expect(result, equals('Team/Trash')); - }, - ); - - test( - 'SHOULD return path with custom separator ' - 'WHEN custom pathSeparator is provided', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.getTeamMailboxNodePathWithSeparator( - mailboxId: teamTrashId, - pathSeparator: '.', - ); - - expect(result, equals('Team.Trash')); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN mailbox id does not exist in tree', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.getTeamMailboxNodePathWithSeparator( - mailboxId: MailboxId(Id('non-existent')), - ); - - expect(result, isNull); - }, - ); - - test( - 'SHOULD return mailbox name only ' - 'WHEN mailbox has no parent', - () { - when(mockMailboxController.teamMailboxesTree) - .thenReturn(buildTeamMailboxTree().obs); - - final result = testMixin.getTeamMailboxNodePathWithSeparator( - mailboxId: teamMailboxId, - ); - - expect(result, equals('Team')); - }, - ); - - test( - 'SHOULD return null ' - 'WHEN MailboxController is not registered', - () { - Get.delete(); - - final result = testMixin.getTeamMailboxNodePathWithSeparator( - mailboxId: teamTrashId, - ); - - expect(result, isNull); - }, - ); - }); -} diff --git a/test/features/thread/presentation/mixin/get_trash_mailbox_id_and_path_test.dart b/test/features/thread/presentation/mixin/get_trash_mailbox_id_and_path_test.dart deleted file mode 100644 index a842deafd..000000000 --- a/test/features/thread/presentation/mixin/get_trash_mailbox_id_and_path_test.dart +++ /dev/null @@ -1,180 +0,0 @@ -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/mailbox/mailbox.dart'; -import 'package:jmap_dart_client/jmap/mail/mailbox/namespace.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:model/mailbox/presentation_mailbox.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/get_trash_mailbox_id_and_path_extension.dart'; - -import 'get_trash_mailbox_id_and_path_test.mocks.dart'; - -@GenerateNiceMocks([ - MockSpec(), -]) -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final defaultTrashId = MailboxId(Id('default-trash')); - final teamNamespace = Namespace('#TeamMailbox'); - final teamMailboxId = MailboxId(Id('team-root')); - final teamTrashId = MailboxId(Id('team-trash')); - - final personalMailbox = PresentationMailbox( - MailboxId(Id('personal-inbox')), - name: MailboxName('Inbox'), - ); - - final teamMailbox = PresentationMailbox( - teamMailboxId, - name: MailboxName('Team'), - namespace: teamNamespace, - ); - - final teamMailboxWithNullNamespace = PresentationMailbox( - MailboxId(Id('team-no-ns')), - name: MailboxName('Team No NS'), - ); - - late MockMailboxDashBoardController mockDashBoardController; - - setUp(() { - Get.testMode = true; - - mockDashBoardController = MockMailboxDashBoardController(); - - when(mockDashBoardController.mapDefaultMailboxIdByRole) - .thenReturn({PresentationMailbox.roleTrash: defaultTrashId}); - when(mockDashBoardController.selectedMailbox) - .thenReturn(Rxn()); - }); - - group('GetTrashMailboxIdAndPathExtension::getTrashMailboxIdAndPath', () { - test( - 'SHOULD return default trash id with null path ' - 'WHEN emailMailbox is personal (no namespace)', - () { - final result = mockDashBoardController.getTrashMailboxIdAndPath( - personalMailbox, - ); - - expect(result.trashId, equals(defaultTrashId)); - expect(result.trashPath, isNull); - }, - ); - - test( - 'SHOULD return team trash id ' - 'WHEN emailMailbox is team mailbox even if selectedMailbox is personal', - () { - when(mockDashBoardController.selectedMailbox) - .thenReturn(Rxn(personalMailbox)); - when(mockDashBoardController.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - )).thenReturn(teamTrashId); - when(mockDashBoardController.getTeamMailboxNodePathWithSeparator( - mailboxId: teamTrashId, - )).thenReturn('Team/Trash'); - - final result = mockDashBoardController.getTrashMailboxIdAndPath( - teamMailbox, - ); - - expect(result.trashId, equals(teamTrashId)); - expect(result.trashPath, equals('Team/Trash')); - }, - ); - - test( - 'SHOULD return default trash id with null path ' - 'WHEN mailbox namespace is null', - () { - final result = mockDashBoardController.getTrashMailboxIdAndPath( - teamMailboxWithNullNamespace, - ); - - expect(result.trashId, equals(defaultTrashId)); - expect(result.trashPath, isNull); - }, - ); - - test( - 'SHOULD return default trash id with null path ' - 'WHEN findDefaultMailboxIdInTeamMailbox returns null', - () { - when(mockDashBoardController.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - )).thenReturn(null); - - final result = mockDashBoardController.getTrashMailboxIdAndPath( - teamMailbox, - ); - - expect(result.trashId, equals(defaultTrashId)); - expect(result.trashPath, isNull); - }, - ); - - test( - 'SHOULD return team trash id with path ' - 'WHEN team mailbox has trash folder', - () { - when(mockDashBoardController.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - )).thenReturn(teamTrashId); - when(mockDashBoardController.getTeamMailboxNodePathWithSeparator( - mailboxId: teamTrashId, - )).thenReturn('Team/Trash'); - - final result = mockDashBoardController.getTrashMailboxIdAndPath( - teamMailbox, - ); - - expect(result.trashId, equals(teamTrashId)); - expect(result.trashPath, equals('Team/Trash')); - }, - ); - - test( - 'SHOULD return team trash id with null path ' - 'WHEN getTeamMailboxNodePathWithSeparator returns null', - () { - when(mockDashBoardController.findDefaultMailboxIdInTeamMailbox( - namespace: teamNamespace, - mailboxName: PresentationMailbox.trashRole, - )).thenReturn(teamTrashId); - when(mockDashBoardController.getTeamMailboxNodePathWithSeparator( - mailboxId: teamTrashId, - )).thenReturn(null); - - final result = mockDashBoardController.getTrashMailboxIdAndPath( - teamMailbox, - ); - - expect(result.trashId, equals(teamTrashId)); - expect(result.trashPath, isNull); - }, - ); - - test( - 'SHOULD return null trash id with null path ' - 'WHEN mapDefaultMailboxIdByRole has no trash entry ' - 'AND mailbox is personal', - () { - when(mockDashBoardController.mapDefaultMailboxIdByRole).thenReturn({}); - - final result = mockDashBoardController.getTrashMailboxIdAndPath( - personalMailbox, - ); - - expect(result.trashId, isNull); - expect(result.trashPath, isNull); - }, - ); - }); -}