TF-3912 Thread Detail Fix real time update
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
|
|
||||||
import '../base/core_robot.dart';
|
import '../base/core_robot.dart';
|
||||||
|
|
||||||
@@ -8,4 +10,25 @@ class SettingRobot extends CoreRobot {
|
|||||||
Future<void> openLanguageMenuItem() async {
|
Future<void> openLanguageMenuItem() async {
|
||||||
await $(#setting_language_region).tap();
|
await $(#setting_language_region).tap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> openPreferencesMenuItem() async {
|
||||||
|
await $(#setting_preferences).tap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> switchOnThreadSetting() async {
|
||||||
|
final threadSettingOn = $(ValueKey(AppLocalizations().thread))
|
||||||
|
.$(#setting_option_switch_on)
|
||||||
|
.visible;
|
||||||
|
if (!threadSettingOn) {
|
||||||
|
await $(ValueKey(AppLocalizations().thread)).tap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> backToSettingsFromFirstLevel() async {
|
||||||
|
await $(#settings_first_level_close_button).tap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> closeSettings() async {
|
||||||
|
await $(#settings_close_button).tap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart';
|
||||||
|
|
||||||
|
import '../../base/base_test_scenario.dart';
|
||||||
|
import '../../robots/composer_robot.dart';
|
||||||
|
import '../../robots/email_robot.dart';
|
||||||
|
import '../../robots/mailbox_menu_robot.dart';
|
||||||
|
import '../../robots/search_robot.dart';
|
||||||
|
import '../../robots/setting_robot.dart';
|
||||||
|
import '../../robots/thread_robot.dart';
|
||||||
|
|
||||||
|
class ThreadDetailReplyRealTimeUpdateScenario extends BaseTestScenario {
|
||||||
|
ThreadDetailReplyRealTimeUpdateScenario(super.$);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> runTestLogic() async {
|
||||||
|
const subject = 'Reply thread';
|
||||||
|
const replyContent = 'reply thread detail';
|
||||||
|
|
||||||
|
final threadRobot = ThreadRobot($);
|
||||||
|
final mailboxMenuRobot = MailboxMenuRobot($);
|
||||||
|
final settingRobot = SettingRobot($);
|
||||||
|
final searchRobot = SearchRobot($);
|
||||||
|
final emailRobot = EmailRobot($);
|
||||||
|
final composerRobot = ComposerRobot($);
|
||||||
|
final imagePaths = ImagePaths();
|
||||||
|
|
||||||
|
await threadRobot.openMailbox();
|
||||||
|
await mailboxMenuRobot.openSetting();
|
||||||
|
await settingRobot.openPreferencesMenuItem();
|
||||||
|
await settingRobot.switchOnThreadSetting();
|
||||||
|
await settingRobot.backToSettingsFromFirstLevel();
|
||||||
|
await settingRobot.closeSettings();
|
||||||
|
|
||||||
|
await threadRobot.openSearchView();
|
||||||
|
await searchRobot.enterQueryString(subject);
|
||||||
|
await searchRobot.tapOnShowAllResultsText();
|
||||||
|
await searchRobot.openEmailWithSubject(subject);
|
||||||
|
|
||||||
|
await emailRobot.onTapReplyEmail();
|
||||||
|
await composerRobot.grantContactPermission();
|
||||||
|
await composerRobot.addContent(replyContent);
|
||||||
|
await composerRobot.sendEmail(imagePaths);
|
||||||
|
await $.waitUntilVisible($(ThreadDetailCollapsedEmail));
|
||||||
|
expect(
|
||||||
|
$(ThreadDetailCollapsedEmail).which<ThreadDetailCollapsedEmail>((widget) {
|
||||||
|
return widget.preview.contains(replyContent);
|
||||||
|
}),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import '../../base/test_base.dart';
|
||||||
|
import '../../scenarios/thread_detail/thread_detail_reply_real_time_update_scenario.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestBase().runPatrolTest(
|
||||||
|
description: 'Should see reply of thread detail',
|
||||||
|
scenarioBuilder: ($) => ThreadDetailReplyRealTimeUpdateScenario($),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -144,6 +144,8 @@ class GetEmailContentInteractor {
|
|||||||
emailCurrent: emailCache.copyWith(
|
emailCurrent: emailCache.copyWith(
|
||||||
headers: detailedEmail.headers,
|
headers: detailedEmail.headers,
|
||||||
sMimeStatusHeader: detailedEmail.sMimeStatusHeader,
|
sMimeStatusHeader: detailedEmail.sMimeStatusHeader,
|
||||||
|
references: detailedEmail.references,
|
||||||
|
messageId: detailedEmail.messageId,
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -183,6 +185,8 @@ class GetEmailContentInteractor {
|
|||||||
headers: detailedEmail.headers,
|
headers: detailedEmail.headers,
|
||||||
sMimeStatusHeader: detailedEmail.sMimeStatusHeader,
|
sMimeStatusHeader: detailedEmail.sMimeStatusHeader,
|
||||||
identityHeader: detailedEmail.identityHeader,
|
identityHeader: detailedEmail.identityHeader,
|
||||||
|
references: detailedEmail.references,
|
||||||
|
messageId: detailedEmail.messageId,
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
+1
@@ -33,6 +33,7 @@ class SettingFirstLevelAppBar extends StatelessWidget {
|
|||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
start: 0,
|
start: 0,
|
||||||
child: TMailButtonWidget.fromIcon(
|
child: TMailButtonWidget.fromIcon(
|
||||||
|
key: const ValueKey('settings_first_level_close_button'),
|
||||||
icon: imagePaths.icArrowBack,
|
icon: imagePaths.icArrowBack,
|
||||||
tooltipMessage: appLocalizations.back,
|
tooltipMessage: appLocalizations.back,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class SettingsFirstLevelView extends GetWidget<SettingsController> {
|
|||||||
if (controller.manageAccountDashboardController.isServerSettingsCapabilitySupported) {
|
if (controller.manageAccountDashboardController.isServerSettingsCapabilitySupported) {
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
SettingFirstLevelTileBuilder(
|
SettingFirstLevelTileBuilder(
|
||||||
|
key: const ValueKey('setting_preferences'),
|
||||||
AccountMenuItem.preferences.getName(AppLocalizations.of(context)),
|
AccountMenuItem.preferences.getName(AppLocalizations.of(context)),
|
||||||
AccountMenuItem.preferences.getIcon(controller.imagePaths),
|
AccountMenuItem.preferences.getIcon(controller.imagePaths),
|
||||||
subtitle: AppLocalizations.of(context).emailReadReceiptsSettingExplanation,
|
subtitle: AppLocalizations.of(context).emailReadReceiptsSettingExplanation,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class UniversalSettingAppBar extends StatelessWidget {
|
|||||||
PositionedDirectional(
|
PositionedDirectional(
|
||||||
start: 24,
|
start: 24,
|
||||||
child: TMailButtonWidget.fromIcon(
|
child: TMailButtonWidget.fromIcon(
|
||||||
|
key: const ValueKey('settings_close_button'),
|
||||||
icon: imagePaths.icClose,
|
icon: imagePaths.icClose,
|
||||||
iconSize: 28,
|
iconSize: 28,
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(5),
|
||||||
|
|||||||
@@ -59,11 +59,17 @@ class SettingOptionItem extends StatelessWidget {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
InkWell(
|
||||||
|
key: ValueKey(optionType.getTitle(appLocalizations)),
|
||||||
onTap: () => onTapSettingOptionAction(
|
onTap: () => onTapSettingOptionAction(
|
||||||
optionType,
|
optionType,
|
||||||
optionType.isEnabled(settingOption, localSettings),
|
optionType.isEnabled(settingOption, localSettings),
|
||||||
),
|
),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
|
key: ValueKey(
|
||||||
|
optionType.isEnabled(settingOption, localSettings)
|
||||||
|
? 'setting_option_switch_on'
|
||||||
|
: 'setting_option_switch_off',
|
||||||
|
),
|
||||||
optionType.isEnabled(settingOption, localSettings)
|
optionType.isEnabled(settingOption, localSettings)
|
||||||
? imagePaths.icSwitchOn
|
? imagePaths.icSwitchOn
|
||||||
: imagePaths.icSwitchOff,
|
: imagePaths.icSwitchOff,
|
||||||
|
|||||||
+9
-6
@@ -104,11 +104,14 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
|||||||
Email email,
|
Email email,
|
||||||
ThreadId currentThreadId,
|
ThreadId currentThreadId,
|
||||||
) {
|
) {
|
||||||
return email.threadId == currentThreadId &&
|
if (email.threadId != currentThreadId) return false;
|
||||||
sentMailboxId != null &&
|
|
||||||
!email.inSentMailbox(sentMailboxId!) &&
|
if (sentMailboxId != null && !email.inSentMailbox(sentMailboxId!)) {
|
||||||
ownEmailAddress != null &&
|
return true;
|
||||||
!email.fromMe(ownEmailAddress!) &&
|
}
|
||||||
!email.recipientsHasMe(ownEmailAddress!);
|
|
||||||
|
return ownEmailAddress != null &&
|
||||||
|
(!email.fromMe(ownEmailAddress!) ||
|
||||||
|
!email.recipientsHasMe(ownEmailAddress!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
Return-Path: <emma@example.com>
|
||||||
|
MIME-Version: 1.0
|
||||||
|
References: <Mime4j.4c.b3b452d18f0d0ef9.192d67aaafc@example.com>
|
||||||
|
Subject: Reply thread
|
||||||
|
From: emma@example.com
|
||||||
|
To: "bob" <bob@example.com>
|
||||||
|
Cc: "alice" <alice@example.com>
|
||||||
|
Bcc: "brian" <brian@example.com>
|
||||||
|
Reply-To: emma-reply-to@example.com
|
||||||
|
Date: Tue, 17 Dec 2024 16:31:00 +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=-"
|
||||||
|
List-Post: <mailto:emma-reply-to-list@example.com>
|
||||||
|
|
||||||
|
---=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
|
||||||
|
|
||||||
|
Reply thread
|
||||||
|
|
||||||
|
---=Part.4f.450e1cfbcd355387.192d67ae03d.c367d8042fea24dd=---
|
||||||
@@ -70,7 +70,7 @@ james-cli ImportEml \#private "bob@example.com" "Calendar" "/root/conf/integrati
|
|||||||
|
|
||||||
# For test reply email
|
# For test reply email
|
||||||
# Import emails into 'Reply Emails' folder for user Bob
|
# Import emails into 'Reply Emails' folder for user Bob
|
||||||
replyEmailsEML=("reply-all.eml" "reply-to-list.eml" "with-reply-to.eml" "without-reply-to.eml")
|
replyEmailsEML=("reply-all.eml" "reply-to-list.eml" "with-reply-to.eml" "without-reply-to.eml" "reply-thread.eml")
|
||||||
|
|
||||||
for eml in "${replyEmailsEML[@]}"; do
|
for eml in "${replyEmailsEML[@]}"; do
|
||||||
echo "Importing $eml into 'Reply Emails' folder for user bob"
|
echo "Importing $eml into 'Reply Emails' folder for user bob"
|
||||||
|
|||||||
+128
@@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email.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/mailbox/mailbox.dart';
|
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||||
import 'package:mockito/annotations.dart';
|
import 'package:mockito/annotations.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
@@ -129,4 +130,131 @@ void main() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('validateNewCreatedEmailForCurrentThread test:', () {
|
||||||
|
test(
|
||||||
|
'should return false '
|
||||||
|
'when email is not in same thread as current thread',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final email = Email(threadId: ThreadId(Id('other-thread-id')));
|
||||||
|
final threadId = ThreadId(Id('thread-id'));
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = threadDetailController.validateNewCreatedEmailForCurrentThread(
|
||||||
|
email,
|
||||||
|
threadId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(result, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return true '
|
||||||
|
'when email is in same thread as current thread '
|
||||||
|
'and email is not in sent mailbox',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final threadId = ThreadId(Id('thread-id'));
|
||||||
|
final sentMailboxId = MailboxId(Id('sent-mailbox-id'));
|
||||||
|
final email = Email(threadId: threadId);
|
||||||
|
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = threadDetailController.validateNewCreatedEmailForCurrentThread(
|
||||||
|
email,
|
||||||
|
threadId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(result, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return true '
|
||||||
|
'when email is in same thread as current thread '
|
||||||
|
'and email is in sent mailbox '
|
||||||
|
'and email is not sent by current user',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final threadId = ThreadId(Id('thread-id'));
|
||||||
|
final sentMailboxId = MailboxId(Id('sent-mailbox-id'));
|
||||||
|
final email = Email(
|
||||||
|
threadId: threadId,
|
||||||
|
mailboxIds: {sentMailboxId: true},
|
||||||
|
);
|
||||||
|
const ownEmailAddress = '9jEYK@example.com';
|
||||||
|
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||||
|
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = threadDetailController.validateNewCreatedEmailForCurrentThread(
|
||||||
|
email,
|
||||||
|
threadId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(result, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return true '
|
||||||
|
'when email is in same thread as current thread '
|
||||||
|
'and email is in sent mailbox '
|
||||||
|
'and email is sent by current user '
|
||||||
|
'and email is not sent to current user',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final threadId = ThreadId(Id('thread-id'));
|
||||||
|
final sentMailboxId = MailboxId(Id('sent-mailbox-id'));
|
||||||
|
const ownEmailAddress = '9jEYK@example.com';
|
||||||
|
final email = Email(
|
||||||
|
threadId: threadId,
|
||||||
|
mailboxIds: {sentMailboxId: true},
|
||||||
|
from: {EmailAddress(null, ownEmailAddress)},
|
||||||
|
);
|
||||||
|
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||||
|
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = threadDetailController.validateNewCreatedEmailForCurrentThread(
|
||||||
|
email,
|
||||||
|
threadId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(result, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'should return false '
|
||||||
|
'when email is in same thread as current thread '
|
||||||
|
'and email is in sent mailbox '
|
||||||
|
'and email is sent by current user '
|
||||||
|
'and email is sent to current user',
|
||||||
|
() {
|
||||||
|
// arrange
|
||||||
|
final threadId = ThreadId(Id('thread-id'));
|
||||||
|
final sentMailboxId = MailboxId(Id('sent-mailbox-id'));
|
||||||
|
const ownEmailAddress = '9jEYK@example.com';
|
||||||
|
final email = Email(
|
||||||
|
threadId: threadId,
|
||||||
|
mailboxIds: {sentMailboxId: true},
|
||||||
|
from: {EmailAddress(null, ownEmailAddress)},
|
||||||
|
to: {EmailAddress(null, ownEmailAddress)},
|
||||||
|
);
|
||||||
|
when(threadDetailController.sentMailboxId).thenReturn(sentMailboxId);
|
||||||
|
when(threadDetailController.ownEmailAddress).thenReturn(ownEmailAddress);
|
||||||
|
|
||||||
|
// act
|
||||||
|
final result = threadDetailController.validateNewCreatedEmailForCurrentThread(
|
||||||
|
email,
|
||||||
|
threadId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(result, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user