TF-4284 Fix refreshing causes "virtual mailboxes" to be hidden (#4290)

This commit is contained in:
Dat Vu
2026-04-10 15:29:04 +07:00
committed by GitHub
parent c9e2e5db80
commit cb5d43630a
11 changed files with 493 additions and 225 deletions
@@ -8,6 +8,7 @@ import 'package:model/mailbox/expand_mode.dart';
import 'package:model/mailbox/mailbox_state.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
@@ -256,9 +257,7 @@ void main() {
final generatedTree = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: testCase,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
);
expect(
@@ -323,9 +322,7 @@ void main() {
() async {
final generatedTrees = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: filteredMailboxes,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
);
expectNoVirtualFoldersRecursively(
@@ -350,9 +347,7 @@ void main() {
final generatedTrees =
await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges(
allMailboxes: filteredMailboxes,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
);
expectNoVirtualFoldersRecursively(
@@ -392,9 +387,7 @@ void main() {
final generatedTree = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: filtered,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
);
final children = generatedTree.defaultTree.root.childrenItems ?? [];
@@ -437,9 +430,7 @@ void main() {
final generatedTree = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: testCaseWithVirtualFolders,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
);
final children = generatedTree.defaultTree.root.childrenItems ?? [];
@@ -484,9 +475,7 @@ void main() {
final result =
await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges(
allMailboxes: mailboxes,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
);
expect(result.defaultTree.root.childrenItems?.length, equals(1));
@@ -542,9 +531,12 @@ void main() {
final result =
await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges(
allMailboxes: [mailbox],
currentDefaultTree: currentDefaultTree,
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection(
allMailboxes: const [],
defaultTree: currentDefaultTree,
personalTree: MailboxTree(MailboxNode.root()),
teamMailboxTree: MailboxTree(MailboxNode.root()),
),
);
final newNode = result.defaultTree.root.childrenItems?.first;
@@ -561,6 +553,128 @@ void main() {
});
});
group('MailboxCollection allMailboxes field', () {
final inbox = PresentationMailbox(
MailboxId(Id('inbox')),
name: MailboxName('Inbox'),
role: Role('inbox'),
);
final sent = PresentationMailbox(
MailboxId(Id('sent')),
name: MailboxName('Sent'),
role: Role('sent'),
);
test(
'generateMailboxTreeInUIAfterRefreshChanges: allMailboxes excludes virtual folders from previous collection',
() async {
final virtualFavorite = PresentationMailbox.favoriteFolder;
final previousDefaultTree = MailboxTree(MailboxNode.root())
..root.addChildNode(MailboxNode(virtualFavorite));
final previousCollection = MailboxCollection(
allMailboxes: [inbox, virtualFavorite],
defaultTree: previousDefaultTree,
personalTree: MailboxTree(MailboxNode.root()),
teamMailboxTree: MailboxTree(MailboxNode.root()),
);
final result = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges(
allMailboxes: [inbox, sent],
currentCollection: previousCollection,
);
expect(
result.allMailboxes.every((m) => !m.isVirtualFolder),
isTrue,
reason: 'Virtual folders from previousCollection must not bleed into result.allMailboxes',
);
expect(result.allMailboxes.length, equals(2));
},
);
test(
'generateMailboxTreeInUIAfterRefreshChanges: deleted server mailboxes are excluded from result',
() async {
final deleted = PresentationMailbox(
MailboxId(Id('deleted')),
name: MailboxName('Old Folder'),
);
final previousDefaultTree = MailboxTree(MailboxNode.root())
..root.addChildNode(MailboxNode(deleted));
final previousCollection = MailboxCollection(
allMailboxes: [inbox, deleted],
defaultTree: previousDefaultTree,
personalTree: MailboxTree(MailboxNode.root()),
teamMailboxTree: MailboxTree(MailboxNode.root()),
);
final result = await TreeBuilder().generateMailboxTreeInUIAfterRefreshChanges(
allMailboxes: [inbox],
currentCollection: previousCollection,
);
expect(result.allMailboxes.length, equals(1));
expect(
result.allMailboxes.any((m) => m.id == deleted.id),
isFalse,
reason: 'Mailbox removed from server must not appear in result',
);
},
);
test(
'generateMailboxTreeInUI: deleted server mailboxes are excluded from result',
() async {
final deleted = PresentationMailbox(
MailboxId(Id('deleted')),
name: MailboxName('Old Folder'),
);
final previousDefaultTree = MailboxTree(MailboxNode.root())
..root.addChildNode(MailboxNode(deleted));
final previousCollection = MailboxCollection(
allMailboxes: [inbox, deleted],
defaultTree: previousDefaultTree,
personalTree: MailboxTree(MailboxNode.root()),
teamMailboxTree: MailboxTree(MailboxNode.root()),
);
final result = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: [inbox],
currentCollection: previousCollection,
);
expect(result.allMailboxes.length, equals(1));
expect(
result.allMailboxes.any((m) => m.id == deleted.id),
isFalse,
reason: 'Mailbox removed from server must not appear in result',
);
},
);
test(
'generateMailboxTreeInUI: selected mailbox in allMailboxes has state deactivated',
() async {
final result = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: [inbox, sent],
currentCollection: MailboxCollection.empty(),
mailboxIdSelected: inbox.id,
);
final selectedInResult = result.allMailboxes.firstWhere((m) => m.id == inbox.id);
expect(
selectedInResult.state,
equals(MailboxState.deactivated),
reason: 'Selected mailbox must be deactivated in allMailboxes',
);
expect(result.allMailboxes.length, equals(2));
},
);
});
group('Cascading Deactivation (generateMailboxTreeInUI only)', () {
test('Selecting a parent deactivates it and cascades to its children',
() async {
@@ -589,9 +703,7 @@ void main() {
final result = await TreeBuilder().generateMailboxTreeInUI(
allMailboxes: mailboxes,
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
mailboxIdSelected: parentId,
);
@@ -60,6 +60,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_mailbox
import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_multiple_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view_web.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
@@ -816,14 +817,12 @@ void main() {
when(
treeBuilder.generateMailboxTreeInUI(
allMailboxes: anyNamed('allMailboxes'),
currentDefaultTree: anyNamed('currentDefaultTree'),
currentPersonalTree: anyNamed('currentPersonalTree'),
currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'),
currentCollection: anyNamed('currentCollection'),
mailboxIdSelected: anyNamed('mailboxIdSelected'),
mailboxIdExpanded: anyNamed('mailboxIdExpanded'),
),
).thenAnswer(
(_) async => (
(_) async => MailboxCollection(
allMailboxes: currentMailboxList,
defaultTree: defaultTree,
personalTree: personalTree,
@@ -865,9 +864,7 @@ void main() {
verify(
treeBuilder.generateMailboxTreeInUI(
allMailboxes: anyNamed('allMailboxes'),
currentDefaultTree: anyNamed('currentDefaultTree'),
currentPersonalTree: anyNamed('currentPersonalTree'),
currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'),
currentCollection: anyNamed('currentCollection'),
mailboxIdSelected: anyNamed('mailboxIdSelected'),
mailboxIdExpanded: anyNamed('mailboxIdExpanded'),
),
@@ -958,14 +955,12 @@ void main() {
when(
treeBuilder.generateMailboxTreeInUI(
allMailboxes: anyNamed('allMailboxes'),
currentDefaultTree: anyNamed('currentDefaultTree'),
currentPersonalTree: anyNamed('currentPersonalTree'),
currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'),
currentCollection: anyNamed('currentCollection'),
mailboxIdSelected: anyNamed('mailboxIdSelected'),
mailboxIdExpanded: anyNamed('mailboxIdExpanded'),
),
).thenAnswer(
(_) async => (
(_) async => MailboxCollection(
allMailboxes: currentMailboxList,
defaultTree: defaultTree,
personalTree: personalTree,
@@ -1008,9 +1003,7 @@ void main() {
verify(
treeBuilder.generateMailboxTreeInUI(
allMailboxes: anyNamed('allMailboxes'),
currentDefaultTree: anyNamed('currentDefaultTree'),
currentPersonalTree: anyNamed('currentPersonalTree'),
currentTeamMailboxTree: anyNamed('currentTeamMailboxTree'),
currentCollection: anyNamed('currentCollection'),
mailboxIdSelected: anyNamed('mailboxIdSelected'),
mailboxIdExpanded: anyNamed('mailboxIdExpanded'),
),
@@ -32,6 +32,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oi
import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart';
import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_collection.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
@@ -211,11 +212,9 @@ void main() {
when(mockTreeBuilder.generateMailboxTreeInUI(
allMailboxes: [spamMailbox],
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
)).thenAnswer((_) async {
return (
return MailboxCollection(
allMailboxes: [spamMailbox],
defaultTree: MailboxTree(MailboxNode(spamMailbox)),
personalTree: MailboxTree(MailboxNode.root()),
@@ -292,11 +291,9 @@ void main() {
when(mockTreeBuilder.generateMailboxTreeInUI(
allMailboxes: [mailboxA],
currentDefaultTree: MailboxTree(MailboxNode.root()),
currentPersonalTree: MailboxTree(MailboxNode.root()),
currentTeamMailboxTree: MailboxTree(MailboxNode.root()),
currentCollection: MailboxCollection.empty(),
)).thenAnswer((_) async {
return (
return MailboxCollection(
allMailboxes: [mailboxA],
defaultTree: MailboxTree(MailboxNode.root()),
personalTree: MailboxTree(MailboxNode(mailboxA)),