From 51a4a1b9b758f847de4b6a3545de1a2af1964ab5 Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 13 Nov 2024 15:51:25 +0700 Subject: [PATCH] =?UTF-8?q?TF-2528=20Add=20integration=20test=20for=20sear?= =?UTF-8?q?ch=20email=20with=20sort=20by=20sender=20nam=E2=80=A6e=20ascend?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-docker/docker-compose.yaml | 2 + core/lib/core.dart | 1 + .../extensions/list_datetime_extension.dart | 21 ++ .../cupertino_action_sheet_builder.dart | 4 +- integration_test/base/base_scenario.dart | 6 + integration_test/base/core_robot.dart | 6 - integration_test/robots/composer_robot.dart | 4 - integration_test/robots/login_robot.dart | 10 +- integration_test/robots/search_robot.dart | 30 +++ integration_test/robots/thread_robot.dart | 9 +- .../login_with_basic_auth_scenario.dart | 24 ++- ...search_email_with_sort_order_scenario.dart | 198 ++++++++++++++++++ .../scenarios/send_email_scenario.dart | 14 +- .../search_email_with_sort_order_test.dart | 35 ++++ .../utils/scenario_utils_mixin.dart | 9 - .../popup_context_menu_action_mixin.dart | 11 +- .../presentation/composer_controller.dart | 15 +- .../model/search/email_sort_order_type.dart | 18 +- .../email/presentation/search_email_view.dart | 7 +- .../search_email_with_sort_order/eml/0.eml | 40 ++++ .../search_email_with_sort_order/eml/1.eml | 40 ++++ .../search_email_with_sort_order/eml/2.eml | 40 ++++ .../search_email_with_sort_order/eml/3.eml | 40 ++++ .../search_email_with_sort_order/eml/4.eml | 40 ++++ .../provisioning.sh | 18 ++ .../patrol-integration-test-with-docker.sh | 4 +- ...trol-local-integration-test-with-docker.sh | 4 +- 27 files changed, 591 insertions(+), 59 deletions(-) create mode 100644 core/lib/domain/extensions/list_datetime_extension.dart create mode 100644 integration_test/robots/search_robot.dart create mode 100644 integration_test/scenarios/search_email_with_sort_order_scenario.dart create mode 100644 integration_test/tests/search/search_email_with_sort_order_test.dart delete mode 100644 integration_test/utils/scenario_utils_mixin.dart create mode 100644 provisioning/integration_test/search_email_with_sort_order/eml/0.eml create mode 100644 provisioning/integration_test/search_email_with_sort_order/eml/1.eml create mode 100644 provisioning/integration_test/search_email_with_sort_order/eml/2.eml create mode 100644 provisioning/integration_test/search_email_with_sort_order/eml/3.eml create mode 100644 provisioning/integration_test/search_email_with_sort_order/eml/4.eml create mode 100755 provisioning/integration_test/search_email_with_sort_order/provisioning.sh diff --git a/backend-docker/docker-compose.yaml b/backend-docker/docker-compose.yaml index 2ce61d4f9..36f851616 100644 --- a/backend-docker/docker-compose.yaml +++ b/backend-docker/docker-compose.yaml @@ -10,6 +10,8 @@ services: - ./mailetcontainer.xml:/root/conf/mailetcontainer.xml - ./imapserver.xml:/root/conf/imapserver.xml - ./jmap.properties:/root/conf/jmap.properties + - ../provisioning/integration_test/search_email_with_sort_order/provisioning.sh:/root/conf/integration_test/search_email_with_sort_order/provisioning.sh + - ../provisioning/integration_test/search_email_with_sort_order/eml:/root/conf/integration_test/search_email_with_sort_order/eml ports: - "80:80" environment: diff --git a/core/lib/core.dart b/core/lib/core.dart index f887e6f07..bfe0664e6 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -8,6 +8,7 @@ export 'presentation/extensions/capitalize_extension.dart'; export 'presentation/extensions/list_extensions.dart'; export 'presentation/extensions/list_nullable_extensions.dart'; export 'domain/extensions/datetime_extension.dart'; +export 'domain/extensions/list_datetime_extension.dart'; export 'presentation/extensions/html_extension.dart'; export 'presentation/extensions/compare_string_extension.dart'; export 'presentation/extensions/compare_list_extensions.dart'; diff --git a/core/lib/domain/extensions/list_datetime_extension.dart b/core/lib/domain/extensions/list_datetime_extension.dart new file mode 100644 index 000000000..a5af81996 --- /dev/null +++ b/core/lib/domain/extensions/list_datetime_extension.dart @@ -0,0 +1,21 @@ + +extension ListDateTimeExtension on List { + + bool isSortedByMostRecent() { + for (int i = 0; i < length - 1; i++) { + if (this[i].isBefore(this[i + 1])) { + return false; + } + } + return true; + } + + bool isSortedByOldestFirst() { + for (int i = 0; i < length - 1; i++) { + if (this[i].isAfter(this[i + 1])) { + return false; + } + } + return true; + } +} \ No newline at end of file diff --git a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart index 3f8ff7845..16cb45a93 100644 --- a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart +++ b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart @@ -6,13 +6,14 @@ import 'package:pointer_interceptor/pointer_interceptor.dart'; class CupertinoActionSheetBuilder { final BuildContext _context; + final Key? key; final List _actionTiles = []; Widget? _titleWidget; Widget? _messageWidget; Widget? _cancelWidget; - CupertinoActionSheetBuilder(this._context); + CupertinoActionSheetBuilder(this._context, {this.key}); void title(Widget? titleWidget) { _titleWidget = titleWidget; @@ -35,6 +36,7 @@ class CupertinoActionSheetBuilder { context: _context, barrierColor: AppColor.colorDefaultCupertinoActionSheet, builder: (context) => PointerInterceptor(child: CupertinoActionSheet( + key: key, title: _titleWidget, message: _messageWidget, actions: _actionTiles, diff --git a/integration_test/base/base_scenario.dart b/integration_test/base/base_scenario.dart index a9445902e..1bd8e923f 100644 --- a/integration_test/base/base_scenario.dart +++ b/integration_test/base/base_scenario.dart @@ -1,3 +1,4 @@ +import 'package:flutter_test/flutter_test.dart'; import 'package:patrol/patrol.dart'; abstract class BaseScenario { @@ -6,4 +7,9 @@ abstract class BaseScenario { const BaseScenario(this.$); Future execute(); + + Future expectViewVisible(PatrolFinder patrolFinder) async { + await $.waitUntilVisible(patrolFinder); + expect(patrolFinder, findsWidgets); + } } \ No newline at end of file diff --git a/integration_test/base/core_robot.dart b/integration_test/base/core_robot.dart index a116b2553..7d586037b 100644 --- a/integration_test/base/core_robot.dart +++ b/integration_test/base/core_robot.dart @@ -1,4 +1,3 @@ -import 'package:flutter_test/flutter_test.dart'; import 'package:patrol/patrol.dart'; abstract class CoreRobot { @@ -6,10 +5,5 @@ abstract class CoreRobot { CoreRobot(this.$); - Future ensureViewVisible(PatrolFinder patrolFinder) async { - await $.waitUntilVisible(patrolFinder); - expect(patrolFinder, findsWidgets); - } - dynamic ignoreException() => $.tester.takeException(); } \ No newline at end of file diff --git a/integration_test/robots/composer_robot.dart b/integration_test/robots/composer_robot.dart index e8cd076a7..02786f2fc 100644 --- a/integration_test/robots/composer_robot.dart +++ b/integration_test/robots/composer_robot.dart @@ -50,10 +50,6 @@ class ComposerRobot extends CoreRobot { .tap(); } - Future expectSendEmailSuccessToast() async { - expect($('Message has been sent successfully'), findsOneWidget); - } - Future grantContactPermission() async { if (await $.native.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) { await $.native.grantPermissionWhenInUse(); diff --git a/integration_test/robots/login_robot.dart b/integration_test/robots/login_robot.dart index 492d7fa27..25edc9aef 100644 --- a/integration_test/robots/login_robot.dart +++ b/integration_test/robots/login_robot.dart @@ -1,9 +1,9 @@ import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart'; import 'package:flutter/material.dart'; +import 'package:patrol/patrol.dart'; import 'package:tmail_ui_user/features/login/domain/model/recent_login_username.dart'; import 'package:tmail_ui_user/features/login/presentation/login_view.dart'; import 'package:tmail_ui_user/features/login/presentation/widgets/login_text_input_builder.dart'; -import 'package:tmail_ui_user/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import '../base/core_robot.dart'; @@ -11,15 +11,17 @@ import '../base/core_robot.dart'; class LoginRobot extends CoreRobot { LoginRobot(super.$); - Future expectWelcomeViewVisible() => ensureViewVisible($(TwakeWelcomeView)); + Future grantNotificationPermission(NativeAutomator nativeAutomator) async { + if (await nativeAutomator.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) { + await nativeAutomator.grantPermissionWhenInUse(); + } + } Future tapOnUseCompanyServer() async { await $.pumpAndSettle(); await $(AppLocalizations().useCompanyServer).tap(); } - Future expectLoginViewVisible() => ensureViewVisible($(LoginView)); - Future enterEmail(String email) async { final finder = $(LoginView).$(TextField); await finder.enterText(email); diff --git a/integration_test/robots/search_robot.dart b/integration_test/robots/search_robot.dart new file mode 100644 index 000000000..8923b5091 --- /dev/null +++ b/integration_test/robots/search_robot.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../base/core_robot.dart'; + +class SearchRobot extends CoreRobot { + SearchRobot(super.$); + + Future enterQueryString(String queryString) async { + await $(#search_email_text_field).enterText(queryString); + } + + Future scrollToEndListSearchFilter() async { + await $.scrollUntilVisible( + finder: $(#mobile_sortBy_search_filter_button), + view: $(#search_filter_list_view), + scrollDirection: AxisDirection.right, + delta: 300, + ); + } + + Future openSortOrderBottomDialog() async { + await $(#mobile_sortBy_search_filter_button).tap(); + } + + Future selectSortOrder(String sortOrderName) async { + await $(find.text(sortOrderName)).tap(); + await $.pump(const Duration(seconds: 2)); + } +} \ No newline at end of file diff --git a/integration_test/robots/thread_robot.dart b/integration_test/robots/thread_robot.dart index d2dd8ee3c..6ff0767b1 100644 --- a/integration_test/robots/thread_robot.dart +++ b/integration_test/robots/thread_robot.dart @@ -1,18 +1,17 @@ +import 'package:core/presentation/views/search/search_bar_view.dart'; import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart'; -import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; -import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart'; import '../base/core_robot.dart'; class ThreadRobot extends CoreRobot { ThreadRobot(super.$); - Future expectThreadViewVisible() => ensureViewVisible($(ThreadView)); - Future openComposer() async { await $(ComposeFloatingButton).$(InkWell).tap(); } - Future expectComposerViewVisible() => ensureViewVisible($(ComposerView)); + Future openSearchView() async { + await $(SearchBarView).$(InkWell).tap(); + } } \ No newline at end of file diff --git a/integration_test/scenarios/login_with_basic_auth_scenario.dart b/integration_test/scenarios/login_with_basic_auth_scenario.dart index 3ed430d45..a11320d88 100644 --- a/integration_test/scenarios/login_with_basic_auth_scenario.dart +++ b/integration_test/scenarios/login_with_basic_auth_scenario.dart @@ -1,9 +1,11 @@ +import 'package:tmail_ui_user/features/login/presentation/login_view.dart'; +import 'package:tmail_ui_user/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart'; +import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart'; + import '../base/base_scenario.dart'; import '../robots/login_robot.dart'; -import '../robots/thread_robot.dart'; -import '../utils/scenario_utils_mixin.dart'; -class LoginWithBasicAuthScenario extends BaseScenario with ScenarioUtilsMixin { +class LoginWithBasicAuthScenario extends BaseScenario { const LoginWithBasicAuthScenario( super.$, { @@ -22,12 +24,12 @@ class LoginWithBasicAuthScenario extends BaseScenario with ScenarioUtilsMixin { @override Future execute() async { final loginRobot = LoginRobot($); - final threadRobot = ThreadRobot($); - await loginRobot.expectWelcomeViewVisible(); + await _expectWelcomeViewVisible(); + await loginRobot.tapOnUseCompanyServer(); + await _expectLoginViewVisible(); - await loginRobot.expectLoginViewVisible(); await loginRobot.enterEmail(username); await loginRobot.enterHostUrl(hostUrl); @@ -35,8 +37,14 @@ class LoginWithBasicAuthScenario extends BaseScenario with ScenarioUtilsMixin { await loginRobot.enterBasicAuthPassword(password); await loginRobot.loginBasicAuth(); - await grantNotificationPermission($.native); + await loginRobot.grantNotificationPermission($.native); - await threadRobot.expectThreadViewVisible(); + await _expectThreadViewVisible(); } + + Future _expectWelcomeViewVisible() => expectViewVisible($(TwakeWelcomeView)); + + Future _expectLoginViewVisible() => expectViewVisible($(LoginView)); + + Future _expectThreadViewVisible() => expectViewVisible($(ThreadView)); } \ No newline at end of file diff --git a/integration_test/scenarios/search_email_with_sort_order_scenario.dart b/integration_test/scenarios/search_email_with_sort_order_scenario.dart new file mode 100644 index 000000000..bac755a99 --- /dev/null +++ b/integration_test/scenarios/search_email_with_sort_order_scenario.dart @@ -0,0 +1,198 @@ + +import 'package:collection/collection.dart'; +import 'package:core/domain/extensions/list_datetime_extension.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; +import 'package:tmail_ui_user/features/search/email/presentation/search_email_view.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +import '../base/base_scenario.dart'; +import '../robots/search_robot.dart'; +import '../robots/thread_robot.dart'; +import 'login_with_basic_auth_scenario.dart'; + +class SearchEmailWithSortOrderScenario extends BaseScenario { + const SearchEmailWithSortOrderScenario( + super.$, + { + required this.loginWithBasicAuthScenario, + required this.queryString, + required this.listUsername, + } + ); + + final LoginWithBasicAuthScenario loginWithBasicAuthScenario; + final String queryString; + final List listUsername; + + @override + Future execute() async { + await loginWithBasicAuthScenario.execute(); + + final threadRobot = ThreadRobot($); + await threadRobot.openSearchView(); + await _expectSearchViewVisible(); + + final searchRobot = SearchRobot($); + await searchRobot.enterQueryString(queryString); + await _expectSuggestionSearchListViewVisible(); + + await searchRobot.scrollToEndListSearchFilter(); + await _expectSortBySearchFilterButtonVisible(); + + final appLocalizations = AppLocalizations(); + + for (var sortOrderType in EmailSortOrderType.values) { + await _performSelectSortOrderAndValidateSearchResult( + searchRobot: searchRobot, + sortOrderType: sortOrderType, + appLocalizations: appLocalizations, + ); + } + } + + Future _performSelectSortOrderAndValidateSearchResult({ + required SearchRobot searchRobot, + required EmailSortOrderType sortOrderType, + required AppLocalizations appLocalizations, + }) async { + await Future.delayed(const Duration(seconds: 2)); + + await searchRobot.openSortOrderBottomDialog(); + await _expectSortFilterContextMenuVisible(); + + await searchRobot.selectSortOrder(sortOrderType.getTitleByAppLocalizations(appLocalizations)); + await _expectSearchResultEmailListVisible(); + await _expectEmailListDisplayedCorrectly(listUsername: listUsername); + + switch (sortOrderType) { + case EmailSortOrderType.mostRecent: + await _expectEmailListSortedCorrectByMostRecent(); + break; + case EmailSortOrderType.oldest: + await _expectEmailListSortedCorrectByOldest(); + break; + case EmailSortOrderType.senderAscending: + await _expectEmailListSortedCorrectBySenderAscending(listUsername: listUsername); + break; + case EmailSortOrderType.senderDescending: + await _expectEmailListSortedCorrectBySenderDescending(listUsername: listUsername); + break; + case EmailSortOrderType.subjectAscending: + await _expectEmailListSortedCorrectBySubjectAscending(listUsername: listUsername); + break; + case EmailSortOrderType.subjectDescending: + await _expectEmailListSortedCorrectBySubjectDescending(listUsername: listUsername); + break; + case EmailSortOrderType.relevance: + break; + } + } + + Future _expectSearchViewVisible() async { + await expectViewVisible($(SearchEmailView)); + } + + Future _expectSuggestionSearchListViewVisible() async { + await expectViewVisible($(#suggestion_search_list_view)); + } + + Future _expectSortBySearchFilterButtonVisible() async { + await expectViewVisible($(#mobile_sortBy_search_filter_button)); + } + + Future _expectSortFilterContextMenuVisible() async { + await expectViewVisible($(#sort_filter_context_menu)); + } + + Future _expectSearchResultEmailListVisible() async { + await expectViewVisible($(#search_email_list_notification_listener)); + } + + Future _expectEmailListDisplayedCorrectly({ + required List listUsername, + }) async { + expect(find.byType(EmailTileBuilder), findsNWidgets(listUsername.length)); + } + + Future _expectEmailListSortedCorrectBySenderAscending({ + required List listUsername, + }) async { + final listEmailTile = $.tester + .widgetList(find.byType(EmailTileBuilder)); + + for (int i = 0; i < listUsername.length; i++) { + EmailTileBuilder emailTile = listEmailTile.elementAt(i); + final senderName = emailTile.presentationEmail.firstEmailAddressInFrom; + expect(senderName, equals('${listUsername[i].toLowerCase()}@example.com')); + } + } + + Future _expectEmailListSortedCorrectBySenderDescending({ + required List listUsername, + }) async { + final listEmailTile = $.tester + .widgetList(find.byType(EmailTileBuilder)); + + final reversedListUsername = listUsername.reversed.toList(); + + for (int i = 0; i < reversedListUsername.length; i++) { + EmailTileBuilder emailTile = listEmailTile.elementAt(i); + final senderName = emailTile.presentationEmail.firstEmailAddressInFrom; + expect(senderName, equals('${reversedListUsername[i].toLowerCase()}@example.com')); + } + } + + Future _expectEmailListSortedCorrectBySubjectAscending({ + required List listUsername, + }) async { + final listEmailTile = $.tester + .widgetList(find.byType(EmailTileBuilder)); + + for (int i = 0; i < listUsername.length; i++) { + EmailTileBuilder emailTile = listEmailTile.elementAt(i); + final subject = emailTile.presentationEmail.subject; + expect(subject, equals('${listUsername[i]} send Bob')); + } + } + + Future _expectEmailListSortedCorrectBySubjectDescending({ + required List listUsername, + }) async { + final listEmailTile = $.tester + .widgetList(find.byType(EmailTileBuilder)); + + final reversedListUsername = listUsername.reversed.toList(); + + for (int i = 0; i < reversedListUsername.length; i++) { + EmailTileBuilder emailTile = listEmailTile.elementAt(i); + final subject = emailTile.presentationEmail.subject; + expect(subject, equals('${reversedListUsername[i]} send Bob')); + } + } + + Future _expectEmailListSortedCorrectByMostRecent() async { + final listEmailTile = $.tester + .widgetList(find.byType(EmailTileBuilder)); + + final listReceiveAtTime = listEmailTile + .map((emailTile) => emailTile.presentationEmail.receivedAt?.value) + .whereNotNull() + .toList(); + + expect(listReceiveAtTime.isSortedByMostRecent(), isTrue); + } + + Future _expectEmailListSortedCorrectByOldest() async { + final listEmailTile = $.tester + .widgetList(find.byType(EmailTileBuilder)); + + final listReceiveAtTime = listEmailTile + .map((emailTile) => emailTile.presentationEmail.receivedAt?.value) + .whereNotNull() + .toList(); + + expect(listReceiveAtTime.isSortedByOldestFirst(), isTrue); + } +} \ No newline at end of file diff --git a/integration_test/scenarios/send_email_scenario.dart b/integration_test/scenarios/send_email_scenario.dart index 8d9184fa8..e0d5f6b8a 100644 --- a/integration_test/scenarios/send_email_scenario.dart +++ b/integration_test/scenarios/send_email_scenario.dart @@ -1,3 +1,6 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; + import '../base/base_scenario.dart'; import '../robots/composer_robot.dart'; import '../robots/thread_robot.dart'; @@ -27,7 +30,7 @@ class SendEmailScenario extends BaseScenario { await loginWithBasicAuthScenario.execute(); await threadRobot.openComposer(); - await threadRobot.expectComposerViewVisible(); + await _expectComposerViewVisible(); await composerRobot.grantContactPermission(); @@ -36,6 +39,13 @@ class SendEmailScenario extends BaseScenario { await composerRobot.addSubject(subject); await composerRobot.addContent(content); await composerRobot.sendEmail(); - await composerRobot.expectSendEmailSuccessToast(); + + await _expectSendEmailSuccessToast(); + } + + Future _expectComposerViewVisible() => expectViewVisible($(ComposerView)); + + Future _expectSendEmailSuccessToast() async { + expect($('Message has been sent successfully'), findsOneWidget); } } \ No newline at end of file diff --git a/integration_test/tests/search/search_email_with_sort_order_test.dart b/integration_test/tests/search/search_email_with_sort_order_test.dart new file mode 100644 index 000000000..1f6001e87 --- /dev/null +++ b/integration_test/tests/search/search_email_with_sort_order_test.dart @@ -0,0 +1,35 @@ +import '../../base/test_base.dart'; +import '../../scenarios/login_with_basic_auth_scenario.dart'; +import '../../scenarios/search_email_with_sort_order_scenario.dart'; + +void main() { + TestBase().runPatrolTest( + description: 'Should see list email displayed by sort order selected when search email successfully', + test: ($) async { + const username = String.fromEnvironment('USERNAME'); + const password = String.fromEnvironment('PASSWORD'); + const hostUrl = String.fromEnvironment('BASIC_AUTH_URL'); + const email = String.fromEnvironment('BASIC_AUTH_EMAIL'); + + final loginWithBasicAuthScenario = LoginWithBasicAuthScenario( + $, + username: username, + password: password, + hostUrl: hostUrl, + email: email, + ); + + const queryString = 'hello'; + const listUsername = ['Alice', 'Brian', 'Charlotte', 'David', 'Emma']; + + final searchEmailWithSortOrderScenario = SearchEmailWithSortOrderScenario( + $, + loginWithBasicAuthScenario: loginWithBasicAuthScenario, + queryString: queryString, + listUsername: listUsername + ); + + await searchEmailWithSortOrderScenario.execute(); + }, + ); +} \ No newline at end of file diff --git a/integration_test/utils/scenario_utils_mixin.dart b/integration_test/utils/scenario_utils_mixin.dart deleted file mode 100644 index 2b119c510..000000000 --- a/integration_test/utils/scenario_utils_mixin.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:patrol/patrol.dart'; - -mixin ScenarioUtilsMixin { - Future grantNotificationPermission(NativeAutomator nativeAutomator) async { - if (await nativeAutomator.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) { - await nativeAutomator.grantPermissionWhenInUse(); - } - } -} \ No newline at end of file diff --git a/lib/features/base/mixin/popup_context_menu_action_mixin.dart b/lib/features/base/mixin/popup_context_menu_action_mixin.dart index 9acdf7e97..5060baf55 100644 --- a/lib/features/base/mixin/popup_context_menu_action_mixin.dart +++ b/lib/features/base/mixin/popup_context_menu_action_mixin.dart @@ -9,8 +9,15 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; mixin PopupContextMenuActionMixin { - void openContextMenuAction(BuildContext context, List actionTiles, {Widget? cancelButton}) async { - await (CupertinoActionSheetBuilder(context) + void openContextMenuAction( + BuildContext context, + List actionTiles, + { + Widget? cancelButton, + Key? key, + } + ) async { + await (CupertinoActionSheetBuilder(context, key: key) ..addTiles(actionTiles) ..addCancelButton(cancelButton ?? buildCancelButton(context))) .show(); diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index b3ec9a78d..55a71420e 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -955,12 +955,15 @@ class ComposerController extends BaseController } Future _getContentInEditor() async { - final htmlTextEditor = PlatformInfo.isWeb - ? _textEditorWeb - : await htmlEditorApi?.getText(); - if (htmlTextEditor?.isNotEmpty == true) { - return htmlTextEditor!.removeEditorStartTag(); - } else { + try { + final htmlTextEditor = PlatformInfo.isWeb + ? _textEditorWeb + : await htmlEditorApi?.getText(); + return htmlTextEditor?.isNotEmpty == true + ? htmlTextEditor!.removeEditorStartTag() + : ''; + } catch (e) { + logError('ComposerController::_getContentInEditor:Exception = $e'); return ''; } } diff --git a/lib/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart b/lib/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart index a781452b2..23172312b 100644 --- a/lib/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart +++ b/lib/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart @@ -15,21 +15,25 @@ enum EmailSortOrderType { subjectDescending; String getTitle(BuildContext context) { + return getTitleByAppLocalizations(AppLocalizations.of(context)); + } + + String getTitleByAppLocalizations(AppLocalizations appLocalizations) { switch (this) { case EmailSortOrderType.mostRecent: - return AppLocalizations.of(context).mostRecent; + return appLocalizations.mostRecent; case EmailSortOrderType.oldest: - return AppLocalizations.of(context).oldest; + return appLocalizations.oldest; case EmailSortOrderType.relevance: - return AppLocalizations.of(context).relevance; + return appLocalizations.relevance; case EmailSortOrderType.senderAscending: - return AppLocalizations.of(context).senderAscending; + return appLocalizations.senderAscending; case EmailSortOrderType.senderDescending: - return AppLocalizations.of(context).senderDescending; + return appLocalizations.senderDescending; case EmailSortOrderType.subjectAscending: - return AppLocalizations.of(context).subjectAscending; + return appLocalizations.subjectAscending; case EmailSortOrderType.subjectDescending: - return AppLocalizations.of(context).subjectDescending; + return appLocalizations.subjectDescending; } } diff --git a/lib/features/search/email/presentation/search_email_view.dart b/lib/features/search/email/presentation/search_email_view.dart index 15d72ce66..d90cdc3a4 100644 --- a/lib/features/search/email/presentation/search_email_view.dart +++ b/lib/features/search/email/presentation/search_email_view.dart @@ -137,6 +137,7 @@ class SearchEmailView extends GetWidget onTap: () => controller.closeSearchView(context: context) ), Expanded(child: TextFieldBuilder( + key: const Key('search_email_text_field'), onTextChange: controller.onTextSearchChange, textInputAction: TextInputAction.search, controller: controller.textInputSearchController, @@ -190,6 +191,7 @@ class SearchEmailView extends GetWidget ), scrollController: controller.listSearchFilterScrollController, child: ListView( + key: const Key('search_filter_list_view'), scrollDirection: Axis.horizontal, shrinkWrap: true, controller: controller.listSearchFilterScrollController, @@ -375,7 +377,8 @@ class SearchEmailView extends GetWidget context, controller.emailSortOrderType.value, controller.selectSortOrderQuickSearchFilter - ) + ), + key: const Key('sort_filter_context_menu') ); } @@ -562,6 +565,7 @@ class SearchEmailView extends GetWidget List listSuggestionSearch ) { return ListView.builder( + key: const Key('suggestion_search_list_view'), shrinkWrap: true, primary: false, itemCount: listSuggestionSearch.length, @@ -618,6 +622,7 @@ class SearchEmailView extends GetWidget Widget _buildListEmailBody(BuildContext context, List listPresentationEmail) { return NotificationListener( + key: const Key('search_email_list_notification_listener'), onNotification: (ScrollNotification scrollInfo) { if (scrollInfo is ScrollEndNotification && controller.searchMoreState != SearchMoreState.waiting diff --git a/provisioning/integration_test/search_email_with_sort_order/eml/0.eml b/provisioning/integration_test/search_email_with_sort_order/eml/0.eml new file mode 100644 index 000000000..f9061eec4 --- /dev/null +++ b/provisioning/integration_test/search_email_with_sort_order/eml/0.eml @@ -0,0 +1,40 @@ +Return-Path: +MIME-Version: 1.0 +References: +Subject: Alice send Bob +From: alice@example.com +To: "bob@example.com" +Reply-To: alice@example.com +Date: Tue, 29 Oct 2024 04:13:50 +0000 +Message-ID: +User-Agent: Twake-Mail/0.13.2 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; + rv:131.0) Gecko/20100101 Firefox/131.0 +Content-Type: multipart/alternative; + boundary="-=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-" + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + +Hello Bob. + +Invite you in to a meeting: Event OPEN TECH TALK: INNOVATION FOR THE TWAKE WORKPLACE + +Our 3rd Open Tech Talk in 2024! + + ► Topic: "Enhancing Multi-Tenancy Security" + ► Topic: "Bringing Desktop Synchronization into TDrive" + ► Topic: "Websocket: Real-time Update + +We are waiting for you! + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=--- diff --git a/provisioning/integration_test/search_email_with_sort_order/eml/1.eml b/provisioning/integration_test/search_email_with_sort_order/eml/1.eml new file mode 100644 index 000000000..4586e5425 --- /dev/null +++ b/provisioning/integration_test/search_email_with_sort_order/eml/1.eml @@ -0,0 +1,40 @@ +Return-Path: +MIME-Version: 1.0 +References: +Subject: Brian send Bob +From: brian@example.com +To: "bob@example.com" +Reply-To: brian@example.com +Date: Wed, 30 Oct 2024 08:13:50 +0000 +Message-ID: +User-Agent: Twake-Mail/0.13.2 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; + rv:131.0) Gecko/20100101 Firefox/131.0 +Content-Type: multipart/alternative; + boundary="-=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-" + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + +Hello Bob. + +Invite you in to a meeting: Event OPEN TECH TALK: INNOVATION FOR THE TWAKE WORKPLACE + +Our 3rd Open Tech Talk in 2024! + + ► Topic: "Enhancing Multi-Tenancy Security" + ► Topic: "Bringing Desktop Synchronization into TDrive" + ► Topic: "Websocket: Real-time Update + +We are waiting for you! + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=--- diff --git a/provisioning/integration_test/search_email_with_sort_order/eml/2.eml b/provisioning/integration_test/search_email_with_sort_order/eml/2.eml new file mode 100644 index 000000000..f71f64a24 --- /dev/null +++ b/provisioning/integration_test/search_email_with_sort_order/eml/2.eml @@ -0,0 +1,40 @@ +Return-Path: +MIME-Version: 1.0 +References: +Subject: Charlotte send Bob +From: charlotte@example.com +To: "bob@example.com" +Reply-To: charlotte@example.com +Date: Thu, 31 Oct 2024 12:13:50 +0000 +Message-ID: +User-Agent: Twake-Mail/0.13.2 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; + rv:131.0) Gecko/20100101 Firefox/131.0 +Content-Type: multipart/alternative; + boundary="-=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-" + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + +Hello Bob. + +Invite you in to a meeting: Event OPEN TECH TALK: INNOVATION FOR THE TWAKE WORKPLACE + +Our 3rd Open Tech Talk in 2024! + + ► Topic: "Enhancing Multi-Tenancy Security" + ► Topic: "Bringing Desktop Synchronization into TDrive" + ► Topic: "Websocket: Real-time Update + +We are waiting for you! + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=--- diff --git a/provisioning/integration_test/search_email_with_sort_order/eml/3.eml b/provisioning/integration_test/search_email_with_sort_order/eml/3.eml new file mode 100644 index 000000000..23282777d --- /dev/null +++ b/provisioning/integration_test/search_email_with_sort_order/eml/3.eml @@ -0,0 +1,40 @@ +Return-Path: +MIME-Version: 1.0 +References: +Subject: David send Bob +From: david@example.com +To: "bob@example.com" +Reply-To: david@example.com +Date: Fri, 1 Nov 2024 07:13:50 +0000 +Message-ID: +User-Agent: Twake-Mail/0.13.2 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; + rv:131.0) Gecko/20100101 Firefox/131.0 +Content-Type: multipart/alternative; + boundary="-=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-" + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + +Hello Bob. + +Invite you in to a meeting: Event OPEN TECH TALK: INNOVATION FOR THE TWAKE WORKPLACE + +Our 3rd Open Tech Talk in 2024! + + ► Topic: "Enhancing Multi-Tenancy Security" + ► Topic: "Bringing Desktop Synchronization into TDrive" + ► Topic: "Websocket: Real-time Update + +We are waiting for you! + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=--- diff --git a/provisioning/integration_test/search_email_with_sort_order/eml/4.eml b/provisioning/integration_test/search_email_with_sort_order/eml/4.eml new file mode 100644 index 000000000..815535ee8 --- /dev/null +++ b/provisioning/integration_test/search_email_with_sort_order/eml/4.eml @@ -0,0 +1,40 @@ +Return-Path: +MIME-Version: 1.0 +References: +Subject: Emma send Bob +From: emma@example.com +To: "bob@example.com" +Reply-To: emma@example.com +Date: Sat, 2 Nov 2024 13:13:50 +0000 +Message-ID: +User-Agent: Twake-Mail/0.13.2 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; + rv:131.0) Gecko/20100101 Firefox/131.0 +Content-Type: multipart/alternative; + boundary="-=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=-" + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + +Hello Bob. + +Invite you in to a meeting: Event OPEN TECH TALK: INNOVATION FOR THE TWAKE WORKPLACE + +Our 3rd Open Tech Talk in 2024! + + ► Topic: "Enhancing Multi-Tenancy Security" + ► Topic: "Bringing Desktop Synchronization into TDrive" + ► Topic: "Websocket: Real-time Update + +We are waiting for you! + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT +Content-Language: en-US + + +---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=--- diff --git a/provisioning/integration_test/search_email_with_sort_order/provisioning.sh b/provisioning/integration_test/search_email_with_sort_order/provisioning.sh new file mode 100755 index 000000000..e09ff2200 --- /dev/null +++ b/provisioning/integration_test/search_email_with_sort_order/provisioning.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Define users and folders +users=("alice" "bob" "brian" "charlotte" "david" "emma") + +# Add users +for user in "${users[@]}"; do + james-cli AddUser "$user@example.com" "$user" +done + +# Create search folder for user Bob +james-cli CreateMailbox \#private "bob@example.com" "search" + +# Import emails into search folder for user Bob +for eml in {0..4}; do + echo "Importing $eml.eml into search folder for user bob" + james-cli ImportEml \#private "bob@example.com" "search" "/root/conf/integration_test/search_email_with_sort_order/eml/$eml.eml" & +done diff --git a/scripts/patrol-integration-test-with-docker.sh b/scripts/patrol-integration-test-with-docker.sh index 5f0065930..244248cbc 100755 --- a/scripts/patrol-integration-test-with-docker.sh +++ b/scripts/patrol-integration-test-with-docker.sh @@ -40,8 +40,8 @@ done export BOB="bob" export ALICE="alice" export DOMAIN="example.com" -docker exec tmail-backend james-cli AddUser "$BOB@$DOMAIN" "$BOB" -docker exec tmail-backend james-cli AddUser "$ALICE@$DOMAIN" "$ALICE" + +docker exec tmail-backend ./root/conf/integration_test/search_email_with_sort_order/provisioning.sh cd .. diff --git a/scripts/patrol-local-integration-test-with-docker.sh b/scripts/patrol-local-integration-test-with-docker.sh index 90f331d83..72b8c589d 100755 --- a/scripts/patrol-local-integration-test-with-docker.sh +++ b/scripts/patrol-local-integration-test-with-docker.sh @@ -40,8 +40,8 @@ done export BOB="bob" export ALICE="alice" export DOMAIN="example.com" -docker exec tmail-backend james-cli AddUser "$BOB@$DOMAIN" "$BOB" -docker exec tmail-backend james-cli AddUser "$ALICE@$DOMAIN" "$ALICE" + +docker exec tmail-backend ./root/conf/integration_test/search_email_with_sort_order/provisioning.sh cd ..