TF-2528 Add integration test for search email with sort by sender nam…e ascending
This commit is contained in:
@@ -10,6 +10,8 @@ services:
|
|||||||
- ./mailetcontainer.xml:/root/conf/mailetcontainer.xml
|
- ./mailetcontainer.xml:/root/conf/mailetcontainer.xml
|
||||||
- ./imapserver.xml:/root/conf/imapserver.xml
|
- ./imapserver.xml:/root/conf/imapserver.xml
|
||||||
- ./jmap.properties:/root/conf/jmap.properties
|
- ./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:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export 'presentation/extensions/capitalize_extension.dart';
|
|||||||
export 'presentation/extensions/list_extensions.dart';
|
export 'presentation/extensions/list_extensions.dart';
|
||||||
export 'presentation/extensions/list_nullable_extensions.dart';
|
export 'presentation/extensions/list_nullable_extensions.dart';
|
||||||
export 'domain/extensions/datetime_extension.dart';
|
export 'domain/extensions/datetime_extension.dart';
|
||||||
|
export 'domain/extensions/list_datetime_extension.dart';
|
||||||
export 'presentation/extensions/html_extension.dart';
|
export 'presentation/extensions/html_extension.dart';
|
||||||
export 'presentation/extensions/compare_string_extension.dart';
|
export 'presentation/extensions/compare_string_extension.dart';
|
||||||
export 'presentation/extensions/compare_list_extensions.dart';
|
export 'presentation/extensions/compare_list_extensions.dart';
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
extension ListDateTimeExtension on List<DateTime> {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,13 +6,14 @@ import 'package:pointer_interceptor/pointer_interceptor.dart';
|
|||||||
class CupertinoActionSheetBuilder {
|
class CupertinoActionSheetBuilder {
|
||||||
|
|
||||||
final BuildContext _context;
|
final BuildContext _context;
|
||||||
|
final Key? key;
|
||||||
final List<Widget> _actionTiles = [];
|
final List<Widget> _actionTiles = [];
|
||||||
|
|
||||||
Widget? _titleWidget;
|
Widget? _titleWidget;
|
||||||
Widget? _messageWidget;
|
Widget? _messageWidget;
|
||||||
Widget? _cancelWidget;
|
Widget? _cancelWidget;
|
||||||
|
|
||||||
CupertinoActionSheetBuilder(this._context);
|
CupertinoActionSheetBuilder(this._context, {this.key});
|
||||||
|
|
||||||
void title(Widget? titleWidget) {
|
void title(Widget? titleWidget) {
|
||||||
_titleWidget = titleWidget;
|
_titleWidget = titleWidget;
|
||||||
@@ -35,6 +36,7 @@ class CupertinoActionSheetBuilder {
|
|||||||
context: _context,
|
context: _context,
|
||||||
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
|
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
|
||||||
builder: (context) => PointerInterceptor(child: CupertinoActionSheet(
|
builder: (context) => PointerInterceptor(child: CupertinoActionSheet(
|
||||||
|
key: key,
|
||||||
title: _titleWidget,
|
title: _titleWidget,
|
||||||
message: _messageWidget,
|
message: _messageWidget,
|
||||||
actions: _actionTiles,
|
actions: _actionTiles,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:patrol/patrol.dart';
|
import 'package:patrol/patrol.dart';
|
||||||
|
|
||||||
abstract class BaseScenario {
|
abstract class BaseScenario {
|
||||||
@@ -6,4 +7,9 @@ abstract class BaseScenario {
|
|||||||
const BaseScenario(this.$);
|
const BaseScenario(this.$);
|
||||||
|
|
||||||
Future<void> execute();
|
Future<void> execute();
|
||||||
|
|
||||||
|
Future<void> expectViewVisible(PatrolFinder patrolFinder) async {
|
||||||
|
await $.waitUntilVisible(patrolFinder);
|
||||||
|
expect(patrolFinder, findsWidgets);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:patrol/patrol.dart';
|
import 'package:patrol/patrol.dart';
|
||||||
|
|
||||||
abstract class CoreRobot {
|
abstract class CoreRobot {
|
||||||
@@ -6,10 +5,5 @@ abstract class CoreRobot {
|
|||||||
|
|
||||||
CoreRobot(this.$);
|
CoreRobot(this.$);
|
||||||
|
|
||||||
Future<void> ensureViewVisible(PatrolFinder patrolFinder) async {
|
|
||||||
await $.waitUntilVisible(patrolFinder);
|
|
||||||
expect(patrolFinder, findsWidgets);
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamic ignoreException() => $.tester.takeException();
|
dynamic ignoreException() => $.tester.takeException();
|
||||||
}
|
}
|
||||||
@@ -50,10 +50,6 @@ class ComposerRobot extends CoreRobot {
|
|||||||
.tap();
|
.tap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> expectSendEmailSuccessToast() async {
|
|
||||||
expect($('Message has been sent successfully'), findsOneWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> grantContactPermission() async {
|
Future<void> grantContactPermission() async {
|
||||||
if (await $.native.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) {
|
if (await $.native.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) {
|
||||||
await $.native.grantPermissionWhenInUse();
|
await $.native.grantPermissionWhenInUse();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
import 'package:core/presentation/views/text/type_ahead_form_field_builder.dart';
|
||||||
import 'package:flutter/material.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/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/login_view.dart';
|
||||||
import 'package:tmail_ui_user/features/login/presentation/widgets/login_text_input_builder.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 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
|
||||||
import '../base/core_robot.dart';
|
import '../base/core_robot.dart';
|
||||||
@@ -11,15 +11,17 @@ import '../base/core_robot.dart';
|
|||||||
class LoginRobot extends CoreRobot {
|
class LoginRobot extends CoreRobot {
|
||||||
LoginRobot(super.$);
|
LoginRobot(super.$);
|
||||||
|
|
||||||
Future<void> expectWelcomeViewVisible() => ensureViewVisible($(TwakeWelcomeView));
|
Future<void> grantNotificationPermission(NativeAutomator nativeAutomator) async {
|
||||||
|
if (await nativeAutomator.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) {
|
||||||
|
await nativeAutomator.grantPermissionWhenInUse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> tapOnUseCompanyServer() async {
|
Future<void> tapOnUseCompanyServer() async {
|
||||||
await $.pumpAndSettle();
|
await $.pumpAndSettle();
|
||||||
await $(AppLocalizations().useCompanyServer).tap();
|
await $(AppLocalizations().useCompanyServer).tap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> expectLoginViewVisible() => ensureViewVisible($(LoginView));
|
|
||||||
|
|
||||||
Future<void> enterEmail(String email) async {
|
Future<void> enterEmail(String email) async {
|
||||||
final finder = $(LoginView).$(TextField);
|
final finder = $(LoginView).$(TextField);
|
||||||
await finder.enterText(email);
|
await finder.enterText(email);
|
||||||
|
|||||||
@@ -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<void> enterQueryString(String queryString) async {
|
||||||
|
await $(#search_email_text_field).enterText(queryString);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> scrollToEndListSearchFilter() async {
|
||||||
|
await $.scrollUntilVisible(
|
||||||
|
finder: $(#mobile_sortBy_search_filter_button),
|
||||||
|
view: $(#search_filter_list_view),
|
||||||
|
scrollDirection: AxisDirection.right,
|
||||||
|
delta: 300,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openSortOrderBottomDialog() async {
|
||||||
|
await $(#mobile_sortBy_search_filter_button).tap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectSortOrder(String sortOrderName) async {
|
||||||
|
await $(find.text(sortOrderName)).tap();
|
||||||
|
await $.pump(const Duration(seconds: 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
|
import 'package:core/presentation/views/search/search_bar_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tmail_ui_user/features/base/widget/compose_floating_button.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';
|
import '../base/core_robot.dart';
|
||||||
|
|
||||||
class ThreadRobot extends CoreRobot {
|
class ThreadRobot extends CoreRobot {
|
||||||
ThreadRobot(super.$);
|
ThreadRobot(super.$);
|
||||||
|
|
||||||
Future<void> expectThreadViewVisible() => ensureViewVisible($(ThreadView));
|
|
||||||
|
|
||||||
Future<void> openComposer() async {
|
Future<void> openComposer() async {
|
||||||
await $(ComposeFloatingButton).$(InkWell).tap();
|
await $(ComposeFloatingButton).$(InkWell).tap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> expectComposerViewVisible() => ensureViewVisible($(ComposerView));
|
Future<void> openSearchView() async {
|
||||||
|
await $(SearchBarView).$(InkWell).tap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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 '../base/base_scenario.dart';
|
||||||
import '../robots/login_robot.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(
|
const LoginWithBasicAuthScenario(
|
||||||
super.$,
|
super.$,
|
||||||
{
|
{
|
||||||
@@ -22,12 +24,12 @@ class LoginWithBasicAuthScenario extends BaseScenario with ScenarioUtilsMixin {
|
|||||||
@override
|
@override
|
||||||
Future<void> execute() async {
|
Future<void> execute() async {
|
||||||
final loginRobot = LoginRobot($);
|
final loginRobot = LoginRobot($);
|
||||||
final threadRobot = ThreadRobot($);
|
|
||||||
|
|
||||||
await loginRobot.expectWelcomeViewVisible();
|
await _expectWelcomeViewVisible();
|
||||||
|
|
||||||
await loginRobot.tapOnUseCompanyServer();
|
await loginRobot.tapOnUseCompanyServer();
|
||||||
|
await _expectLoginViewVisible();
|
||||||
|
|
||||||
await loginRobot.expectLoginViewVisible();
|
|
||||||
await loginRobot.enterEmail(username);
|
await loginRobot.enterEmail(username);
|
||||||
await loginRobot.enterHostUrl(hostUrl);
|
await loginRobot.enterHostUrl(hostUrl);
|
||||||
|
|
||||||
@@ -35,8 +37,14 @@ class LoginWithBasicAuthScenario extends BaseScenario with ScenarioUtilsMixin {
|
|||||||
await loginRobot.enterBasicAuthPassword(password);
|
await loginRobot.enterBasicAuthPassword(password);
|
||||||
await loginRobot.loginBasicAuth();
|
await loginRobot.loginBasicAuth();
|
||||||
|
|
||||||
await grantNotificationPermission($.native);
|
await loginRobot.grantNotificationPermission($.native);
|
||||||
|
|
||||||
await threadRobot.expectThreadViewVisible();
|
await _expectThreadViewVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _expectWelcomeViewVisible() => expectViewVisible($(TwakeWelcomeView));
|
||||||
|
|
||||||
|
Future<void> _expectLoginViewVisible() => expectViewVisible($(LoginView));
|
||||||
|
|
||||||
|
Future<void> _expectThreadViewVisible() => expectViewVisible($(ThreadView));
|
||||||
}
|
}
|
||||||
@@ -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<String> listUsername;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> 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<void> _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<void> _expectSearchViewVisible() async {
|
||||||
|
await expectViewVisible($(SearchEmailView));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectSuggestionSearchListViewVisible() async {
|
||||||
|
await expectViewVisible($(#suggestion_search_list_view));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectSortBySearchFilterButtonVisible() async {
|
||||||
|
await expectViewVisible($(#mobile_sortBy_search_filter_button));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectSortFilterContextMenuVisible() async {
|
||||||
|
await expectViewVisible($(#sort_filter_context_menu));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectSearchResultEmailListVisible() async {
|
||||||
|
await expectViewVisible($(#search_email_list_notification_listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectEmailListDisplayedCorrectly({
|
||||||
|
required List<String> listUsername,
|
||||||
|
}) async {
|
||||||
|
expect(find.byType(EmailTileBuilder), findsNWidgets(listUsername.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectEmailListSortedCorrectBySenderAscending({
|
||||||
|
required List<String> listUsername,
|
||||||
|
}) async {
|
||||||
|
final listEmailTile = $.tester
|
||||||
|
.widgetList<EmailTileBuilder>(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<void> _expectEmailListSortedCorrectBySenderDescending({
|
||||||
|
required List<String> listUsername,
|
||||||
|
}) async {
|
||||||
|
final listEmailTile = $.tester
|
||||||
|
.widgetList<EmailTileBuilder>(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<void> _expectEmailListSortedCorrectBySubjectAscending({
|
||||||
|
required List<String> listUsername,
|
||||||
|
}) async {
|
||||||
|
final listEmailTile = $.tester
|
||||||
|
.widgetList<EmailTileBuilder>(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<void> _expectEmailListSortedCorrectBySubjectDescending({
|
||||||
|
required List<String> listUsername,
|
||||||
|
}) async {
|
||||||
|
final listEmailTile = $.tester
|
||||||
|
.widgetList<EmailTileBuilder>(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<void> _expectEmailListSortedCorrectByMostRecent() async {
|
||||||
|
final listEmailTile = $.tester
|
||||||
|
.widgetList<EmailTileBuilder>(find.byType(EmailTileBuilder));
|
||||||
|
|
||||||
|
final listReceiveAtTime = listEmailTile
|
||||||
|
.map((emailTile) => emailTile.presentationEmail.receivedAt?.value)
|
||||||
|
.whereNotNull()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
expect(listReceiveAtTime.isSortedByMostRecent(), isTrue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectEmailListSortedCorrectByOldest() async {
|
||||||
|
final listEmailTile = $.tester
|
||||||
|
.widgetList<EmailTileBuilder>(find.byType(EmailTileBuilder));
|
||||||
|
|
||||||
|
final listReceiveAtTime = listEmailTile
|
||||||
|
.map((emailTile) => emailTile.presentationEmail.receivedAt?.value)
|
||||||
|
.whereNotNull()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
expect(listReceiveAtTime.isSortedByOldestFirst(), isTrue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 '../base/base_scenario.dart';
|
||||||
import '../robots/composer_robot.dart';
|
import '../robots/composer_robot.dart';
|
||||||
import '../robots/thread_robot.dart';
|
import '../robots/thread_robot.dart';
|
||||||
@@ -27,7 +30,7 @@ class SendEmailScenario extends BaseScenario {
|
|||||||
await loginWithBasicAuthScenario.execute();
|
await loginWithBasicAuthScenario.execute();
|
||||||
|
|
||||||
await threadRobot.openComposer();
|
await threadRobot.openComposer();
|
||||||
await threadRobot.expectComposerViewVisible();
|
await _expectComposerViewVisible();
|
||||||
|
|
||||||
await composerRobot.grantContactPermission();
|
await composerRobot.grantContactPermission();
|
||||||
|
|
||||||
@@ -36,6 +39,13 @@ class SendEmailScenario extends BaseScenario {
|
|||||||
await composerRobot.addSubject(subject);
|
await composerRobot.addSubject(subject);
|
||||||
await composerRobot.addContent(content);
|
await composerRobot.addContent(content);
|
||||||
await composerRobot.sendEmail();
|
await composerRobot.sendEmail();
|
||||||
await composerRobot.expectSendEmailSuccessToast();
|
|
||||||
|
await _expectSendEmailSuccessToast();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _expectComposerViewVisible() => expectViewVisible($(ComposerView));
|
||||||
|
|
||||||
|
Future<void> _expectSendEmailSuccessToast() async {
|
||||||
|
expect($('Message has been sent successfully'), findsOneWidget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import 'package:patrol/patrol.dart';
|
|
||||||
|
|
||||||
mixin ScenarioUtilsMixin {
|
|
||||||
Future<void> grantNotificationPermission(NativeAutomator nativeAutomator) async {
|
|
||||||
if (await nativeAutomator.isPermissionDialogVisible(timeout: const Duration(seconds: 5))) {
|
|
||||||
await nativeAutomator.grantPermissionWhenInUse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,15 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
|||||||
|
|
||||||
mixin PopupContextMenuActionMixin {
|
mixin PopupContextMenuActionMixin {
|
||||||
|
|
||||||
void openContextMenuAction(BuildContext context, List<Widget> actionTiles, {Widget? cancelButton}) async {
|
void openContextMenuAction(
|
||||||
await (CupertinoActionSheetBuilder(context)
|
BuildContext context,
|
||||||
|
List<Widget> actionTiles,
|
||||||
|
{
|
||||||
|
Widget? cancelButton,
|
||||||
|
Key? key,
|
||||||
|
}
|
||||||
|
) async {
|
||||||
|
await (CupertinoActionSheetBuilder(context, key: key)
|
||||||
..addTiles(actionTiles)
|
..addTiles(actionTiles)
|
||||||
..addCancelButton(cancelButton ?? buildCancelButton(context)))
|
..addCancelButton(cancelButton ?? buildCancelButton(context)))
|
||||||
.show();
|
.show();
|
||||||
|
|||||||
@@ -955,12 +955,15 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _getContentInEditor() async {
|
Future<String> _getContentInEditor() async {
|
||||||
final htmlTextEditor = PlatformInfo.isWeb
|
try {
|
||||||
? _textEditorWeb
|
final htmlTextEditor = PlatformInfo.isWeb
|
||||||
: await htmlEditorApi?.getText();
|
? _textEditorWeb
|
||||||
if (htmlTextEditor?.isNotEmpty == true) {
|
: await htmlEditorApi?.getText();
|
||||||
return htmlTextEditor!.removeEditorStartTag();
|
return htmlTextEditor?.isNotEmpty == true
|
||||||
} else {
|
? htmlTextEditor!.removeEditorStartTag()
|
||||||
|
: '';
|
||||||
|
} catch (e) {
|
||||||
|
logError('ComposerController::_getContentInEditor:Exception = $e');
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-7
@@ -15,21 +15,25 @@ enum EmailSortOrderType {
|
|||||||
subjectDescending;
|
subjectDescending;
|
||||||
|
|
||||||
String getTitle(BuildContext context) {
|
String getTitle(BuildContext context) {
|
||||||
|
return getTitleByAppLocalizations(AppLocalizations.of(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTitleByAppLocalizations(AppLocalizations appLocalizations) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case EmailSortOrderType.mostRecent:
|
case EmailSortOrderType.mostRecent:
|
||||||
return AppLocalizations.of(context).mostRecent;
|
return appLocalizations.mostRecent;
|
||||||
case EmailSortOrderType.oldest:
|
case EmailSortOrderType.oldest:
|
||||||
return AppLocalizations.of(context).oldest;
|
return appLocalizations.oldest;
|
||||||
case EmailSortOrderType.relevance:
|
case EmailSortOrderType.relevance:
|
||||||
return AppLocalizations.of(context).relevance;
|
return appLocalizations.relevance;
|
||||||
case EmailSortOrderType.senderAscending:
|
case EmailSortOrderType.senderAscending:
|
||||||
return AppLocalizations.of(context).senderAscending;
|
return appLocalizations.senderAscending;
|
||||||
case EmailSortOrderType.senderDescending:
|
case EmailSortOrderType.senderDescending:
|
||||||
return AppLocalizations.of(context).senderDescending;
|
return appLocalizations.senderDescending;
|
||||||
case EmailSortOrderType.subjectAscending:
|
case EmailSortOrderType.subjectAscending:
|
||||||
return AppLocalizations.of(context).subjectAscending;
|
return appLocalizations.subjectAscending;
|
||||||
case EmailSortOrderType.subjectDescending:
|
case EmailSortOrderType.subjectDescending:
|
||||||
return AppLocalizations.of(context).subjectDescending;
|
return appLocalizations.subjectDescending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ class SearchEmailView extends GetWidget<SearchEmailController>
|
|||||||
onTap: () => controller.closeSearchView(context: context)
|
onTap: () => controller.closeSearchView(context: context)
|
||||||
),
|
),
|
||||||
Expanded(child: TextFieldBuilder(
|
Expanded(child: TextFieldBuilder(
|
||||||
|
key: const Key('search_email_text_field'),
|
||||||
onTextChange: controller.onTextSearchChange,
|
onTextChange: controller.onTextSearchChange,
|
||||||
textInputAction: TextInputAction.search,
|
textInputAction: TextInputAction.search,
|
||||||
controller: controller.textInputSearchController,
|
controller: controller.textInputSearchController,
|
||||||
@@ -190,6 +191,7 @@ class SearchEmailView extends GetWidget<SearchEmailController>
|
|||||||
),
|
),
|
||||||
scrollController: controller.listSearchFilterScrollController,
|
scrollController: controller.listSearchFilterScrollController,
|
||||||
child: ListView(
|
child: ListView(
|
||||||
|
key: const Key('search_filter_list_view'),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: controller.listSearchFilterScrollController,
|
controller: controller.listSearchFilterScrollController,
|
||||||
@@ -375,7 +377,8 @@ class SearchEmailView extends GetWidget<SearchEmailController>
|
|||||||
context,
|
context,
|
||||||
controller.emailSortOrderType.value,
|
controller.emailSortOrderType.value,
|
||||||
controller.selectSortOrderQuickSearchFilter
|
controller.selectSortOrderQuickSearchFilter
|
||||||
)
|
),
|
||||||
|
key: const Key('sort_filter_context_menu')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,6 +565,7 @@ class SearchEmailView extends GetWidget<SearchEmailController>
|
|||||||
List<PresentationEmail> listSuggestionSearch
|
List<PresentationEmail> listSuggestionSearch
|
||||||
) {
|
) {
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
|
key: const Key('suggestion_search_list_view'),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
primary: false,
|
primary: false,
|
||||||
itemCount: listSuggestionSearch.length,
|
itemCount: listSuggestionSearch.length,
|
||||||
@@ -618,6 +622,7 @@ class SearchEmailView extends GetWidget<SearchEmailController>
|
|||||||
|
|
||||||
Widget _buildListEmailBody(BuildContext context, List<PresentationEmail> listPresentationEmail) {
|
Widget _buildListEmailBody(BuildContext context, List<PresentationEmail> listPresentationEmail) {
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
|
key: const Key('search_email_list_notification_listener'),
|
||||||
onNotification: (ScrollNotification scrollInfo) {
|
onNotification: (ScrollNotification scrollInfo) {
|
||||||
if (scrollInfo is ScrollEndNotification
|
if (scrollInfo is ScrollEndNotification
|
||||||
&& controller.searchMoreState != SearchMoreState.waiting
|
&& controller.searchMoreState != SearchMoreState.waiting
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Return-Path: <alice@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
References: <Mime4j.4c.b3b452d18f0d0ef9.192d67aaafc@example.com>
|
||||||
|
Subject: Alice send Bob
|
||||||
|
From: alice@example.com
|
||||||
|
To: "bob@example.com" <bob@example.com>
|
||||||
|
Reply-To: alice@example.com
|
||||||
|
Date: Tue, 29 Oct 2024 04:13:50 +0000
|
||||||
|
Message-ID: <Mime4j.4e.728b2b72b23cc182.192d67ae03c@example.com>
|
||||||
|
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=---
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Return-Path: <brian@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
References: <Mime4j.4c.b3b452d18f0d0ef9.192d67aaafc@example.com>
|
||||||
|
Subject: Brian send Bob
|
||||||
|
From: brian@example.com
|
||||||
|
To: "bob@example.com" <bob@example.com>
|
||||||
|
Reply-To: brian@example.com
|
||||||
|
Date: Wed, 30 Oct 2024 08:13:50 +0000
|
||||||
|
Message-ID: <Mime4j.4e.728b2b72b23cc182.192d67ae03c@example.com>
|
||||||
|
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=---
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Return-Path: <charlotte@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
References: <Mime4j.4c.b3b452d18f0d0ef9.192d67aaafc@example.com>
|
||||||
|
Subject: Charlotte send Bob
|
||||||
|
From: charlotte@example.com
|
||||||
|
To: "bob@example.com" <bob@example.com>
|
||||||
|
Reply-To: charlotte@example.com
|
||||||
|
Date: Thu, 31 Oct 2024 12:13:50 +0000
|
||||||
|
Message-ID: <Mime4j.4e.728b2b72b23cc182.192d67ae03c@example.com>
|
||||||
|
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=---
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Return-Path: <david@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
References: <Mime4j.4c.b3b452d18f0d0ef9.192d67aaafc@example.com>
|
||||||
|
Subject: David send Bob
|
||||||
|
From: david@example.com
|
||||||
|
To: "bob@example.com" <bob@example.com>
|
||||||
|
Reply-To: david@example.com
|
||||||
|
Date: Fri, 1 Nov 2024 07:13:50 +0000
|
||||||
|
Message-ID: <Mime4j.4e.728b2b72b23cc182.192d67ae03c@example.com>
|
||||||
|
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=---
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
Return-Path: <emma@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
References: <Mime4j.4c.b3b452d18f0d0ef9.192d67aaafc@example.com>
|
||||||
|
Subject: Emma send Bob
|
||||||
|
From: emma@example.com
|
||||||
|
To: "bob@example.com" <bob@example.com>
|
||||||
|
Reply-To: emma@example.com
|
||||||
|
Date: Sat, 2 Nov 2024 13:13:50 +0000
|
||||||
|
Message-ID: <Mime4j.4e.728b2b72b23cc182.192d67ae03c@example.com>
|
||||||
|
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=---
|
||||||
@@ -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
|
||||||
@@ -40,8 +40,8 @@ done
|
|||||||
export BOB="bob"
|
export BOB="bob"
|
||||||
export ALICE="alice"
|
export ALICE="alice"
|
||||||
export DOMAIN="example.com"
|
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 ..
|
cd ..
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ done
|
|||||||
export BOB="bob"
|
export BOB="bob"
|
||||||
export ALICE="alice"
|
export ALICE="alice"
|
||||||
export DOMAIN="example.com"
|
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 ..
|
cd ..
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user