Fix email list does not automatically refill after bulk delete (#4323)
This commit is contained in:
@@ -594,7 +594,9 @@ class MailboxController extends BaseMailboxController
|
|||||||
await _handleRefreshChangeMailboxSuccess(refreshState);
|
await _handleRefreshChangeMailboxSuccess(refreshState);
|
||||||
} else {
|
} else {
|
||||||
_clearNewFolderId();
|
_clearNewFolderId();
|
||||||
onDataFailureViewState(refreshState);
|
if (refreshState != null) {
|
||||||
|
onDataFailureViewState(refreshState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
logWarning('MailboxController::_processMailboxStateQueue:Error processing state: $e');
|
logWarning('MailboxController::_processMailboxStateQueue:Error processing state: $e');
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
bool canLoadMore = false;
|
bool canLoadMore = false;
|
||||||
bool canSearchMore = false;
|
bool canSearchMore = false;
|
||||||
MailboxId? _currentMemoryMailboxId;
|
MailboxId? _currentMemoryMailboxId;
|
||||||
|
int _peakEmailCount = 0;
|
||||||
final ScrollController listEmailController = ScrollController();
|
final ScrollController listEmailController = ScrollController();
|
||||||
final latestEmailSelectedOrUnselected = Rxn<PresentationEmail>();
|
final latestEmailSelectedOrUnselected = Rxn<PresentationEmail>();
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
@@ -408,6 +409,20 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
_checkIfCurrentMailboxCanLoadMore();
|
_checkIfCurrentMailboxCanLoadMore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ever(mailboxDashBoardController.emailsInCurrentMailbox, (emails) {
|
||||||
|
final countEmails = emails.length;
|
||||||
|
log(
|
||||||
|
'ThreadController::Ever(mailboxDashBoardController.emailsInCurrentMailbox): '
|
||||||
|
'Count emails is $countEmails, '
|
||||||
|
'_peakEmailCount is $_peakEmailCount',
|
||||||
|
);
|
||||||
|
if (emails.isEmpty) {
|
||||||
|
_peakEmailCount = 0;
|
||||||
|
} else if (countEmails > _peakEmailCount) {
|
||||||
|
_peakEmailCount = countEmails;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleMarkEmailsAsReadByMailboxId(MailboxId mailboxId) {
|
void _handleMarkEmailsAsReadByMailboxId(MailboxId mailboxId) {
|
||||||
@@ -571,7 +586,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
if (mailboxDashBoardController.isSelectionEnabled()) {
|
if (mailboxDashBoardController.isSelectionEnabled()) {
|
||||||
mailboxDashBoardController.listEmailSelected.value = listEmailSelected;
|
mailboxDashBoardController.listEmailSelected.value = listEmailSelected;
|
||||||
}
|
}
|
||||||
canLoadMore = newListEmail.length >= ThreadConstants.maxCountEmails;
|
canLoadMore = emailListSynced.length >= ThreadConstants.maxCountEmails;
|
||||||
|
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
_validateBrowserHeight();
|
_validateBrowserHeight();
|
||||||
@@ -616,7 +631,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canLoadMore = false;
|
canLoadMore = false;
|
||||||
loadingMoreStatus.value == LoadingMoreStatus.idle;
|
loadingMoreStatus.value = LoadingMoreStatus.idle;
|
||||||
cancelSelectEmail();
|
cancelSelectEmail();
|
||||||
|
|
||||||
if (searchController.isSearchEmailRunning) {
|
if (searchController.isSearchEmailRunning) {
|
||||||
@@ -627,11 +642,9 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UnsignedInt get limitEmailFetched {
|
UnsignedInt get limitEmailFetched {
|
||||||
final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox;
|
return _peakEmailCount == 0
|
||||||
final limit = emailsInCurrentMailbox.isNotEmpty
|
? ThreadConstants.defaultLimit
|
||||||
? UnsignedInt(emailsInCurrentMailbox.length)
|
: UnsignedInt(_peakEmailCount);
|
||||||
: ThreadConstants.defaultLimit;
|
|
||||||
return limit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _refreshEmailChanges({required jmap.State newState}) {
|
void _refreshEmailChanges({required jmap.State newState}) {
|
||||||
@@ -699,6 +712,8 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
needRefreshSearchState: true,
|
needRefreshSearchState: true,
|
||||||
).last;
|
).last;
|
||||||
|
|
||||||
|
dispatchState(searchViewState);
|
||||||
|
|
||||||
final searchState = searchViewState
|
final searchState = searchViewState
|
||||||
.foldSuccessWithResult<SearchEmailSuccess>();
|
.foldSuccessWithResult<SearchEmailSuccess>();
|
||||||
|
|
||||||
@@ -709,7 +724,9 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
Left(RefreshAllEmailFailure()));
|
Left(RefreshAllEmailFailure()));
|
||||||
canSearchMore = false;
|
canSearchMore = false;
|
||||||
mailboxDashBoardController.emailsInCurrentMailbox.clear();
|
mailboxDashBoardController.emailsInCurrentMailbox.clear();
|
||||||
onDataFailureViewState(searchState);
|
if (searchState != null) {
|
||||||
|
onDataFailureViewState(searchState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,9 +767,11 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
final refreshState = refreshViewState
|
final refreshState = refreshViewState
|
||||||
.foldSuccessWithResult<RefreshChangesAllEmailSuccess>();
|
.foldSuccessWithResult<RefreshChangesAllEmailSuccess>();
|
||||||
|
|
||||||
|
dispatchState(refreshViewState);
|
||||||
|
|
||||||
if (refreshState is RefreshChangesAllEmailSuccess) {
|
if (refreshState is RefreshChangesAllEmailSuccess) {
|
||||||
_refreshChangesAllEmailSuccess(refreshState);
|
_refreshChangesAllEmailSuccess(refreshState);
|
||||||
} else {
|
} else if (refreshState != null) {
|
||||||
onDataFailureViewState(refreshState);
|
onDataFailureViewState(refreshState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -781,12 +800,14 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
)
|
)
|
||||||
.last;
|
.last;
|
||||||
|
|
||||||
|
dispatchState(viewState);
|
||||||
|
|
||||||
final emailSuccessState = viewState
|
final emailSuccessState = viewState
|
||||||
.foldSuccessWithResult<GetAllEmailSuccess>();
|
.foldSuccessWithResult<GetAllEmailSuccess>();
|
||||||
|
|
||||||
if (emailSuccessState is GetAllEmailSuccess) {
|
if (emailSuccessState is GetAllEmailSuccess) {
|
||||||
_getAllEmailSuccess(emailSuccessState);
|
_getAllEmailSuccess(emailSuccessState);
|
||||||
} else {
|
} else if (emailSuccessState != null) {
|
||||||
onDataFailureViewState(emailSuccessState);
|
onDataFailureViewState(emailSuccessState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,6 +406,7 @@ void main() {
|
|||||||
mockGetEmailByIdInteractor,
|
mockGetEmailByIdInteractor,
|
||||||
mockCleanAndGetEmailsInMailboxInteractor,
|
mockCleanAndGetEmailsInMailboxInteractor,
|
||||||
);
|
);
|
||||||
|
threadController.onInit();
|
||||||
|
|
||||||
mailboxDashboardController.sessionCurrent = SessionFixtures.aliceSession;
|
mailboxDashboardController.sessionCurrent = SessionFixtures.aliceSession;
|
||||||
mailboxDashboardController.filterMessageOption.value = FilterMessageOption.all;
|
mailboxDashboardController.filterMessageOption.value = FilterMessageOption.all;
|
||||||
|
|||||||
@@ -266,6 +266,24 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('_refreshEmailChanges::test', () {
|
group('_refreshEmailChanges::test', () {
|
||||||
|
late ThreadController refreshChangesController;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
refreshChangesController = ThreadController(
|
||||||
|
mockGetEmailsInMailboxInteractor,
|
||||||
|
mockRefreshChangesEmailsInMailboxInteractor,
|
||||||
|
mockLoadMoreEmailsInMailboxInteractor,
|
||||||
|
mockSearchEmailInteractor,
|
||||||
|
mockSearchMoreEmailInteractor,
|
||||||
|
mockGetEmailByIdInteractor,
|
||||||
|
mockCleanAndGetEmailsInMailboxInteractor,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
refreshChangesController.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'WHEN thread controller in searching\n'
|
'WHEN thread controller in searching\n'
|
||||||
'AND `MarkAsStarEmailSuccess` is coming\n'
|
'AND `MarkAsStarEmailSuccess` is coming\n'
|
||||||
@@ -310,23 +328,24 @@ void main() {
|
|||||||
properties: anyNamed('properties'),
|
properties: anyNamed('properties'),
|
||||||
needRefreshSearchState: anyNamed('needRefreshSearchState'),
|
needRefreshSearchState: anyNamed('needRefreshSearchState'),
|
||||||
)).thenAnswer((_) => Stream.value(Right(SearchEmailSuccess(emailList))));
|
)).thenAnswer((_) => Stream.value(Right(SearchEmailSuccess(emailList))));
|
||||||
|
|
||||||
when(mockRefreshChangesEmailsInMailboxInteractor.execute(
|
when(mockRefreshChangesEmailsInMailboxInteractor.execute(
|
||||||
any,
|
any,
|
||||||
any,
|
any,
|
||||||
any,
|
any,
|
||||||
sort: anyNamed('sort'),
|
sort: anyNamed('sort'),
|
||||||
limit: anyNamed('limit'),
|
limit: anyNamed('limit'),
|
||||||
propertiesCreated: anyNamed('propertiesCreated'),
|
propertiesCreated: anyNamed('propertiesCreated'),
|
||||||
propertiesUpdated: anyNamed('propertiesUpdated'),
|
propertiesUpdated: anyNamed('propertiesUpdated'),
|
||||||
emailFilter: anyNamed('emailFilter'),
|
emailFilter: anyNamed('emailFilter'),
|
||||||
)).thenAnswer((_) => Stream.value(Right(RefreshChangesAllEmailSuccess(
|
)).thenAnswer((_) => Stream.value(Right(RefreshChangesAllEmailSuccess(
|
||||||
emailList: emailList,
|
emailList: emailList,
|
||||||
currentEmailState: State('old-state'))))
|
currentEmailState: State('old-state'))))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
threadController.onInit();
|
refreshChangesController.onInit();
|
||||||
|
mockMailboxDashBoardController.emailsInCurrentMailbox.refresh();
|
||||||
|
|
||||||
mockMailboxDashBoardController.emailUIAction.value =
|
mockMailboxDashBoardController.emailUIAction.value =
|
||||||
RefreshChangeEmailAction(newState: State('new-state'));
|
RefreshChangeEmailAction(newState: State('new-state'));
|
||||||
@@ -357,7 +376,7 @@ void main() {
|
|||||||
)).called(1);
|
)).called(1);
|
||||||
expect(mockMailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty, isTrue);
|
expect(mockMailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty, isTrue);
|
||||||
expect(mockMailboxDashBoardController.emailsInCurrentMailbox.length, emailList.length);
|
expect(mockMailboxDashBoardController.emailsInCurrentMailbox.length, emailList.length);
|
||||||
expect(threadController.isListEmailScrollViewJumping, isFalse);
|
expect(refreshChangesController.isListEmailScrollViewJumping, isFalse);
|
||||||
PlatformInfo.isTestingForWeb = false;
|
PlatformInfo.isTestingForWeb = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -406,7 +425,7 @@ void main() {
|
|||||||
)).thenAnswer((_) => Stream.value(Right(SearchEmailSuccess(emailList))));
|
)).thenAnswer((_) => Stream.value(Right(SearchEmailSuccess(emailList))));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
threadController.onInit();
|
refreshChangesController.onInit();
|
||||||
mockMailboxDashBoardController.dashBoardAction.value = StartSearchEmailAction();
|
mockMailboxDashBoardController.dashBoardAction.value = StartSearchEmailAction();
|
||||||
|
|
||||||
await untilCalled(mockSearchEmailInteractor.execute(
|
await untilCalled(mockSearchEmailInteractor.execute(
|
||||||
@@ -439,6 +458,24 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('_registerObxStreamListener test:', () {
|
group('_registerObxStreamListener test:', () {
|
||||||
|
late ThreadController obxListenerController;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
obxListenerController = ThreadController(
|
||||||
|
mockGetEmailsInMailboxInteractor,
|
||||||
|
mockRefreshChangesEmailsInMailboxInteractor,
|
||||||
|
mockLoadMoreEmailsInMailboxInteractor,
|
||||||
|
mockSearchEmailInteractor,
|
||||||
|
mockSearchMoreEmailInteractor,
|
||||||
|
mockGetEmailByIdInteractor,
|
||||||
|
mockCleanAndGetEmailsInMailboxInteractor,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
obxListenerController.onClose();
|
||||||
|
});
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'should call _getEmailsInMailboxInteractor.execute with getLatestChanges is false '
|
'should call _getEmailsInMailboxInteractor.execute with getLatestChanges is false '
|
||||||
'when mailboxDashBoardController.selectedMailbox updated',
|
'when mailboxDashBoardController.selectedMailbox updated',
|
||||||
@@ -459,9 +496,9 @@ void main() {
|
|||||||
when(mockMailboxDashBoardController.currentSelectMode).thenReturn(Rx(SelectMode.INACTIVE));
|
when(mockMailboxDashBoardController.currentSelectMode).thenReturn(Rx(SelectMode.INACTIVE));
|
||||||
when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all));
|
when(mockMailboxDashBoardController.filterMessageOption).thenReturn(Rx(FilterMessageOption.all));
|
||||||
when(mockSearchController.searchState).thenReturn(SearchState(SearchStatus.INACTIVE).obs);
|
when(mockSearchController.searchState).thenReturn(SearchState(SearchStatus.INACTIVE).obs);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
threadController.onInit();
|
obxListenerController.onInit();
|
||||||
mockMailboxDashBoardController.selectedMailbox.value = mailboxAfter;
|
mockMailboxDashBoardController.selectedMailbox.value = mailboxAfter;
|
||||||
await untilCalled(mockGetEmailsInMailboxInteractor.execute(
|
await untilCalled(mockGetEmailsInMailboxInteractor.execute(
|
||||||
any,
|
any,
|
||||||
@@ -487,5 +524,129 @@ void main() {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('limitEmailFetched::test', () {
|
||||||
|
late RxList<PresentationEmail> emailsRxList;
|
||||||
|
late ThreadController limitEmailFetchedController;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
emailsRxList = RxList<PresentationEmail>();
|
||||||
|
|
||||||
|
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<PresentationEmail> 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));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user