diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index a7bb6ea4e..85e475608 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -9,6 +9,8 @@ import 'package:flutter/material.dart' hide SearchController, State; import 'package:flutter_localizations/flutter_localizations.dart'; 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'; @@ -73,6 +75,7 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/store_sendi import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_sending_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; @@ -587,6 +590,109 @@ void main() { debugDefaultTargetPlatformOverride = null; tester.view.reset(); }); + + testWidgets( + 'LoadMoreButton SHOULD be displayed\n' + 'WHEN the load more action returns a non-empty list', + (tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.macOS; + final dpi = tester.view.devicePixelRatio; + tester.view.physicalSize = Size(dpi * 1920 * 2, dpi * 1080 * 2); + + when(mailboxDashboardController.spamReportController.spamReportState).thenReturn(Rx(SpamReportState.disabled)); + when(mailboxDashboardController.spamReportController.presentationSpamMailbox).thenReturn(Rxn(null)); + when(mailboxDashboardController.downloadController.listDownloadTaskState).thenReturn(RxList([])); + + final widget = makeTestableWidget(child: MailboxDashBoardView()); + await tester.pumpWidget(widget); + + // Open mailbox Inbox + final listEmailsOfInbox = List.generate( + 3, + (index) => PresentationEmail( + id: EmailId(Id('id_$index')), + mailboxIds: { + MailboxFixtures.inboxMailbox.id!: true + } + )); + mailboxDashboardController.setSelectedMailbox(MailboxFixtures.inboxMailbox.toPresentationMailbox()); + mailboxDashboardController.updateEmailList(listEmailsOfInbox); + + // Perform load more action + final emailList = [ + PresentationEmail( + id: EmailId(Id('id_4')), + mailboxIds: { + MailboxFixtures.inboxMailbox.id!: true + } + ), + PresentationEmail( + id: EmailId(Id('id_5')), + mailboxIds: { + MailboxFixtures.inboxMailbox.id!: true + } + ), + ]; + threadController.consumeState(Stream.value(Right(LoadMoreEmailsSuccess(emailList)))); + + await tester.pump(); + + final listViewEmailWidgetFinder = find.byKey(const PageStorageKey('list_presentation_email_in_threads')); + expect(listViewEmailWidgetFinder, findsOneWidget); + + final listViewEmailWidget = tester.widget(listViewEmailWidgetFinder); + expect(listViewEmailWidget.semanticChildCount, equals(7)); + + expect(find.text('Load more'), findsOneWidget); + + debugDefaultTargetPlatformOverride = null; + tester.view.reset(); + }); + + testWidgets( + 'LoadMoreButton SHOULD not be displayed\n' + 'WHEN the load more action returns a empty list', + (tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.macOS; + final dpi = tester.view.devicePixelRatio; + tester.view.physicalSize = Size(dpi * 1920 * 2, dpi * 1080 * 2); + + when(mailboxDashboardController.spamReportController.spamReportState).thenReturn(Rx(SpamReportState.disabled)); + when(mailboxDashboardController.spamReportController.presentationSpamMailbox).thenReturn(Rxn(null)); + when(mailboxDashboardController.downloadController.listDownloadTaskState).thenReturn(RxList([])); + + final widget = makeTestableWidget(child: MailboxDashBoardView()); + await tester.pumpWidget(widget); + + // Open mailbox Inbox + final listEmailsOfInbox = List.generate( + 3, + (index) => PresentationEmail( + id: EmailId(Id('id_$index')), + mailboxIds: { + MailboxFixtures.inboxMailbox.id!: true + } + )); + mailboxDashboardController.setSelectedMailbox(MailboxFixtures.inboxMailbox.toPresentationMailbox()); + mailboxDashboardController.updateEmailList(listEmailsOfInbox); + + // Perform load more action + final emailList = []; + threadController.consumeState(Stream.value(Right(LoadMoreEmailsSuccess(emailList)))); + + await tester.pump(); + + final listViewEmailWidgetFinder = find.byKey(const PageStorageKey('list_presentation_email_in_threads')); + expect(listViewEmailWidgetFinder, findsOneWidget); + + final listViewEmailWidget = tester.widget(listViewEmailWidgetFinder); + expect(listViewEmailWidget.semanticChildCount, equals(5)); + + expect(find.text('Load more'), findsNothing); + + debugDefaultTargetPlatformOverride = null; + tester.view.reset(); + }); }); tearDown(Get.deleteAll); diff --git a/test/features/thread/presentation/controller/thread_controller_test.dart b/test/features/thread/presentation/controller/thread_controller_test.dart new file mode 100644 index 000000000..7a1385f1d --- /dev/null +++ b/test/features/thread/presentation/controller/thread_controller_test.dart @@ -0,0 +1,243 @@ +import 'package:core/data/network/config/dynamic_url_interceptors.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/utils/app_toast.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/utils/application_manager.dart'; +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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/caching/caching_manager.dart'; +import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oidc_interactor.dart'; +import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart'; +import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart'; +import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart'; +import 'package:tmail_ui_user/main/utils/toast_manager.dart'; +import 'package:uuid/uuid.dart'; + +import 'thread_controller_test.mocks.dart'; + +mockControllerCallback() => InternalFinalCallback(callback: () {}); +const fallbackGenerators = { + #onStart: mockControllerCallback, + #onDelete: mockControllerCallback, +}; + +@GenerateNiceMocks([ + // Base controller mock specs + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + // Thread controller mock specs + MockSpec(fallbackGenerators: fallbackGenerators), + MockSpec(fallbackGenerators: fallbackGenerators), + MockSpec(fallbackGenerators: fallbackGenerators), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + // Declaration thread controller + late ThreadController threadController; + late MockNetworkConnectionController mockNetworkConnectionController; + late MockSearchController mockSearchController; + late MockMailboxDashBoardController mockMailboxDashBoardController; + late MockGetEmailsInMailboxInteractor mockGetEmailsInMailboxInteractor; + late MockRefreshChangesEmailsInMailboxInteractor mockRefreshChangesEmailsInMailboxInteractor; + late MockLoadMoreEmailsInMailboxInteractor mockLoadMoreEmailsInMailboxInteractor; + late MockSearchEmailInteractor mockSearchEmailInteractor; + late MockSearchMoreEmailInteractor mockSearchMoreEmailInteractor; + late MockGetEmailByIdInteractor mockGetEmailByIdInteractor; + + // Declaration base controller + late MockCachingManager mockCachingManager; + late MockLanguageCacheManager mockLanguageCacheManager; + late MockAuthorizationInterceptors mockAuthorizationInterceptors; + late MockDynamicUrlInterceptors mockDynamicUrlInterceptors; + late MockDeleteCredentialInteractor mockDeleteCredentialInteractor; + late MockLogoutOidcInteractor mockLogoutOidcInteractor; + late MockDeleteAuthorityOidcInteractor mockDeleteAuthorityOidcInteractor; + late MockAppToast mockAppToast; + late MockImagePaths mockImagePaths; + late MockResponsiveUtils mockResponsiveUtils; + late MockUuid mockUuid; + late MockApplicationManager mockApplicationManager; + late MockToastManager mockToastManager; + + setUpAll(() { + Get.testMode = true; + // Mock base controller + mockCachingManager = MockCachingManager(); + mockLanguageCacheManager = MockLanguageCacheManager(); + mockAuthorizationInterceptors = MockAuthorizationInterceptors(); + mockDynamicUrlInterceptors = MockDynamicUrlInterceptors(); + mockDeleteCredentialInteractor = MockDeleteCredentialInteractor(); + mockLogoutOidcInteractor = MockLogoutOidcInteractor(); + mockDeleteAuthorityOidcInteractor = MockDeleteAuthorityOidcInteractor(); + mockAppToast = MockAppToast(); + mockImagePaths = MockImagePaths(); + mockResponsiveUtils = MockResponsiveUtils(); + mockUuid = MockUuid(); + mockApplicationManager = MockApplicationManager(); + mockToastManager = MockToastManager(); + + Get.put(mockCachingManager); + Get.put(mockLanguageCacheManager); + Get.put(mockAuthorizationInterceptors); + Get.put( + mockAuthorizationInterceptors, + tag: BindingTag.isolateTag, + ); + Get.put(mockDynamicUrlInterceptors); + Get.put(mockDeleteCredentialInteractor); + Get.put(mockLogoutOidcInteractor); + Get.put(mockDeleteAuthorityOidcInteractor); + Get.put(mockAppToast); + Get.put(mockImagePaths); + Get.put(mockResponsiveUtils); + Get.put(mockUuid); + Get.put(mockApplicationManager); + Get.put(mockToastManager); + + // Mock thread controller + mockNetworkConnectionController = MockNetworkConnectionController(); + mockSearchController = MockSearchController(); + mockMailboxDashBoardController = MockMailboxDashBoardController(); + mockGetEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); + mockRefreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); + mockLoadMoreEmailsInMailboxInteractor = MockLoadMoreEmailsInMailboxInteractor(); + mockSearchEmailInteractor = MockSearchEmailInteractor(); + mockSearchMoreEmailInteractor = MockSearchMoreEmailInteractor(); + mockGetEmailByIdInteractor = MockGetEmailByIdInteractor(); + + Get.put(mockNetworkConnectionController); + Get.put(mockSearchController); + Get.put(mockMailboxDashBoardController); + + threadController = ThreadController( + mockGetEmailsInMailboxInteractor, + mockRefreshChangesEmailsInMailboxInteractor, + mockLoadMoreEmailsInMailboxInteractor, + mockSearchEmailInteractor, + mockSearchMoreEmailInteractor, + mockGetEmailByIdInteractor); + }); + + group('ThreadController::test', () { + group('validateListEmailsLoadMore::test', () { + final MailboxId selectedMailboxId = MailboxId(Id('mailboxA')); + final emailsInCurrentMailbox = []; + + test('SHOULD returns filtered and synced emails', () { + // Arrange + final emailList = [ + PresentationEmail( + id: EmailId(Id('email1')), + mailboxIds: {MailboxId(Id('mailbox1')): true}), + PresentationEmail( + id: EmailId(Id('email2')), + mailboxIds: {selectedMailboxId: true}), + PresentationEmail( + id: EmailId(Id('email3')), + mailboxIds: {selectedMailboxId: true}), + ]; + + when(mockMailboxDashBoardController.selectedMailbox).thenReturn(Rxn(PresentationMailbox(selectedMailboxId))); + when(mockMailboxDashBoardController.mapMailboxById).thenReturn({}); + when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(RxList(emailsInCurrentMailbox)); + when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); + when(mockSearchController.searchQuery).thenReturn(SearchQuery('')); + when(mockSearchController.isSearchEmailRunning).thenReturn(false); + + // Act + final result = threadController.validateListEmailsLoadMore(emailList); + + // Assert + expect(result.length, 2); + expect( + result.map((e) => e.id).toList(), + containsAll([EmailId(Id('email2')), EmailId(Id('email3'))])); + }); + + test('SHOULD filters out duplicated emails', () { + // Arrange + final emailList = [ + PresentationEmail( + id: EmailId(Id('email1')), + mailboxIds: {MailboxId(Id('mailbox1')): true}), + PresentationEmail( + id: EmailId(Id('email2')), + mailboxIds: {selectedMailboxId: true}), + ]; + emailsInCurrentMailbox.add( + PresentationEmail( + id: EmailId(Id('email2')), + mailboxIds: {selectedMailboxId: true})); + + when(mockMailboxDashBoardController.selectedMailbox).thenReturn(Rxn(PresentationMailbox(selectedMailboxId))); + when(mockMailboxDashBoardController.mapMailboxById).thenReturn({}); + when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(RxList(emailsInCurrentMailbox)); + when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); + when(mockSearchController.searchQuery).thenReturn(SearchQuery('')); + when(mockSearchController.isSearchEmailRunning).thenReturn(false); + + // Act + final result = threadController.validateListEmailsLoadMore(emailList); + + // Assert + expect(result.length, 0); + }); + + test('SHOULD handles empty emailList', () { + // Arrange + final emailList = []; + + when(mockMailboxDashBoardController.selectedMailbox).thenReturn(Rxn(PresentationMailbox(selectedMailboxId))); + when(mockMailboxDashBoardController.mapMailboxById).thenReturn({}); + when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(RxList(emailsInCurrentMailbox)); + when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); + when(mockSearchController.searchQuery).thenReturn(SearchQuery('')); + when(mockSearchController.isSearchEmailRunning).thenReturn(false); + + // Act + final result = threadController.validateListEmailsLoadMore(emailList); + + // Assert + expect(result.isEmpty, isTrue); + }); + }); + }); +} \ No newline at end of file