import 'package:core/data/network/config/dynamic_url_interceptors.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart' hide State; 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/core/state.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.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:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.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/action/dashboard_action.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/mailbox_dashboard/presentation/model/search/search_email_filter.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/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/clean_and_get_emails_in_mailbox_interactor.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/model/search_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/search_status.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:tmail_ui_user/main/utils/twake_app_manager.dart'; import 'package:uuid/uuid.dart'; import '../../../../fixtures/account_fixtures.dart'; import '../../../../fixtures/session_fixtures.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(), 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; late MockCleanAndGetEmailsInMailboxInteractor mockCleanAndGetEmailsInMailboxInteractor; // 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 MockToastManager mockToastManager; late MockTwakeAppManager mockTwakeAppManager; 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(); mockToastManager = MockToastManager(); mockTwakeAppManager = MockTwakeAppManager(); 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(mockToastManager); Get.put(mockTwakeAppManager); // Mock thread controller mockNetworkConnectionController = MockNetworkConnectionController(); mockSearchController = MockSearchController(); mockMailboxDashBoardController = MockMailboxDashBoardController(); mockGetEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); mockRefreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); mockLoadMoreEmailsInMailboxInteractor = MockLoadMoreEmailsInMailboxInteractor(); mockSearchEmailInteractor = MockSearchEmailInteractor(); mockSearchMoreEmailInteractor = MockSearchMoreEmailInteractor(); mockGetEmailByIdInteractor = MockGetEmailByIdInteractor(); mockCleanAndGetEmailsInMailboxInteractor = MockCleanAndGetEmailsInMailboxInteractor(); Get.put(mockNetworkConnectionController); Get.put(mockSearchController); Get.put(mockMailboxDashBoardController); threadController = ThreadController( mockGetEmailsInMailboxInteractor, mockRefreshChangesEmailsInMailboxInteractor, mockLoadMoreEmailsInMailboxInteractor, mockSearchEmailInteractor, mockSearchMoreEmailInteractor, mockGetEmailByIdInteractor, mockCleanAndGetEmailsInMailboxInteractor, ); }); 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); }); }); group('_refreshEmailChanges::test', () { late ThreadController refreshChangesController; setUp(() { refreshChangesController = ThreadController( mockGetEmailsInMailboxInteractor, mockRefreshChangesEmailsInMailboxInteractor, mockLoadMoreEmailsInMailboxInteractor, mockSearchEmailInteractor, mockSearchMoreEmailInteractor, mockGetEmailByIdInteractor, mockCleanAndGetEmailsInMailboxInteractor, ); }); tearDown(() { refreshChangesController.onClose(); }); test( 'WHEN thread controller in searching\n' 'AND `MarkAsStarEmailSuccess` is coming\n' 'THEN `SearchEmailInteractor` is invoked with proper arguments\n' 'AND `listEmailController` should not jumped\n' 'AND `mailboxDashBoardController.emailsInCurrentMailbox` should not be cleared', () async { // Arrange PlatformInfo.isTestingForWeb = true; final emailList = [ PresentationEmail( id: EmailId(Id('email1')), subject: 'hello', keywords: {KeyWordIdentifier.emailFlagged: true}), PresentationEmail( id: EmailId(Id('email2')), subject: 'hello') ]; when(mockMailboxDashBoardController.sessionCurrent).thenReturn(SessionFixtures.aliceSession); when(mockMailboxDashBoardController.accountId).thenReturn(Rxn(AccountFixtures.aliceAccountId)); when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(SearchEmailSuccess(emailList)))); when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(RxList(emailList)); when(mockMailboxDashBoardController.selectedMailbox).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); when(mockMailboxDashBoardController.dashBoardAction).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.emailUIAction).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(UIState.idle))); when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all)); when(mockMailboxDashBoardController.currentEmailState).thenReturn(State('old-state')); when(mockSearchController.searchState).thenReturn(Rx(SearchState.initial())); when(mockSearchController.isAdvancedSearchViewOpen).thenReturn(RxBool(false)); when(mockSearchController.isSearchEmailRunning).thenReturn(true); when(mockSearchController.searchEmailFilter).thenReturn(Rx(SearchEmailFilter.initial())); when(mockSearchEmailInteractor.execute( any, any, limit: anyNamed('limit'), position: anyNamed('position'), sort:anyNamed('sort'), filter: anyNamed('filter'), properties: anyNamed('properties'), needRefreshSearchState: anyNamed('needRefreshSearchState'), )).thenAnswer((_) => Stream.value(Right(SearchEmailSuccess(emailList)))); when(mockRefreshChangesEmailsInMailboxInteractor.execute( any, any, any, sort: anyNamed('sort'), limit: anyNamed('limit'), propertiesCreated: anyNamed('propertiesCreated'), propertiesUpdated: anyNamed('propertiesUpdated'), emailFilter: anyNamed('emailFilter'), )).thenAnswer((_) => Stream.value(Right(RefreshChangesAllEmailSuccess( emailList: emailList, currentEmailState: State('old-state')))) ); // Act refreshChangesController.onInit(); mockMailboxDashBoardController.emailsInCurrentMailbox.refresh(); mockMailboxDashBoardController.emailUIAction.value = RefreshChangeEmailAction(newState: State('new-state')); await untilCalled(mockSearchEmailInteractor.execute( any, any, limit: anyNamed('limit'), position: anyNamed('position'), sort:anyNamed('sort'), filter: anyNamed('filter'), properties: anyNamed('properties'), needRefreshSearchState: anyNamed('needRefreshSearchState'), )); // Assert verify(mockSearchEmailInteractor.execute( SessionFixtures.aliceSession, AccountFixtures.aliceAccountId, limit: UnsignedInt(emailList.length), position: null, sort: SearchEmailFilter.defaultSortOrder.getSortOrder().toNullable(), filter: SearchEmailFilter.initial().mappingToEmailFilterCondition(), properties: EmailUtils.getPropertiesForEmailGetMethod( SessionFixtures.aliceSession, AccountFixtures.aliceAccountId), needRefreshSearchState: true )).called(1); expect(mockMailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty, isTrue); expect(mockMailboxDashBoardController.emailsInCurrentMailbox.length, emailList.length); expect(refreshChangesController.isListEmailScrollViewJumping, isFalse); PlatformInfo.isTestingForWeb = false; }); test( 'WHEN thread controller in searching\n' 'AND `StartSearchEmailAction` is coming\n' 'THEN `SearchEmailInteractor` is invoked with proper arguments\n' 'AND `listEmailController` should jumped\n' 'AND `mailboxDashBoardController.emailsInCurrentMailbox` should be cleared', () async { // Arrange final emailList = [ PresentationEmail( id: EmailId(Id('email1')), subject: 'hello'), PresentationEmail( id: EmailId(Id('email2')), subject: 'hello') ]; when(mockMailboxDashBoardController.sessionCurrent).thenReturn(SessionFixtures.aliceSession); when(mockMailboxDashBoardController.accountId).thenReturn(Rxn(AccountFixtures.aliceAccountId)); when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(SearchEmailSuccess(emailList)))); when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(RxList(emailList)); when(mockMailboxDashBoardController.selectedMailbox).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); when(mockMailboxDashBoardController.dashBoardAction).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.emailUIAction).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(UIState.idle))); when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all)); when(mockMailboxDashBoardController.currentSelectMode).thenReturn(Rx(SelectMode.INACTIVE)); when(mockMailboxDashBoardController.listEmailSelected).thenReturn(RxList([])); when(mockSearchController.searchState).thenReturn(Rx(SearchState.initial())); when(mockSearchController.isAdvancedSearchViewOpen).thenReturn(RxBool(false)); when(mockSearchController.isSearchEmailRunning).thenReturn(true); when(mockSearchController.searchEmailFilter).thenReturn(Rx(SearchEmailFilter.initial())); when(mockSearchEmailInteractor.execute( any, any, limit: anyNamed('limit'), position: anyNamed('position'), sort:anyNamed('sort'), filter: anyNamed('filter'), properties: anyNamed('properties'), needRefreshSearchState: anyNamed('needRefreshSearchState'), )).thenAnswer((_) => Stream.value(Right(SearchEmailSuccess(emailList)))); // Act refreshChangesController.onInit(); mockMailboxDashBoardController.dashBoardAction.value = StartSearchEmailAction(); await untilCalled(mockSearchEmailInteractor.execute( any, any, limit: anyNamed('limit'), position: anyNamed('position'), sort:anyNamed('sort'), filter: anyNamed('filter'), properties: anyNamed('properties'), needRefreshSearchState: anyNamed('needRefreshSearchState'), )); // Assert verify(mockSearchEmailInteractor.execute( SessionFixtures.aliceSession, AccountFixtures.aliceAccountId, limit: ThreadConstants.defaultLimit, position: null, sort: SearchEmailFilter.defaultSortOrder.getSortOrder().toNullable(), filter: SearchEmailFilter.initial().mappingToEmailFilterCondition(), properties: EmailUtils.getPropertiesForEmailGetMethod( SessionFixtures.aliceSession, AccountFixtures.aliceAccountId), needRefreshSearchState: false )).called(1); expect(mockMailboxDashBoardController.emailsInCurrentMailbox.isEmpty, isTrue); expect(mockMailboxDashBoardController.emailsInCurrentMailbox.length, equals(0)); }); }); group('_registerObxStreamListener test:', () { late ThreadController obxListenerController; setUp(() { obxListenerController = ThreadController( mockGetEmailsInMailboxInteractor, mockRefreshChangesEmailsInMailboxInteractor, mockLoadMoreEmailsInMailboxInteractor, mockSearchEmailInteractor, mockSearchMoreEmailInteractor, mockGetEmailByIdInteractor, mockCleanAndGetEmailsInMailboxInteractor, ); }); tearDown(() { obxListenerController.onClose(); }); test( 'should call _getEmailsInMailboxInteractor.execute with getLatestChanges is false ' 'when mailboxDashBoardController.selectedMailbox updated', () async { // arrange final mailboxBefore = PresentationMailbox(MailboxId(Id('mailbox-before-id'))); final mailboxAfter = PresentationMailbox(MailboxId(Id('mailbox-after-id'))); final selectedMailbox = Rxn(mailboxBefore); when(mockMailboxDashBoardController.sessionCurrent).thenReturn(SessionFixtures.aliceSession); when(mockMailboxDashBoardController.accountId).thenReturn(Rxn(AccountFixtures.aliceAccountId)); when(mockMailboxDashBoardController.selectedMailbox).thenReturn(selectedMailbox); when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); when(mockMailboxDashBoardController.dashBoardAction).thenReturn(Rxn()); when(mockMailboxDashBoardController.emailUIAction).thenReturn(Rxn()); when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(UIState.idle))); when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(RxList()); when(mockMailboxDashBoardController.listEmailSelected).thenReturn(RxList()); when(mockMailboxDashBoardController.currentSelectMode).thenReturn(Rx(SelectMode.INACTIVE)); when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all)); when(mockSearchController.searchState).thenReturn(SearchState(SearchStatus.INACTIVE).obs); // act obxListenerController.onInit(); mockMailboxDashBoardController.selectedMailbox.value = mailboxAfter; await untilCalled(mockGetEmailsInMailboxInteractor.execute( any, any, limit: anyNamed('limit'), sort: anyNamed('sort'), emailFilter: anyNamed('emailFilter'), propertiesCreated: anyNamed('propertiesCreated'), propertiesUpdated: anyNamed('propertiesUpdated'), getLatestChanges: false, )); // assert verify(mockGetEmailsInMailboxInteractor.execute( any, any, limit: anyNamed('limit'), sort: anyNamed('sort'), emailFilter: anyNamed('emailFilter'), propertiesCreated: anyNamed('propertiesCreated'), propertiesUpdated: anyNamed('propertiesUpdated'), getLatestChanges: false, )); }); }); group('limitEmailFetched::test', () { late RxList emailsRxList; late ThreadController limitEmailFetchedController; setUp(() { emailsRxList = RxList(); limitEmailFetchedController = ThreadController( mockGetEmailsInMailboxInteractor, mockRefreshChangesEmailsInMailboxInteractor, mockLoadMoreEmailsInMailboxInteractor, mockSearchEmailInteractor, mockSearchMoreEmailInteractor, mockGetEmailByIdInteractor, mockCleanAndGetEmailsInMailboxInteractor, ); when(mockMailboxDashBoardController.selectedMailbox).thenReturn(Rxn(null)); when(mockMailboxDashBoardController.searchController).thenReturn(mockSearchController); when(mockMailboxDashBoardController.dashBoardAction).thenReturn(Rxn()); when(mockMailboxDashBoardController.emailUIAction).thenReturn(Rxn()); when(mockMailboxDashBoardController.viewState).thenReturn(Rx(Right(UIState.idle))); when(mockMailboxDashBoardController.emailsInCurrentMailbox).thenReturn(emailsRxList); when(mockMailboxDashBoardController.listEmailSelected).thenReturn(RxList()); when(mockMailboxDashBoardController.currentSelectMode).thenReturn(Rx(SelectMode.INACTIVE)); when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all)); when(mockSearchController.searchState).thenReturn(SearchState(SearchStatus.INACTIVE).obs); limitEmailFetchedController.onInit(); }); tearDown(() { limitEmailFetchedController.onClose(); }); List generateEmails(int count, {String prefix = 'email'}) { return List.generate( count, (i) => PresentationEmail(id: EmailId(Id('$prefix$i'))), ); } test( 'SHOULD return defaultLimit\n' 'WHEN no emails loaded', () { // Assert expect(limitEmailFetchedController.limitEmailFetched, ThreadConstants.defaultLimit); }); test( 'SHOULD return email count\n' 'WHEN emails are loaded into the list', () { // Act emailsRxList.addAll(generateEmails(40)); // Assert expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(40)); }); test( 'SHOULD retain peak count\n' 'WHEN emails are bulk deleted from the list', () { // Arrange emailsRxList.addAll(generateEmails(40)); // Act - simulate bulk delete (removeWhere) emailsRxList.removeRange(0, 20); // Assert - peak should still be 40, not 20 expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(40)); }); test( 'SHOULD reset to defaultLimit\n' 'WHEN resetToOriginalValue is called (mailbox switch)', () { // Arrange emailsRxList.addAll(generateEmails(40)); expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(40)); // Act limitEmailFetchedController.resetToOriginalValue(); // Assert expect(limitEmailFetchedController.limitEmailFetched, ThreadConstants.defaultLimit); }); test( 'SHOULD track peak across load-more then delete\n' 'WHEN emails are loaded incrementally and then partially deleted', () { // Arrange - simulate initial load + 2 load-mores emailsRxList.addAll(generateEmails(20, prefix: 'init')); emailsRxList.addAll(generateEmails(20, prefix: 'more1')); emailsRxList.addAll(generateEmails(20, prefix: 'more2')); expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(60)); // Act - delete 30 emails emailsRxList.removeRange(0, 30); // Assert - peak was 60 expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(60)); }); test( 'SHOULD reset between mailbox switches\n' 'WHEN switching to a mailbox with fewer emails', () { // Arrange - first mailbox has 40 emails emailsRxList.addAll(generateEmails(40)); expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(40)); // Act - switch mailbox (reset) then load fewer emails limitEmailFetchedController.resetToOriginalValue(); emailsRxList.addAll(generateEmails(15, prefix: 'new')); // Assert - peak should be 15, not stale 40 expect(limitEmailFetchedController.limitEmailFetched, UnsignedInt(15)); }); }); }); }