From 52cd120e523951b3c969f92dcc9aab745aca65c9 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 14 Apr 2026 17:03:16 +0700 Subject: [PATCH 1/3] hotfix: Set NOT event-email when search email as default Signed-off-by: dab246 --- .../presentation/model/search/search_email_filter.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart b/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart index b8d454c58c..d2db619cbe 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart @@ -12,6 +12,7 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:labels/model/label.dart'; import 'package:model/email/prefix_email_address.dart'; import 'package:model/extensions/email_filter_condition_extension.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_receive_time_type.dart'; @@ -157,6 +158,10 @@ class SearchEmailFilter with EquatableMixin, OptionParamMixin { ), if (label?.keyword?.value != null) EmailFilterCondition(hasKeyword: label!.keyword!.value), + if (!hasKeyword.contains(KeyWordIdentifierExtension.eventsMail.value)) + EmailFilterCondition( + notKeyword: KeyWordIdentifierExtension.eventsMail.value, + ), if (moreFilterCondition != null && moreFilterCondition.hasCondition) moreFilterCondition }; From d2af6575a5a3984e115f478396f17c8f424cafec Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 14 Apr 2026 17:14:44 +0700 Subject: [PATCH 2/3] fixup! hotfix: Set NOT event-email when search email as default Signed-off-by: dab246 --- .../search/search_email_filter_test.dart | 94 +++++++++++++------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/test/features/search/search_email_filter_test.dart b/test/features/search/search_email_filter_test.dart index a98ce46b66..75a15b3007 100644 --- a/test/features/search/search_email_filter_test.dart +++ b/test/features/search/search_email_filter_test.dart @@ -5,6 +5,7 @@ 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/mailbox/mailbox.dart'; +import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/mailbox/presentation_mailbox.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'; @@ -78,7 +79,7 @@ void main() { }); group('mappingToEmailFilterCondition::test', () { - test('SHOULD returns null WHEN there are no conditions', () { + test('SHOULD return an EmailFilterCondition excluding events WHEN there are no other conditions', () { // Arrange final filter = SearchEmailFilter.initial(); @@ -86,10 +87,13 @@ void main() { final result = filter.mappingToEmailFilterCondition(); // Assert - expect(result, isNull); + // Events are excluded by default even when no other filter is set. + expect(result, isA()); + final emailCondition = result as EmailFilterCondition; + expect(emailCondition.notKeyword, KeyWordIdentifierExtension.eventsMail.value); }); - test('SHOULD creates a simple filter WHEN text is provided', () { + test('SHOULD creates a filter combined with events-exclusion WHEN text is provided', () { // Arrange final filter = SearchEmailFilter( text: SearchQuery('example'), @@ -99,9 +103,19 @@ void main() { final result = filter.mappingToEmailFilterCondition(); // Assert - expect(result, isA()); - final emailCondition = result as EmailFilterCondition; - expect(emailCondition.text, 'example'); + // Events are excluded by default, so result is AND(text=example, notKeyword=event). + expect(result, isA()); + final andFilter = result as LogicFilterOperator; + expect(andFilter.operator, Operator.AND); + expect(andFilter.conditions.length, equals(2)); + + final textCondition = andFilter.conditions.whereType() + .firstWhere((c) => c.text != null); + expect(textCondition.text, 'example'); + + final eventsExclusion = andFilter.conditions.whereType() + .firstWhere((c) => c.notKeyword != null); + expect(eventsExclusion.notKeyword, KeyWordIdentifierExtension.eventsMail.value); }); test('SHOULD creates a filter with multiple "to" values using AND logic operator', () { @@ -114,10 +128,11 @@ void main() { final result = filter.mappingToEmailFilterCondition(); // Assert + // 2 "to" OR-conditions + events-exclusion = 3 conditions under AND. expect(result, isA()); final logicOperator = result as LogicFilterOperator; expect(logicOperator.operator, Operator.AND); - expect(logicOperator.conditions.length, equals(2)); + expect(logicOperator.conditions.length, equals(3)); }); test('SHOULD includes moreFilterCondition WHEN provided', () { @@ -165,7 +180,7 @@ void main() { expect(logicOperator.conditions.length, greaterThan(1)); }); - test('SHOULD wrap a simple EmailFilterCondition inside a NOT LogicFilterOperator when notKeyword is given', () { + test('SHOULD combine a NOT LogicFilterOperator for notKeyword with an events-exclusion condition using AND WHEN notKeyword is given', () { // Arrange final filter = SearchEmailFilter( notKeyword: {'hello'}, @@ -175,43 +190,46 @@ void main() { final result = filter.mappingToEmailFilterCondition(); // Assert + // The result is AND(NOT(text=hello), notKeyword=event) because events are excluded by default. expect(result, isA()); - final logicFilter = result as LogicFilterOperator; - expect(result.conditions.length, equals(1)); - expect(logicFilter.operator, equals(Operator.NOT)); - - final emailCondition = result.conditions.first as EmailFilterCondition; - expect(emailCondition.text, 'hello'); - expect(emailCondition.notKeyword, isNull); + final andFilter = result as LogicFilterOperator; + expect(andFilter.operator, equals(Operator.AND)); + expect(andFilter.conditions.length, equals(2)); + + final notFilter = andFilter.conditions.whereType().first; + expect(notFilter.operator, equals(Operator.NOT)); + expect(notFilter.conditions.length, equals(1)); + final notCondition = notFilter.conditions.first as EmailFilterCondition; + expect(notCondition.text, 'hello'); + expect(notCondition.notKeyword, isNull); + + final eventsExclusion = andFilter.conditions.whereType().first; + expect(eventsExclusion.notKeyword, KeyWordIdentifierExtension.eventsMail.value); }); test( - 'SHOULD convert the notKeyword set into a NOT LogicFilterOperator containing multiple EmailFilterCondition instances,\n' - 'each representing a keyword from the set', + 'SHOULD combine a NOT LogicFilterOperator with multiple keywords and an events-exclusion condition using AND', () { // Arrange - // Create a SearchEmailFilter with three keywords in the notKeyword field final filter = SearchEmailFilter( notKeyword: {'hello', 'hi', 'bye'}, ); // Act - // Call mappingToEmailFilterCondition to transform it into filter conditions final result = filter.mappingToEmailFilterCondition(); // Assert - // The result should be a LogicFilterOperator with the NOT operator + // The result is AND(NOT(text=hello, text=hi, text=bye), notKeyword=event). expect(result, isA()); - final logicFilter = result as LogicFilterOperator; - expect(logicFilter.operator, equals(Operator.NOT)); + final andFilter = result as LogicFilterOperator; + expect(andFilter.operator, equals(Operator.AND)); + expect(andFilter.conditions.length, equals(2)); - // The LogicFilterOperator should contain 3 EmailFilterCondition objects, one for each keyword - expect(logicFilter.conditions.isNotEmpty, equals(true)); - expect(logicFilter.conditions.length, equals(3)); - - // Verify each EmailFilterCondition's content - final listEmailCondition = logicFilter.conditions.map((e) => e as EmailFilterCondition).toList(); + final notFilter = andFilter.conditions.whereType().first; + expect(notFilter.operator, equals(Operator.NOT)); + expect(notFilter.conditions.length, equals(3)); + final listEmailCondition = notFilter.conditions.map((e) => e as EmailFilterCondition).toList(); expect(listEmailCondition[0].text, 'hello'); expect(listEmailCondition[0].notKeyword, isNull); @@ -220,6 +238,26 @@ void main() { expect(listEmailCondition[2].text, 'bye'); expect(listEmailCondition[2].notKeyword, isNull); + + final eventsExclusion = andFilter.conditions.whereType().first; + expect(eventsExclusion.notKeyword, KeyWordIdentifierExtension.eventsMail.value); + }); + + test('SHOULD NOT add an events-exclusion condition WHEN hasKeyword explicitly includes the events keyword', () { + // Arrange + final filter = SearchEmailFilter( + hasKeyword: {KeyWordIdentifierExtension.eventsMail.value}, + ); + + // Act + final result = filter.mappingToEmailFilterCondition(); + + // Assert + // With events explicitly included, no notKeyword=event condition should appear. + expect(result, isA()); + final emailCondition = result as EmailFilterCondition; + expect(emailCondition.hasKeyword, KeyWordIdentifierExtension.eventsMail.value); + expect(emailCondition.notKeyword, isNull); }); }); }); From 9cd5bec997440cc1e934857f4765319a38c3762e Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 14 Apr 2026 18:34:44 +0700 Subject: [PATCH 3/3] fixup! fixup! hotfix: Set NOT event-email when search email as default --- .../search/search_email_filter_test.dart | 57 +++++++++++++++++-- ...fore_time_in_search_email_filter_test.dart | 31 ++++++---- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/test/features/search/search_email_filter_test.dart b/test/features/search/search_email_filter_test.dart index 75a15b3007..4e1a86adaf 100644 --- a/test/features/search/search_email_filter_test.dart +++ b/test/features/search/search_email_filter_test.dart @@ -133,6 +133,14 @@ void main() { final logicOperator = result as LogicFilterOperator; expect(logicOperator.operator, Operator.AND); expect(logicOperator.conditions.length, equals(3)); + expect( + logicOperator.conditions.whereType().any( + (condition) => + condition.notKeyword == + KeyWordIdentifierExtension.eventsMail.value, + ), + isTrue, + ); }); test('SHOULD includes moreFilterCondition WHEN provided', () { @@ -243,10 +251,13 @@ void main() { expect(eventsExclusion.notKeyword, KeyWordIdentifierExtension.eventsMail.value); }); - test('SHOULD NOT add an events-exclusion condition WHEN hasKeyword explicitly includes the events keyword', () { + test('SHOULD NOT add an events-exclusion condition WHEN hasKeyword contains the events keyword', () { // Arrange final filter = SearchEmailFilter( - hasKeyword: {KeyWordIdentifierExtension.eventsMail.value}, + hasKeyword: { + KeyWordIdentifierExtension.eventsMail.value, + 'another-keyword', + }, ); // Act @@ -254,10 +265,44 @@ void main() { // Assert // With events explicitly included, no notKeyword=event condition should appear. - expect(result, isA()); - final emailCondition = result as EmailFilterCondition; - expect(emailCondition.hasKeyword, KeyWordIdentifierExtension.eventsMail.value); - expect(emailCondition.notKeyword, isNull); + expect(result, isA()); + final andFilter = result as LogicFilterOperator; + expect( + andFilter.conditions.whereType().any( + (condition) => + condition.notKeyword == + KeyWordIdentifierExtension.eventsMail.value, + ), + isFalse, + ); + }); + + test('SHOULD still add an events-exclusion condition WHEN hasKeyword does not contain the events keyword', () { + // Arrange + final filter = SearchEmailFilter( + hasKeyword: {'another-keyword'}, + ); + + // Act + final result = filter.mappingToEmailFilterCondition(); + + // Assert + expect(result, isA()); + final andFilter = result as LogicFilterOperator; + expect( + andFilter.conditions.whereType().any( + (condition) => condition.hasKeyword == 'another-keyword', + ), + isTrue, + ); + expect( + andFilter.conditions.whereType().any( + (condition) => + condition.notKeyword == + KeyWordIdentifierExtension.eventsMail.value, + ), + isTrue, + ); }); }); }); diff --git a/test/features/search/verify_before_time_in_search_email_filter_test.dart b/test/features/search/verify_before_time_in_search_email_filter_test.dart index cf5f2de12a..819986ac40 100644 --- a/test/features/search/verify_before_time_in_search_email_filter_test.dart +++ b/test/features/search/verify_before_time_in_search_email_filter_test.dart @@ -8,6 +8,7 @@ import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart' hide State; import 'package:flutter_test/flutter_test.dart'; import 'package:get/get.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/state.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; @@ -587,8 +588,10 @@ void main() { equals(emailList.last.receivedAt), ); - expect(filterInJMapRequest, isA()); - final emailFilter = filterInJMapRequest as EmailFilterCondition; + expect(filterInJMapRequest, isA()); + final andFilter = filterInJMapRequest as LogicFilterOperator; + final emailFilter = andFilter.conditions.whereType() + .firstWhere((c) => c.before != null); expect(emailFilter.before, equals(emailList.last.receivedAt)); }); @@ -730,8 +733,10 @@ void main() { isNull, ); - expect(filterInJMapRequest, isA()); - final emailFilter = filterInJMapRequest as EmailFilterCondition; + expect(filterInJMapRequest, isA()); + final andFilter = filterInJMapRequest as LogicFilterOperator; + final emailFilter = andFilter.conditions.whereType() + .firstWhere((c) => c.before != null); expect(emailFilter.before, equals(UTCDate(endDate))); }); @@ -891,8 +896,10 @@ void main() { isNull, ); - expect(filterInJMapRequest, isA()); - final emailFilter = filterInJMapRequest as EmailFilterCondition; + expect(filterInJMapRequest, isA()); + final andFilter = filterInJMapRequest as LogicFilterOperator; + final emailFilter = andFilter.conditions.whereType() + .firstWhere((c) => c.before != null); expect(emailFilter.before, equals(UTCDate(endDate))); }); @@ -1061,8 +1068,10 @@ void main() { isNull, ); - expect(filterInJMapRequest, isA()); - final emailFilter = filterInJMapRequest as EmailFilterCondition; + expect(filterInJMapRequest, isA()); + final andFilter = filterInJMapRequest as LogicFilterOperator; + final emailFilter = andFilter.conditions.whereType() + .firstWhere((c) => c.before != null); expect(emailFilter.before, equals(UTCDate(endDate))); }); @@ -1172,8 +1181,10 @@ void main() { UTCDate(loadMoreDate), ); - expect(filterInJMapRequest, isA()); - final emailFilter = filterInJMapRequest as EmailFilterCondition; + expect(filterInJMapRequest, isA()); + final andFilter = filterInJMapRequest as LogicFilterOperator; + final emailFilter = andFilter.conditions.whereType() + .firstWhere((c) => c.before != null); expect(emailFilter.before, equals(UTCDate(loadMoreDate))); }); });