Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (moreFilterCondition != null && moreFilterCondition.hasCondition)
moreFilterCondition
};
Expand Down
139 changes: 111 additions & 28 deletions test/features/search/search_email_filter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -78,18 +79,21 @@ 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();

// Act
final result = filter.mappingToEmailFilterCondition();

// Assert
expect(result, isNull);
// Events are excluded by default even when no other filter is set.
expect(result, isA<EmailFilterCondition>());
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'),
Expand All @@ -99,9 +103,19 @@ void main() {
final result = filter.mappingToEmailFilterCondition();

// Assert
expect(result, isA<EmailFilterCondition>());
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<LogicFilterOperator>());
final andFilter = result as LogicFilterOperator;
expect(andFilter.operator, Operator.AND);
expect(andFilter.conditions.length, equals(2));

final textCondition = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.text != null);
expect(textCondition.text, 'example');

final eventsExclusion = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.notKeyword != null);
expect(eventsExclusion.notKeyword, KeyWordIdentifierExtension.eventsMail.value);
});

test('SHOULD creates a filter with multiple "to" values using AND logic operator', () {
Expand All @@ -114,10 +128,19 @@ void main() {
final result = filter.mappingToEmailFilterCondition();

// Assert
// 2 "to" OR-conditions + events-exclusion = 3 conditions under AND.
expect(result, isA<LogicFilterOperator>());
final logicOperator = result as LogicFilterOperator;
expect(logicOperator.operator, Operator.AND);
expect(logicOperator.conditions.length, equals(2));
expect(logicOperator.conditions.length, equals(3));
expect(
logicOperator.conditions.whereType<EmailFilterCondition>().any(
(condition) =>
condition.notKeyword ==
KeyWordIdentifierExtension.eventsMail.value,
),
isTrue,
);
});

test('SHOULD includes moreFilterCondition WHEN provided', () {
Expand Down Expand Up @@ -165,7 +188,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'},
Expand All @@ -175,43 +198,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<LogicFilterOperator>());
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<LogicFilterOperator>().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<EmailFilterCondition>().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<LogicFilterOperator>());
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<LogicFilterOperator>().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);

Expand All @@ -220,6 +246,63 @@ void main() {

expect(listEmailCondition[2].text, 'bye');
expect(listEmailCondition[2].notKeyword, isNull);

final eventsExclusion = andFilter.conditions.whereType<EmailFilterCondition>().first;
expect(eventsExclusion.notKeyword, KeyWordIdentifierExtension.eventsMail.value);
});

test('SHOULD NOT add an events-exclusion condition WHEN hasKeyword contains the events keyword', () {
// Arrange
final filter = SearchEmailFilter(
hasKeyword: {
KeyWordIdentifierExtension.eventsMail.value,
'another-keyword',
},
);

// Act
final result = filter.mappingToEmailFilterCondition();

// Assert
// With events explicitly included, no notKeyword=event condition should appear.
expect(result, isA<LogicFilterOperator>());
final andFilter = result as LogicFilterOperator;
expect(
andFilter.conditions.whereType<EmailFilterCondition>().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<LogicFilterOperator>());
final andFilter = result as LogicFilterOperator;
expect(
andFilter.conditions.whereType<EmailFilterCondition>().any(
(condition) => condition.hasKeyword == 'another-keyword',
),
isTrue,
);
expect(
andFilter.conditions.whereType<EmailFilterCondition>().any(
(condition) =>
condition.notKeyword ==
KeyWordIdentifierExtension.eventsMail.value,
),
isTrue,
);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -587,8 +588,10 @@ void main() {
equals(emailList.last.receivedAt),
);

expect(filterInJMapRequest, isA<EmailFilterCondition>());
final emailFilter = filterInJMapRequest as EmailFilterCondition;
expect(filterInJMapRequest, isA<LogicFilterOperator>());
final andFilter = filterInJMapRequest as LogicFilterOperator;
final emailFilter = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.before != null);
expect(emailFilter.before, equals(emailList.last.receivedAt));
});

Expand Down Expand Up @@ -730,8 +733,10 @@ void main() {
isNull,
);

expect(filterInJMapRequest, isA<EmailFilterCondition>());
final emailFilter = filterInJMapRequest as EmailFilterCondition;
expect(filterInJMapRequest, isA<LogicFilterOperator>());
final andFilter = filterInJMapRequest as LogicFilterOperator;
final emailFilter = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.before != null);
expect(emailFilter.before, equals(UTCDate(endDate)));
});

Expand Down Expand Up @@ -891,8 +896,10 @@ void main() {
isNull,
);

expect(filterInJMapRequest, isA<EmailFilterCondition>());
final emailFilter = filterInJMapRequest as EmailFilterCondition;
expect(filterInJMapRequest, isA<LogicFilterOperator>());
final andFilter = filterInJMapRequest as LogicFilterOperator;
final emailFilter = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.before != null);
expect(emailFilter.before, equals(UTCDate(endDate)));
});

Expand Down Expand Up @@ -1061,8 +1068,10 @@ void main() {
isNull,
);

expect(filterInJMapRequest, isA<EmailFilterCondition>());
final emailFilter = filterInJMapRequest as EmailFilterCondition;
expect(filterInJMapRequest, isA<LogicFilterOperator>());
final andFilter = filterInJMapRequest as LogicFilterOperator;
final emailFilter = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.before != null);
expect(emailFilter.before, equals(UTCDate(endDate)));
});

Expand Down Expand Up @@ -1172,8 +1181,10 @@ void main() {
UTCDate(loadMoreDate),
);

expect(filterInJMapRequest, isA<EmailFilterCondition>());
final emailFilter = filterInJMapRequest as EmailFilterCondition;
expect(filterInJMapRequest, isA<LogicFilterOperator>());
final andFilter = filterInJMapRequest as LogicFilterOperator;
final emailFilter = andFilter.conditions.whereType<EmailFilterCondition>()
.firstWhere((c) => c.before != null);
expect(emailFilter.before, equals(UTCDate(loadMoreDate)));
});
});
Expand Down
Loading