TF-4141 Add unit test for mailbox_filter_builder

This commit is contained in:
dab246
2025-12-29 13:49:00 +07:00
committed by Dat H. Pham
parent df5ed118b3
commit fcb7fcf3ba
9 changed files with 564 additions and 173 deletions
@@ -12,16 +12,17 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/state.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:rxdart/subjects.dart';
import 'package:tmail_ui_user/features/base/extensions/handle_mailbox_action_type_extension.dart';
import 'package:tmail_ui_user/features/base/model/filter_filter.dart';
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart';
import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/delete_email_permanently_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_restored_deleted_message_interactor.dart';
@@ -72,23 +73,22 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_e
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/advanced_filter_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart';
import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart';
import 'package:tmail_ui_user/features/base/model/filter_filter.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart';
import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/usecases/delete_sending_email_interactor.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/usecases/get_all_sending_email_interactor.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/usecases/store_sending_email_interactor.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_sending_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/clean_and_get_emails_in_mailbox_interactor.dart';
@@ -103,11 +103,10 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_emai
import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart';
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
import 'package:tmail_ui_user/main/utils/twake_app_manager.dart';
import 'package:uuid/uuid.dart';
@@ -485,10 +484,7 @@ void main() {
testSession, testAccountId,
limit: ThreadConstants.defaultLimit,
sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(),
emailFilter: EmailFilter(
filter: EmailFilterCondition(inMailbox: testMailboxId),
filterOption: FilterMessageOption.all,
mailboxId: testMailboxId),
emailFilter: threadController.getEmailFilterForLoadMailbox(),
getLatestChanges: false,
propertiesCreated: ThreadConstants.propertiesDefault,
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault));
@@ -546,10 +542,7 @@ void main() {
testSession, testAccountId,
limit: ThreadConstants.defaultLimit,
sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(),
emailFilter: EmailFilter(
filter: EmailFilterCondition(inMailbox: testMailboxId),
filterOption: FilterMessageOption.all,
mailboxId: testMailboxId),
emailFilter: threadController.getEmailFilterForLoadMailbox(),
getLatestChanges: false,
propertiesCreated: ThreadConstants.propertiesDefault,
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault
@@ -21,6 +21,7 @@ import 'package:rxdart/rxdart.dart';
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart';
import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/delete_email_permanently_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_restored_deleted_message_interactor.dart';
@@ -55,7 +56,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_re
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_email_sort_order_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/download_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart';
import 'package:tmail_ui_user/features/download/presentation/controllers/download_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart';
@@ -70,7 +70,6 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/get_all_sen
import 'package:tmail_ui_user/features/sending_queue/domain/usecases/store_sending_email_interactor.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_sending_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_all_email_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart';
@@ -86,6 +85,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_emai
import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart';
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
@@ -95,7 +95,6 @@ import 'package:uuid/uuid.dart';
import '../../fixtures/account_fixtures.dart';
import '../../fixtures/session_fixtures.dart';
import 'verify_before_time_in_search_email_filter_test.mocks.dart';
mockControllerCallback() => InternalFinalCallback<void>(callback: () {});
@@ -858,11 +857,7 @@ void main() {
AccountFixtures.aliceAccountId,
),
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault,
emailFilter: EmailFilter(
filter: threadController.getFilterCondition(mailboxIdSelected: threadController.selectedMailboxId),
filterOption: mailboxDashboardController.filterMessageOption.value,
mailboxId: threadController.selectedMailboxId,
),
emailFilter: threadController.getEmailFilterForLoadMailbox(),
)).called(1);
verify(mockSearchEmailInteractor.execute(
@@ -1026,11 +1021,7 @@ void main() {
AccountFixtures.aliceAccountId,
),
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault,
emailFilter: EmailFilter(
filter: threadController.getFilterCondition(mailboxIdSelected: threadController.selectedMailboxId),
filterOption: mailboxDashboardController.filterMessageOption.value,
mailboxId: threadController.selectedMailboxId,
),
emailFilter: threadController.getEmailFilterForLoadMailbox(),
)).called(1);
verify(mockSearchEmailInteractor.execute(
@@ -1139,11 +1130,7 @@ void main() {
AccountFixtures.aliceAccountId,
),
propertiesUpdated: ThreadConstants.propertiesUpdatedDefault,
emailFilter: EmailFilter(
filter: threadController.getFilterCondition(mailboxIdSelected: threadController.selectedMailboxId),
filterOption: mailboxDashboardController.filterMessageOption.value,
mailboxId: threadController.selectedMailboxId,
),
emailFilter: threadController.getEmailFilterForLoadMailbox(),
)).called(1);
verify(mockSearchEmailInteractor.execute(
@@ -0,0 +1,370 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:jmap_dart_client/jmap/core/filter/filter.dart';
import 'package:jmap_dart_client/jmap/core/filter/filter_operator.dart';
import 'package:jmap_dart_client/jmap/core/filter/operator/logic_filter_operator.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.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:model/extensions/keyword_identifier_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
import 'package:tmail_ui_user/features/thread/presentation/filters/mailbox_filter_builder.dart';
void main() {
PresentationMailbox mailbox({
bool favorite = false,
bool actionRequired = false,
}) {
if (favorite) {
return PresentationMailbox.favoriteFolder;
} else if (actionRequired) {
return PresentationMailbox.actionRequiredFolder;
} else {
return PresentationMailbox(
MailboxId(Id('inbox')),
name: MailboxName('Inbox'),
role: PresentationMailbox.roleInbox,
);
}
}
UTCDate beforeDate() => UTCDate(DateTime.utc(2024, 1, 1));
group('buildEmailFilterForLoadMailbox', () {
test(
'ActionRequired mailbox → returns EmailFilter without mailboxId and filterOption',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.all,
);
final result = builder.buildEmailFilterForLoadMailbox();
expect(result.filter, isA<Filter>());
expect(result.mailboxId, isNull);
expect(result.filterOption, isNull);
},
);
test(
'Normal mailbox → returns EmailFilter with filterOption applied',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.unread,
);
final result = builder.buildEmailFilterForLoadMailbox();
expect(result.filterOption, FilterMessageOption.unread);
},
);
});
group('Favorite mailbox - buildFilterCondition', () {
test(
'Unread filter → flagged AND not seen',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(favorite: true),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition(
oldestEmail: null,
) as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
},
);
test(
'Attachments filter → hasAttachment AND flagged',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(favorite: true),
filterMessageOption: FilterMessageOption.attachments,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasAttachment, true);
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
},
);
test(
'Starred filter → flagged only',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(favorite: true),
filterMessageOption: FilterMessageOption.starred,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
expect(filter.notKeyword, isNull);
},
);
});
group('ActionRequired mailbox - buildFilterCondition', () {
test(
'Starred filter → AND(flagged, needsAction AND not seen)',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.starred,
);
final filter = builder.buildFilterCondition();
expect(filter, isA<LogicFilterOperator>());
final logic = filter as LogicFilterOperator;
expect(logic.operator, Operator.AND);
expect(logic.conditions.length, 2);
},
);
test(
'Attachments filter → hasAttachment AND needsAction AND not seen',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.attachments,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasAttachment, true);
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
expect(
filter.hasKeyword,
KeyWordIdentifierExtension.needsActionMail.value,
);
},
);
test(
'Unread filter → needsAction AND not seen',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
expect(
filter.hasKeyword,
KeyWordIdentifierExtension.needsActionMail.value,
);
},
);
});
group('Default mailbox - buildFilterCondition', () {
test(
'All filter → no keyword or attachment constraints',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.all,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, isNull);
expect(filter.notKeyword, isNull);
expect(filter.hasAttachment, isNull);
},
);
test(
'Unread filter → not seen',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
},
);
test(
'Attachments filter → hasAttachment',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.attachments,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasAttachment, true);
},
);
test(
'Starred filter → flagged',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.starred,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
},
);
});
group('Pagination (before) handling', () {
test(
'Oldest email provided → before date propagated into filter',
() {
final before = beforeDate();
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.all,
);
final filter = builder.buildFilterCondition(
oldestEmail: PresentationEmail(receivedAt: before),
) as EmailFilterCondition;
expect(filter.before, before);
},
);
});
group('MailboxFilterBuilder - edge cases and regression', () {
test(
'No selectedMailbox → treated as default mailbox',
() {
const builder = MailboxFilterBuilder(
selectedMailbox: null,
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
},
);
test(
'Favorite mailbox + All filter → flagged only',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(favorite: true),
filterMessageOption: FilterMessageOption.all,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
expect(filter.notKeyword, isNull);
},
);
test(
'ActionRequired mailbox + All filter → needsAction AND not seen',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.all,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
expect(
filter.hasKeyword,
KeyWordIdentifierExtension.needsActionMail.value,
);
},
);
test(
'Favorite mailbox takes precedence over ActionRequired',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(
favorite: true,
actionRequired: true,
),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword, KeyWordIdentifier.emailFlagged.value);
expect(filter.notKeyword, KeyWordIdentifier.emailSeen.value);
},
);
test(
'Before date propagated into all conditions of LogicFilterOperator',
() {
final before = beforeDate();
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.starred,
);
final filter = builder.buildFilterCondition(
oldestEmail: PresentationEmail(receivedAt: before),
) as LogicFilterOperator;
for (final condition in filter.conditions) {
final emailFilter = condition as EmailFilterCondition;
expect(emailFilter.before, before);
}
},
);
test(
'buildEmailFilterForLoadMailbox uses buildFilterCondition result',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(),
filterMessageOption: FilterMessageOption.unread,
);
final emailFilter = builder.buildEmailFilterForLoadMailbox();
final condition = emailFilter.filter as EmailFilterCondition;
expect(condition.notKeyword, KeyWordIdentifier.emailSeen.value);
},
);
test(
'Filter never contains same keyword in both hasKeyword and notKeyword',
() {
final builder = MailboxFilterBuilder(
selectedMailbox: mailbox(actionRequired: true),
filterMessageOption: FilterMessageOption.unread,
);
final filter = builder.buildFilterCondition() as EmailFilterCondition;
expect(filter.hasKeyword == filter.notKeyword, false);
},
);
});
}