TF-4227 Add integration test on mobile for case display_view_with_all_email_with_tag_scenario

This commit is contained in:
dab246
2026-01-07 12:10:04 +07:00
committed by Dat H. Pham
parent a4aceec107
commit 2052fdbe5e
13 changed files with 415 additions and 11 deletions
@@ -10,9 +10,15 @@ abstract class BaseTestScenario extends BaseScenario
@override
Future<void> execute() async {
await setupPreLogin();
await executeLoginScenario();
await runTestLogic();
await disposeAfterTest();
}
Future<void> setupPreLogin() async {}
Future<void> runTestLogic();
Future<void> disposeAfterTest() async {}
}
@@ -0,0 +1,88 @@
import 'package:core/utils/app_logger.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:labels/labels.dart';
import 'package:tmail_ui_user/features/labels/domain/state/create_new_label_state.dart';
import 'package:tmail_ui_user/features/labels/domain/usecases/create_new_label_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import '../models/provisioning_email.dart';
import '../models/provisioning_label.dart';
mixin ProvisioningLabelScenarioMixin {
Future<List<Label>> provisionLabels(
List<ProvisioningLabel> provisioningLabels,
) async {
if (provisioningLabels.isEmpty) {
return [];
}
final dashboardController = getBinding<MailboxDashBoardController>();
final createLabelInteractor = getBinding<CreateNewLabelInteractor>();
final accountId = dashboardController?.accountId.value;
final labelController = dashboardController?.labelController;
if (createLabelInteractor == null || accountId == null) {
log(
'ProvisioningLabelScenarioMixin::provisionLabels '
'skipped: missing CreateNewLabelInteractor or accountId',
);
return [];
}
final List<Label?> results = await Future.wait(provisioningLabels.map(
(label) => _createLabel(createLabelInteractor, accountId, label),
));
final List<Label> createdLabels = results.whereType<Label>().toList();
if (createdLabels.isNotEmpty) {
labelController?.getAllLabels(accountId);
}
return createdLabels;
}
Future<Label?> _createLabel(
CreateNewLabelInteractor createLabelInteractor,
AccountId accountId,
ProvisioningLabel provisioningLabel,
) async {
final result = await createLabelInteractor
.execute(accountId, provisioningLabel.toLabel())
.last;
return result.fold(
(_) => null,
(success) => success is CreateNewLabelSuccess ? success.newLabel : null,
);
}
Future<List<Label>> provisionLabelsByDisplayNames(List<String> labelNames) {
return provisionLabels(
labelNames.map(ProvisioningLabel.new).toList(),
);
}
List<ProvisioningEmail> buildEmailsForLabel({
required Label label,
required String toEmail,
required int count,
}) {
return List.generate(
count,
(index) {
final emailNumber = index + 1;
final displayName = label.safeDisplayName;
return ProvisioningEmail(
toEmail: toEmail,
subject: 'Email $emailNumber subject $displayName',
content: 'Email $emailNumber content $displayName',
labels: [label],
);
},
);
}
}
@@ -14,6 +14,7 @@ import 'package:jmap_dart_client/jmap/jmap_request.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/email/set/set_email_method.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:labels/extensions/list_label_extension.dart';
import 'package:model/model.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
@@ -81,6 +82,7 @@ mixin ScenarioUtilsMixin {
identity: identity,
attachments: attachments,
hasRequestReadReceipt: requestReadReceipt,
keywords: provisioningEmail.labels.keywords,
),
)
.last;
@@ -1,13 +1,17 @@
import 'package:labels/labels.dart';
class ProvisioningEmail {
final String toEmail;
final String subject;
final String content;
final List<String> attachmentPaths;
final List<Label> labels;
ProvisioningEmail({
required this.toEmail,
required this.subject,
required this.content,
this.attachmentPaths = const [],
this.labels = const [],
});
}
@@ -0,0 +1,9 @@
import 'package:labels/model/label.dart';
class ProvisioningLabel {
final String displayName;
ProvisioningLabel(this.displayName);
Label toLabel() => Label(displayName: displayName);
}
+14
View File
@@ -0,0 +1,14 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/labels/label_list_item.dart';
import '../base/core_robot.dart';
class LabelRobot extends CoreRobot {
LabelRobot(super.$);
Future<void> openLabelByName(String name) async {
final item = $(LabelListItem).$(name);
await $.scrollUntilVisible(finder: item);
await item.tap();
}
}
@@ -0,0 +1,82 @@
import 'package:core/utils/platform_info.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:labels/labels.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/labels/label_list_view.dart';
import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart';
import '../../base/base_test_scenario.dart';
import '../../mixin/provisioning_label_scenario_mixin.dart';
import '../../robots/label_robot.dart';
import '../../robots/thread_robot.dart';
class DisplayViewWithAllEmailWithTagScenario extends BaseTestScenario
with ProvisioningLabelScenarioMixin {
const DisplayViewWithAllEmailWithTagScenario(super.$);
@override
Future<void> setupPreLogin() async {
PlatformInfo.isIntegrationTesting = true;
}
@override
Future<void> runTestLogic() async {
const emailUser = String.fromEnvironment('BASIC_AUTH_EMAIL');
final threadRobot = ThreadRobot($);
final labelRobot = LabelRobot($);
final labels = await provisionLabelsByDisplayNames(
['Tag 1', 'Tag 2', 'Tag 3'],
);
await $.pumpAndSettle();
int emailCount = 3;
for (final label in labels) {
await provisionEmail(
buildEmailsForLabel(
label: label,
toEmail: emailUser,
count: emailCount,
),
requestReadReceipt: false,
);
}
await $.pumpAndSettle(duration: const Duration(seconds: 2));
for (final label in labels) {
await threadRobot.openMailbox();
await _expectLabelListViewVisible();
await labelRobot.openLabelByName(label.safeDisplayName);
await _expectEmailListDisplayedCorrectByTag(
label: label,
emailCount: emailCount,
);
await $.pumpAndSettle(duration: const Duration(seconds: 1));
}
}
Future<void> _expectLabelListViewVisible() =>
expectViewVisible($(LabelListView));
Future<void> _expectEmailListDisplayedCorrectByTag({
required Label label,
required int emailCount,
}) async {
final tagDisplayName = label.safeDisplayName;
final listEmailTileWithTag = $.tester.widgetList<EmailTileBuilder>(
$(EmailTileBuilder).which<EmailTileBuilder>((widget) =>
widget.presentationEmail.subject?.contains(tagDisplayName) == true),
);
expect(listEmailTileWithTag.length, greaterThanOrEqualTo(emailCount));
}
@override
Future<void> disposeAfterTest() async {
PlatformInfo.isIntegrationTesting = false;
}
}
@@ -0,0 +1,10 @@
import '../../base/test_base.dart';
import '../../scenarios/labels/display_view_with_all_email_with_tag_scenario.dart';
void main() {
TestBase().runPatrolTest(
description:
'Should display view with all email with tag correctly when open label',
scenarioBuilder: ($) => DisplayViewWithAllEmailWithTagScenario($),
);
}
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:labels/extensions/label_extension.dart';
import 'package:labels/model/label.dart';
@@ -13,4 +14,7 @@ extension ListLabelExtension on List<Label> {
List<String> get displayNameNotNullList => map((label) => label.safeDisplayName)
.where((name) => name.trim().isNotEmpty)
.toList();
List<KeyWordIdentifier> get keywords =>
map((label) => label.keyword).nonNulls.toList();
}
@@ -60,18 +60,30 @@ extension CreateEmailRequestExtension on CreateEmailRequest {
Set<EmailBodyPart> createAttachments() => attachments?.toEmailBodyPart() ?? {};
Map<KeyWordIdentifier, bool>? createKeywords() {
if (draftsMailboxId != null) {
return {
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
};
} else if (templateMailboxId != null) {
return {
KeyWordIdentifier.emailSeen: true,
};
} else {
if (draftsMailboxId == null &&
templateMailboxId == null &&
keywords == null) {
return null;
}
final result = <KeyWordIdentifier, bool>{};
if (draftsMailboxId != null) {
result.addAll({
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
});
} else if (templateMailboxId != null) {
result[KeyWordIdentifier.emailSeen] = true;
}
if (keywords != null) {
for (final keyword in keywords!) {
result[keyword] = true;
}
}
return result;
}
Map<MailboxId, bool>? createMailboxIds() {
@@ -5,6 +5,7 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/email_action_type.dart';
@@ -47,6 +48,7 @@ class CreateEmailRequest with EquatableMixin {
final int? savedDraftHash;
final EmailActionType? savedActionType;
final EmailId? savedEmailDraftId;
final List<KeyWordIdentifier>? keywords;
CreateEmailRequest({
required this.session,
@@ -83,6 +85,7 @@ class CreateEmailRequest with EquatableMixin {
this.savedDraftHash,
this.savedActionType,
this.savedEmailDraftId,
this.keywords,
});
@override
@@ -121,5 +124,6 @@ class CreateEmailRequest with EquatableMixin {
savedDraftHash,
savedActionType,
savedEmailDraftId,
keywords,
];
}
@@ -162,7 +162,7 @@ extension PresentationMailboxExtension on PresentationMailbox {
if (isFavorite) {
return KeyWordIdentifier.emailFlagged.value;
}
if (this is PresentationLabelMailbox) {
if (isLabelMailbox) {
return (this as PresentationLabelMailbox).label.keyword?.value;
}
return null;
@@ -1,6 +1,11 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/create_email_request_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
@@ -98,4 +103,168 @@ void main() {
);
});
});
group('CreateEmailRequestExtension.createKeywords', () {
late Session session;
late AccountId accountId;
setUp(() {
session = SessionFixtures.aliceSession;
accountId = AccountFixtures.aliceAccountId;
});
CreateEmailRequest buildRequest({
MailboxId? draftsMailboxId,
MailboxId? templateMailboxId,
List<KeyWordIdentifier>? keywords,
}) {
return CreateEmailRequest(
session: session,
accountId: accountId,
emailActionType: EmailActionType.compose,
ownEmailAddress: 'user@example.com',
subject: 'subject',
emailContent: 'content',
draftsMailboxId: draftsMailboxId,
templateMailboxId: templateMailboxId,
keywords: keywords,
);
}
test('returns null when drafts, template and keywords are all null', () {
final request = buildRequest();
final result = request.createKeywords();
expect(result, isNull);
});
test('returns \$draft and \$seen when draftsMailboxId is provided', () {
final request = buildRequest(
draftsMailboxId: MailboxId(Id('drafts')),
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
});
});
test('returns only \$seen when templateMailboxId is provided', () {
final request = buildRequest(
templateMailboxId: MailboxId(Id('template')),
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailSeen: true,
});
});
test('returns only custom keywords when keywords is provided alone', () {
final request = buildRequest(
keywords: [
KeyWordIdentifier.emailFlagged,
KeyWordIdentifier.emailAnswered,
],
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailFlagged: true,
KeyWordIdentifier.emailAnswered: true,
});
});
test('merges drafts keywords with custom keywords', () {
final request = buildRequest(
draftsMailboxId: MailboxId(Id('drafts')),
keywords: [
KeyWordIdentifier.emailFlagged,
],
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
KeyWordIdentifier.emailFlagged: true,
});
});
test('merges template keywords with custom keywords', () {
final request = buildRequest(
templateMailboxId: MailboxId(Id('template')),
keywords: [
KeyWordIdentifier.emailFlagged,
],
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailSeen: true,
KeyWordIdentifier.emailFlagged: true,
});
});
test('does not duplicate \$seen if provided in custom keywords', () {
final request = buildRequest(
templateMailboxId: MailboxId(Id('template')),
keywords: [
KeyWordIdentifier.emailSeen,
KeyWordIdentifier.emailFlagged,
],
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailSeen: true,
KeyWordIdentifier.emailFlagged: true,
});
});
test('does not duplicate \$draft if provided in custom keywords', () {
final request = buildRequest(
draftsMailboxId: MailboxId(Id('drafts')),
keywords: [
KeyWordIdentifier.emailDraft,
],
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
});
});
test('handles multiple custom keywords correctly', () {
final request = buildRequest(
draftsMailboxId: MailboxId(Id('drafts')),
keywords: [
KeyWordIdentifier.emailFlagged,
KeyWordIdentifier.emailAnswered,
KeyWordIdentifier.emailForwarded,
],
);
final result = request.createKeywords();
expect(result, {
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
KeyWordIdentifier.emailFlagged: true,
KeyWordIdentifier.emailAnswered: true,
KeyWordIdentifier.emailForwarded: true,
});
});
});
}