TF-3697 Open specific thread detail email

This commit is contained in:
DatDang
2025-05-23 16:00:18 +07:00
committed by Dat H. Pham
parent 9878b062d3
commit c9ca3925cb
13 changed files with 634 additions and 122 deletions
@@ -6,6 +6,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
@@ -16,15 +17,18 @@ import 'initialize_thread_detail_emails_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<GetEmailsByIdsInteractor>(),
MockSpec<ThreadDetailController>(),
MockSpec<MailboxDashBoardController>(),
])
void main() {
group('InitializeThreadDetailEmails', () {
late MockGetEmailsByIdsInteractor getEmailsByIdsInteractor;
late MockThreadDetailController threadDetailController;
late MockMailboxDashBoardController mailboxDashboardController;
setUp(() {
getEmailsByIdsInteractor = MockGetEmailsByIdsInteractor();
threadDetailController = MockThreadDetailController();
mailboxDashboardController = MockMailboxDashBoardController();
});
test('initializeThreadDetailEmails should call getEmailsByIdsInteractor with 3 elements', () async {
@@ -52,6 +56,8 @@ void main() {
final session = SessionFixtures.aliceSession;
when(threadDetailController.accountId).thenReturn(accountId);
when(threadDetailController.session).thenReturn(session);
when(threadDetailController.mailboxDashBoardController).thenReturn(mailboxDashboardController);
when(mailboxDashboardController.selectedEmail).thenReturn(Rxn());
// Act
threadDetailController.initializeThreadDetailEmails();
@@ -7,9 +7,11 @@ import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
import '../../../../fixtures/account_fixtures.dart';
import '../../../../fixtures/session_fixtures.dart';
@@ -18,55 +20,66 @@ import 'load_more_thread_detail_emails_test.mocks.dart';
@GenerateNiceMocks([
MockSpec<ThreadDetailController>(),
MockSpec<GetEmailsByIdsInteractor>(),
MockSpec<MailboxDashBoardController>(),
])
void main() {
late MockThreadDetailController controller;
late MockGetEmailsByIdsInteractor getEmailsByIdsInteractor;
late MockMailboxDashBoardController mailboxDashBoardController;
setUp(() {
controller = MockThreadDetailController();
getEmailsByIdsInteractor = MockGetEmailsByIdsInteractor();
mailboxDashBoardController = MockMailboxDashBoardController();
});
test('Only call getEmailsByIdsInteractor.execute on emails where presentation email is null, max 20 emails', () async {
// Arrange
when(controller.emailIdsPresentation).thenReturn({
for (int i = 0; i < 40; i++)
EmailId(Id('$i')): null,
EmailId(Id('40')): PresentationEmail(),
for (int i = 0; i < 40; i++) EmailId(Id('$i')): null,
}.obs);
when(controller.currentExpandedEmailId).thenReturn(Rxn());
when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.additionalProperties).thenReturn(Properties.empty());
when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());
// Act
controller.loadMoreThreadDetailEmails();
controller.loadMoreThreadDetailEmails(
loadMoreIndex: 0,
loadMoreCount: controller.emailIdsPresentation.length,
);
// Assert
verify(getEmailsByIdsInteractor.execute(
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
List.generate(20, (i) => EmailId(Id('${i + 20}'))),
List.generate(20, (i) => EmailId(Id('$i'))),
properties: EmailUtils.getPropertiesForEmailGetMethod(
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
),
loadMoreIndex: 0,
)).called(1);
});
test('No getEmailsByIdsInteractor.execute call if emailIdsToLoadMetaData is empty', () async {
// Arrange
when(controller.emailIdsPresentation).thenReturn({
EmailId(Id('1')): PresentationEmail(),
EmailId(Id('2')): PresentationEmail(),
}.obs);
when(controller.emailIdsPresentation).thenReturn(<EmailId, PresentationEmail?>{}.obs);
when(controller.currentExpandedEmailId).thenReturn(Rxn());
when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());
// Act
controller.loadMoreThreadDetailEmails();
controller.loadMoreThreadDetailEmails(
loadMoreIndex: 0,
loadMoreCount: controller.emailIdsPresentation.length,
);
// Assert
verifyNever(getEmailsByIdsInteractor.execute(
@@ -81,17 +94,21 @@ void main() {
// Arrange
const limit = 15;
when(controller.emailIdsPresentation).thenReturn({
for (int i = 0; i < limit; i++)
EmailId(Id('$i')): null,
EmailId(Id('$limit')): PresentationEmail(),
for (int i = 0; i < limit; i++) EmailId(Id('$i')): null,
}.obs);
when(controller.currentExpandedEmailId).thenReturn(Rxn());
when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.additionalProperties).thenReturn(Properties.empty());
when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());
// Act
controller.loadMoreThreadDetailEmails();
controller.loadMoreThreadDetailEmails(
loadMoreIndex: 0,
loadMoreCount: controller.emailIdsPresentation.length,
);
// Assert
verify(getEmailsByIdsInteractor.execute(
@@ -102,6 +119,96 @@ void main() {
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
),
loadMoreIndex: 0,
)).called(1);
});
test(
'should call getEmailsByIdsInteractor.execute '
'with ThreadDetailPresentationUtils.defaultLoadSize (20) top to bottom '
'when loadMoreIndex is larger than index of currentExpandedEmailId',
() {
// arrange
final loadMoreEmails = {
for (int i = 31; i < 60; i++) EmailId(Id('$i')): null
};
const loadMoreIndex = 32;
when(controller.emailIdsPresentation).thenReturn({
for (int i = 0; i < 30; i++) EmailId(Id('$i')): null,
EmailId(Id('30')): PresentationEmail(),
...loadMoreEmails,
}.obs);
when(controller.currentExpandedEmailId).thenReturn(Rxn(EmailId(Id('30'))));
when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());
// act
controller.loadMoreThreadDetailEmails(
loadMoreIndex: loadMoreIndex,
loadMoreCount: loadMoreEmails.length,
);
// assert
verify(getEmailsByIdsInteractor.execute(
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
List.generate(
ThreadDetailPresentationUtils.defaultLoadSize,
(i) => EmailId(Id('${i + loadMoreIndex}')),
),
properties: EmailUtils.getPropertiesForEmailGetMethod(
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
),
loadMoreIndex: loadMoreIndex,
)).called(1);
});
test(
'should call getEmailsByIdsInteractor.execute '
'with ThreadDetailPresentationUtils.defaultLoadSize (20) bottom to top '
'when loadMoreIndex is larger than index of currentExpandedEmailId',
() {
// arrange
final loadMoreEmails = {
for (int i = 0; i < 30; i++) EmailId(Id('$i')): null
};
const loadMoreIndex = 0;
when(controller.emailIdsPresentation).thenReturn({
...loadMoreEmails,
EmailId(Id('30')): PresentationEmail(),
for (int i = 31; i < 60; i++) EmailId(Id('$i')): null,
}.obs);
when(controller.currentExpandedEmailId).thenReturn(Rxn(EmailId(Id('30'))));
when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController);
when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());
// act
controller.loadMoreThreadDetailEmails(
loadMoreIndex: loadMoreIndex,
loadMoreCount: loadMoreEmails.length,
);
// assert
verify(getEmailsByIdsInteractor.execute(
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
List.generate(
ThreadDetailPresentationUtils.defaultLoadSize,
(i) => EmailId(Id('${loadMoreEmails.length - i - 1}')),
).reversed.toList(),
properties: EmailUtils.getPropertiesForEmailGetMethod(
SessionFixtures.aliceSession,
AccountFixtures.aliceAccountId,
),
loadMoreIndex: loadMoreIndex,
)).called(1);
});
}
@@ -0,0 +1,76 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_load_more_segments.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'thread_detail_load_more_segments_test.mocks.dart';
@GenerateNiceMocks([MockSpec<ThreadDetailController>()])
void main() {
late MockThreadDetailController controller;
setUp(() {
controller = MockThreadDetailController();
});
group('ThreadDetailLoadMoreSegments', () {
test(
'should return empty map when emailIdsPresentation is empty',
() {
// arrange
when(controller.emailIdsPresentation).thenReturn(<EmailId, PresentationEmail?>{}.obs);
// act
final result = controller.loadMoreSegments;
// assert
expect(result, {});
});
test(
'should return correct load more segments when there are null values in emailIdsPresentation',
() {
// arrange
when(controller.emailIdsPresentation).thenReturn({
EmailId(Id('1')): PresentationEmail(),
EmailId(Id('2')): null,
EmailId(Id('3')): null,
EmailId(Id('4')): PresentationEmail(),
EmailId(Id('5')): null,
}.obs);
// act
final result = controller.loadMoreSegments;
// assert
expect(result, {
1: 2,
4: 1,
});
});
test(
'should return correct load more segments when there are no null values in emailIdsPresentation',
() {
// arrange
when(controller.emailIdsPresentation).thenReturn({
EmailId(Id('1')): PresentationEmail(),
EmailId(Id('2')): PresentationEmail(),
EmailId(Id('3')): PresentationEmail(),
EmailId(Id('4')): PresentationEmail(),
EmailId(Id('5')): PresentationEmail(),
}.obs);
// act
final result = controller.loadMoreSegments;
// assert
expect(result, {});
});
});
}
@@ -1,68 +1,326 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
void main() {
group('ThreadDetailPresentationUtils', () {
group('getEmailIdsToLoad', () {
test('loads first time with length equal or more than initial load size', () {
final emailIdsPresentation = {
EmailId(Id('1')): null,
EmailId(Id('2')): null,
EmailId(Id('3')): null,
EmailId(Id('4')): null,
EmailId(Id('5')): null,
};
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
expect(result.length, equals(ThreadDetailPresentationUtils.initialLoadSize));
expect(result, equals([EmailId(Id('1')), EmailId(Id('2'))]));
test(
'should return empty list '
'when emailIds is empty',
() {
// arrange
final emailIds = <EmailId>[];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
);
// assert
expect(result, isEmpty);
});
test('loads first time with length smaller than initial load size', () {
final emailIdsPresentation = {
EmailId(Id('1')): null,
EmailId(Id('2')): null,
};
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
expect(result.length, equals(emailIdsPresentation.length));
expect(result, equals([EmailId(Id('1')), EmailId(Id('2'))]));
test(
'should return all email ids in emailIds '
'when isFirstLoad is true '
'and emailIds contains equal to or less than 3 email ids',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
);
// assert
expect(result, equals(emailIds));
});
test('loads with length equal or more than default load size', () {
final emailIdsPresentation = {
EmailId(Id('1')): PresentationEmail(),
EmailId(Id('2')): null,
EmailId(Id('3')): null,
EmailId(Id('4')): null,
EmailId(Id('5')): null,
EmailId(Id('6')): null,
EmailId(Id('7')): null,
};
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
expect(result.length, equals(6));
expect(result, equals([EmailId(Id('2')), EmailId(Id('3')), EmailId(Id('4')), EmailId(Id('5')), EmailId(Id('6')), EmailId(Id('7'))]));
test(
'should return first two emails and last email '
'when isFirstLoad is true '
'and emailIds contains more than 3 email ids '
'and selectedEmailId is not null ',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
);
// assert
expect(
result,
equals([EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('5'))]),
);
});
test('loads with length smaller than default load size', () {
final emailIdsPresentation = {
EmailId(Id('1')): PresentationEmail(),
EmailId(Id('2')): null,
EmailId(Id('3')): null,
};
test(
'should return first two emails and last email '
'when isFirstLoad is true '
'and emailIds contains more than 3 email ids '
'and selectedEmailId is not inside emailIds',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
selectedEmailId: EmailId(Id('6')),
);
// assert
expect(
result,
equals([EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('5'))]),
);
});
final result = ThreadDetailPresentationUtils.getEmailIdsToLoad(emailIdsPresentation);
test(
'should return first two emails and last email '
'when isFirstLoad is true '
'and emailIds contains more than 3 email ids '
'and selectedEmailId is first element of emailIds',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
selectedEmailId: emailIds.first,
);
// assert
expect(
result,
equals([EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('5'))]),
);
});
expect(result.length, equals(emailIdsPresentation.length - 1));
expect(result, equals([EmailId(Id('2')), EmailId(Id('3'))]));
test(
'should return first two emails and last email '
'when isFirstLoad is true '
'and emailIds contains more than 3 email ids '
'and selectedEmailId is last element of emailIds',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
selectedEmailId: emailIds.last,
);
// assert
expect(
result,
equals([EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('5'))]),
);
});
test(
'should return first two emails and last email '
'when isFirstLoad is true '
'and emailIds contains more than 3 email ids '
'and selectedEmailId is second element of emailIds',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
selectedEmailId: emailIds.elementAt(1),
);
// assert
expect(
result,
equals([EmailId(Id('1')), EmailId(Id('2')), EmailId(Id('5'))]),
);
});
test(
'should return first, last and selected email '
'when emailIds contains more than 3 email ids '
'and isFirstLoad is true '
'and selectedEmailId is inside emailIds '
'and selectedEmailId is not first, last or second element of emailIds',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
final selectedEmail = EmailId(Id('3'));
// act
final result = ThreadDetailPresentationUtils.getFirstLoadEmailIds(
emailIds,
selectedEmailId: selectedEmail,
);
// assert
expect(
result,
equals([emailIds.first, selectedEmail, emailIds.last]),
);
});
test(
'should return all emails '
'when emailIds contains more than 3 email ids '
'and isFirstLoad is false '
'and loadEmailsAfterSelectedEmail is true '
'and emailsIds.length is less than defaultLoadSize',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getLoadMoreEmailIds(
emailIds,
loadEmailsAfterSelectedEmail: true,
);
// assert
expect(
result,
equals(emailIds),
);
});
test(
'should return first defaultLoadSize (20) emails '
'when emailIds contains more than 3 email ids '
'and isFirstLoad is false '
'and loadEmailsAfterSelectedEmail is true '
'and emailsIds.length is more than defaultLoadSize',
() {
// arrange
final emailIds = List.generate(30, (i) => EmailId(Id('$i')));
// act
final result = ThreadDetailPresentationUtils.getLoadMoreEmailIds(
emailIds,
loadEmailsAfterSelectedEmail: true,
);
// assert
expect(
result,
equals(emailIds.sublist(
0,
ThreadDetailPresentationUtils.defaultLoadSize,
)),
);
});
test(
'should return all emails '
'when emailIds contains more than 3 email ids '
'and isFirstLoad is false '
'and loadEmailsAfterSelectedEmail is false '
'and emailsIds.length is less than defaultLoadSize',
() {
// arrange
final emailIds = [
EmailId(Id('1')),
EmailId(Id('2')),
EmailId(Id('3')),
EmailId(Id('4')),
EmailId(Id('5')),
];
// act
final result = ThreadDetailPresentationUtils.getLoadMoreEmailIds(
emailIds,
loadEmailsAfterSelectedEmail: false,
);
// assert
expect(
result,
equals(emailIds),
);
});
test(
'should return last defaultLoadSize (20) emails '
'when emailIds contains more than 3 email ids '
'and isFirstLoad is false '
'and loadEmailsAfterSelectedEmail is false '
'and emailsIds.length is more than defaultLoadSize',
() {
// arrange
final emailIds = List.generate(30, (i) => EmailId(Id('$i')));
// act
final result = ThreadDetailPresentationUtils.getLoadMoreEmailIds(
emailIds,
loadEmailsAfterSelectedEmail: false,
);
// assert
expect(
result,
equals(emailIds.sublist(
emailIds.length - ThreadDetailPresentationUtils.defaultLoadSize,
emailIds.length,
)),
);
});
});
});
}
}