TF-3236 Add integration test to search snippet
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
class ProvisioningEmail {
|
||||||
|
final String toEmail;
|
||||||
|
final String subject;
|
||||||
|
final String content;
|
||||||
|
final List<String> attachmentPaths;
|
||||||
|
|
||||||
|
ProvisioningEmail({
|
||||||
|
required this.toEmail,
|
||||||
|
required this.subject,
|
||||||
|
required this.content,
|
||||||
|
this.attachmentPaths = const [],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:core/presentation/views/text/text_field_builder.dart';
|
||||||
|
import 'package:tmail_ui_user/features/search/email/presentation/search_email_view.dart';
|
||||||
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
|
||||||
import '../base/core_robot.dart';
|
import '../base/core_robot.dart';
|
||||||
|
|
||||||
@@ -27,4 +30,13 @@ class SearchRobot extends CoreRobot {
|
|||||||
await $(find.text(sortOrderName)).tap();
|
await $(find.text(sortOrderName)).tap();
|
||||||
await $.pump(const Duration(seconds: 2));
|
await $.pump(const Duration(seconds: 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> enterKeyword(String keyword) async {
|
||||||
|
await $(SearchEmailView).$(TextFieldBuilder).enterText(keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapOnShowAllResultsText() async {
|
||||||
|
await $.waitUntilVisible($(AppLocalizations().showingResultsFor));
|
||||||
|
await $(AppLocalizations().showingResultsFor).tap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:core/presentation/views/search/search_bar_view.dart';
|
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/thread/presentation/thread_view.dart';
|
||||||
|
|
||||||
import '../base/core_robot.dart';
|
import '../base/core_robot.dart';
|
||||||
|
|
||||||
@@ -14,4 +15,8 @@ class ThreadRobot extends CoreRobot {
|
|||||||
Future<void> openSearchView() async {
|
Future<void> openSearchView() async {
|
||||||
await $(SearchBarView).$(InkWell).tap();
|
await $(SearchBarView).$(InkWell).tap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapOnSearchField() async {
|
||||||
|
await $(ThreadView).$(SearchBarView).tap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:core/core.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../base/base_scenario.dart';
|
||||||
|
import '../models/provisioning_email.dart';
|
||||||
|
import '../robots/search_robot.dart';
|
||||||
|
import '../robots/thread_robot.dart';
|
||||||
|
import '../utils/scenario_utils_mixin.dart';
|
||||||
|
import 'login_with_basic_auth_scenario.dart';
|
||||||
|
|
||||||
|
class SearchResultHighlightsScenario extends BaseScenario with ScenarioUtilsMixin {
|
||||||
|
SearchResultHighlightsScenario(
|
||||||
|
super.$, {
|
||||||
|
required this.loginWithBasicAuthScenario,
|
||||||
|
required this.keyword,
|
||||||
|
required this.longEmailContents,
|
||||||
|
});
|
||||||
|
|
||||||
|
final LoginWithBasicAuthScenario loginWithBasicAuthScenario;
|
||||||
|
final String keyword;
|
||||||
|
final List<String> longEmailContents;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> execute() async {
|
||||||
|
// Robots
|
||||||
|
final threadRobot = ThreadRobot($);
|
||||||
|
final searchRobot = SearchRobot($);
|
||||||
|
|
||||||
|
// Login
|
||||||
|
await loginWithBasicAuthScenario.execute();
|
||||||
|
|
||||||
|
// Prepare attachment file
|
||||||
|
final file = await preparingTxtFile(keyword);
|
||||||
|
|
||||||
|
// Provisioning some emails
|
||||||
|
await provisionEmail(longEmailContents
|
||||||
|
.map(
|
||||||
|
(emailContent) => ProvisioningEmail(
|
||||||
|
toEmail: loginWithBasicAuthScenario.email,
|
||||||
|
subject: keyword,
|
||||||
|
content: emailContent,
|
||||||
|
attachmentPaths: emailContent == longEmailContents.first
|
||||||
|
? [file.path]
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList());
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
|
||||||
|
// Search
|
||||||
|
await threadRobot.tapOnSearchField();
|
||||||
|
await searchRobot.enterKeyword(keyword);
|
||||||
|
await searchRobot.tapOnShowAllResultsText();
|
||||||
|
await $.pump(const Duration(seconds: 5));
|
||||||
|
expect($(RichTextBuilder).$(keyword.split(' ').first).hitTestable().evaluate().length, 12);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:core/core.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import '../base/base_scenario.dart';
|
||||||
|
import '../models/provisioning_email.dart';
|
||||||
|
import '../robots/search_robot.dart';
|
||||||
|
import '../robots/thread_robot.dart';
|
||||||
|
import '../utils/scenario_utils_mixin.dart';
|
||||||
|
import 'login_with_basic_auth_scenario.dart';
|
||||||
|
|
||||||
|
class SearchSuggestionHighlightsScenario extends BaseScenario with ScenarioUtilsMixin {
|
||||||
|
SearchSuggestionHighlightsScenario(
|
||||||
|
super.$, {
|
||||||
|
required this.loginWithBasicAuthScenario,
|
||||||
|
required this.keyword,
|
||||||
|
required this.longEmailContents,
|
||||||
|
});
|
||||||
|
|
||||||
|
final LoginWithBasicAuthScenario loginWithBasicAuthScenario;
|
||||||
|
final String keyword;
|
||||||
|
final List<String> longEmailContents;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> execute() async {
|
||||||
|
// Robots
|
||||||
|
final threadRobot = ThreadRobot($);
|
||||||
|
final searchRobot = SearchRobot($);
|
||||||
|
|
||||||
|
// Login
|
||||||
|
await loginWithBasicAuthScenario.execute();
|
||||||
|
|
||||||
|
// Prepare attachment file
|
||||||
|
final file = await preparingTxtFile(keyword);
|
||||||
|
|
||||||
|
// Provisioning some emails
|
||||||
|
await provisionEmail(longEmailContents
|
||||||
|
.map(
|
||||||
|
(emailContent) => ProvisioningEmail(
|
||||||
|
toEmail: loginWithBasicAuthScenario.email,
|
||||||
|
subject: keyword,
|
||||||
|
content: emailContent,
|
||||||
|
attachmentPaths: emailContent == longEmailContents.first
|
||||||
|
? [file.path]
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList());
|
||||||
|
await $.pumpAndSettle();
|
||||||
|
|
||||||
|
// Search
|
||||||
|
await threadRobot.tapOnSearchField();
|
||||||
|
await searchRobot.enterKeyword(keyword);
|
||||||
|
await $.pump(const Duration(seconds: 5));
|
||||||
|
expect($(RichTextBuilder).$(keyword.split(' ').first).hitTestable().evaluate().length, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import '../../base/test_base.dart';
|
||||||
|
import '../../scenarios/login_with_basic_auth_scenario.dart';
|
||||||
|
import '../../scenarios/search_result_highlights_scenario.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestBase().runPatrolTest(
|
||||||
|
description: 'Should see highlighted keyword in search result',
|
||||||
|
test: ($) async {
|
||||||
|
const keyword = 'Search snippet results';
|
||||||
|
|
||||||
|
const longContentWithSearchKeywordAtTheStart = "$keyword Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s";
|
||||||
|
const longContentWithSearchKeywordAtTheMiddle = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. $keyword Lorem Ipsum has been the industry's standard dummy text ever since the 1500s";
|
||||||
|
const longContentWithSearchKeywordAtTheEnd = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s $keyword";
|
||||||
|
|
||||||
|
final loginWithBasicAuthScenario = LoginWithBasicAuthScenario($,
|
||||||
|
username: const String.fromEnvironment('USERNAME'),
|
||||||
|
hostUrl: const String.fromEnvironment('BASIC_AUTH_URL'),
|
||||||
|
email: const String.fromEnvironment('BASIC_AUTH_EMAIL'),
|
||||||
|
password: const String.fromEnvironment('PASSWORD'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final searchResultHighlightsScenario = SearchResultHighlightsScenario(
|
||||||
|
$,
|
||||||
|
loginWithBasicAuthScenario: loginWithBasicAuthScenario,
|
||||||
|
keyword: keyword,
|
||||||
|
longEmailContents: [
|
||||||
|
longContentWithSearchKeywordAtTheStart,
|
||||||
|
longContentWithSearchKeywordAtTheMiddle,
|
||||||
|
longContentWithSearchKeywordAtTheEnd
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await searchResultHighlightsScenario.execute();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import '../../base/test_base.dart';
|
||||||
|
import '../../scenarios/login_with_basic_auth_scenario.dart';
|
||||||
|
import '../../scenarios/search_suggestion_highlights_scenario.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestBase().runPatrolTest(
|
||||||
|
description: 'Should see highlighted keyword in search suggestion',
|
||||||
|
test: ($) async {
|
||||||
|
const keyword = 'Search snippet suggestions';
|
||||||
|
|
||||||
|
const longContentWithSearchKeywordAtTheStart = "$keyword Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s";
|
||||||
|
const longContentWithSearchKeywordAtTheMiddle = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. $keyword Lorem Ipsum has been the industry's standard dummy text ever since the 1500s";
|
||||||
|
const longContentWithSearchKeywordAtTheEnd = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s $keyword";
|
||||||
|
|
||||||
|
final loginWithBasicAuthScenario = LoginWithBasicAuthScenario($,
|
||||||
|
username: const String.fromEnvironment('USERNAME'),
|
||||||
|
hostUrl: const String.fromEnvironment('BASIC_AUTH_URL'),
|
||||||
|
email: const String.fromEnvironment('BASIC_AUTH_EMAIL'),
|
||||||
|
password: const String.fromEnvironment('PASSWORD'),
|
||||||
|
);
|
||||||
|
|
||||||
|
final searchResultHighlightsScenario = SearchSuggestionHighlightsScenario(
|
||||||
|
$,
|
||||||
|
loginWithBasicAuthScenario: loginWithBasicAuthScenario,
|
||||||
|
keyword: keyword,
|
||||||
|
longEmailContents: [
|
||||||
|
longContentWithSearchKeywordAtTheStart,
|
||||||
|
longContentWithSearchKeywordAtTheMiddle,
|
||||||
|
longContentWithSearchKeywordAtTheEnd
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await searchResultHighlightsScenario.execute();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/identities/identity.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
|
import 'package:model/email/attachment.dart';
|
||||||
|
import 'package:model/email/email_action_type.dart';
|
||||||
|
import 'package:model/extensions/session_extension.dart';
|
||||||
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
|
import 'package:model/extensions/presentation_mailbox_extension.dart';
|
||||||
|
import 'package:model/upload/file_info.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart';
|
||||||
|
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
|
||||||
|
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart';
|
||||||
|
import 'package:tmail_ui_user/features/upload/domain/state/attachment_upload_state.dart';
|
||||||
|
|
||||||
|
import '../models/provisioning_email.dart';
|
||||||
|
|
||||||
|
mixin ScenarioUtilsMixin {
|
||||||
|
Future<void> provisionEmail(List<ProvisioningEmail> provisioningEmails) async {
|
||||||
|
ComposerBindings().dependencies();
|
||||||
|
|
||||||
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
|
final createNewAndSendEmailInteractor = Get.find<CreateNewAndSendEmailInteractor>();
|
||||||
|
final threadController = Get.find<ThreadController>();
|
||||||
|
|
||||||
|
final identity = await getIdentity();
|
||||||
|
|
||||||
|
if (identity == null) return;
|
||||||
|
|
||||||
|
// Provision emails
|
||||||
|
await Future.wait(provisioningEmails.map((provisioningEmail) async {
|
||||||
|
final attachments = await uploadAttachments(provisioningEmail.attachmentPaths);
|
||||||
|
|
||||||
|
// Create and send email
|
||||||
|
return await createNewAndSendEmailInteractor.execute(
|
||||||
|
createEmailRequest: CreateEmailRequest(
|
||||||
|
session: mailboxDashBoardController.sessionCurrent!,
|
||||||
|
accountId: mailboxDashBoardController.accountId.value!,
|
||||||
|
emailActionType: EmailActionType.compose,
|
||||||
|
subject: provisioningEmail.subject,
|
||||||
|
emailContent: provisioningEmail.content,
|
||||||
|
fromSender: {},
|
||||||
|
toRecipients: {EmailAddress(null, provisioningEmail.toEmail)},
|
||||||
|
ccRecipients: {},
|
||||||
|
bccRecipients: {},
|
||||||
|
outboxMailboxId: mailboxDashBoardController.outboxMailbox?.mailboxId,
|
||||||
|
sentMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent],
|
||||||
|
identity: identity,
|
||||||
|
attachments: attachments,
|
||||||
|
),
|
||||||
|
).last;
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Refresh view after provisioning emails
|
||||||
|
threadController.refreshAllEmail();
|
||||||
|
|
||||||
|
ComposerBindings().dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File> preparingTxtFile(String content) async {
|
||||||
|
final directory = await getTemporaryDirectory();
|
||||||
|
final file = File('${directory.path}/test.txt');
|
||||||
|
return file.writeAsString(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Attachment>> uploadAttachments(
|
||||||
|
List<String> attachmentPaths
|
||||||
|
) async {
|
||||||
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
|
|
||||||
|
final attachments = <Attachment>[];
|
||||||
|
for (final path in attachmentPaths) {
|
||||||
|
final file = File(path);
|
||||||
|
final fileName = path.split('/').last;
|
||||||
|
|
||||||
|
final fileInfo = FileInfo(
|
||||||
|
fileName: fileName,
|
||||||
|
filePath: path,
|
||||||
|
fileSize: await file.length(),
|
||||||
|
);
|
||||||
|
final uploadUri = mailboxDashBoardController.sessionCurrent!.getUploadUri(
|
||||||
|
mailboxDashBoardController.accountId.value!,
|
||||||
|
jmapUrl: mailboxDashBoardController.dynamicUrlInterceptors.jmapUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
final uploadAttachmentInteractor = Get.find<UploadAttachmentInteractor>();
|
||||||
|
final uploadAttachmentState = await uploadAttachmentInteractor
|
||||||
|
.execute(fileInfo, uploadUri)
|
||||||
|
.last;
|
||||||
|
final attachment = await uploadAttachmentState.fold(
|
||||||
|
(failure) => null,
|
||||||
|
(success) async {
|
||||||
|
if (success is UploadAttachmentSuccess) {
|
||||||
|
final uploadAttachment = await success.uploadAttachment.progressState.last;
|
||||||
|
return uploadAttachment.fold(
|
||||||
|
(failure) => null,
|
||||||
|
(success) {
|
||||||
|
if (success is! SuccessAttachmentUploadState) return null;
|
||||||
|
|
||||||
|
return success.attachment;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (attachment != null) {
|
||||||
|
attachments.add(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Identity?> getIdentity() async {
|
||||||
|
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
|
||||||
|
final getAllIdentitiesInteractor = Get.find<GetAllIdentitiesInteractor>();
|
||||||
|
final getAllIdentities = await getAllIdentitiesInteractor.execute(
|
||||||
|
mailboxDashBoardController.sessionCurrent!,
|
||||||
|
mailboxDashBoardController.accountId.value!,
|
||||||
|
).last;
|
||||||
|
return getAllIdentities.fold(
|
||||||
|
(failure) => null,
|
||||||
|
(success) {
|
||||||
|
if (success is GetAllIdentitiesSuccess) {
|
||||||
|
return success.identities?.firstOrNull;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user