fix(ux): navigate back after deleting all search results (#85)

When the user searches in a mailbox, selects all results, and deletes
them, re-evaluate the search. If no results remain and there is a
previous screen in the navigation stack, pop back to it instead of
clearing the search and showing the regular inbox.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas SharedInbox
2026-05-15 15:36:05 +02:00
co-authored by Claude Sonnet 4.6
parent ef3fb72f4e
commit 122358c9a2
2 changed files with 86 additions and 1 deletions
+5 -1
View File
@@ -503,7 +503,11 @@ class _EmailListScreenState extends ConsumerState<EmailListScreen> {
.searchEmails(widget.accountId, widget.mailboxPath, searchQuery);
if (!mounted) return;
if (remaining.isEmpty) {
_searchController.clear();
if (context.canPop()) {
context.pop();
} else {
_searchController.clear();
}
} else {
setState(() => _searchResults = remaining);
}
+81
View File
@@ -3,9 +3,30 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:sharedinbox/core/models/email.dart';
import 'package:sharedinbox/di.dart';
import 'package:sharedinbox/ui/screens/email_list_screen.dart';
import 'package:sharedinbox/ui/screens/mailbox_list_screen.dart';
import 'helpers.dart';
// A fake email repository whose search results can be changed mid-test.
class _MutableFakeEmailRepository extends FakeEmailRepository {
List<Email> _results;
_MutableFakeEmailRepository(List<Email> initial)
: _results = List.of(initial),
super();
void setSearchResults(List<Email> results) => _results = results;
@override
Future<List<Email>> searchEmails(
String accountId,
String mailboxPath,
String query,
) async =>
_results;
}
final _kDate = DateTime(2024, 6);
void main() {
@@ -405,6 +426,66 @@ void main() {
expect(find.text('Result email'), findsWidgets);
});
testWidgets(
'deleting all search results pops back to previous screen',
(tester) async {
final email = testEmail(subject: 'Needle');
final repo = _MutableFakeEmailRepository([email]);
// Start at the mailbox list so the email list is pushed on top of it,
// making context.canPop() == true inside EmailListScreen.
await tester.pumpWidget(
buildApp(
initialLocation: '/accounts/acc-1/mailboxes',
overrides: [
accountRepositoryProvider.overrideWithValue(
FakeAccountRepository([kTestAccount]),
),
mailboxRepositoryProvider.overrideWithValue(
FakeMailboxRepository([kTestMailbox]),
),
emailRepositoryProvider.overrideWithValue(repo),
],
),
);
await tester.pumpAndSettle();
expect(find.byType(MailboxListScreen), findsOneWidget);
// Navigate into INBOX (pushes EmailListScreen onto the stack).
await tester.tap(find.text('INBOX'));
await tester.pumpAndSettle();
expect(find.byType(EmailListScreen), findsOneWidget);
// Search for the email.
await tester.enterText(find.byType(TextField), 'Needle');
await tester.testTextInput.receiveAction(TextInputAction.search);
await tester.pumpAndSettle();
// 'Needle' also appears in the SearchBar input, so match at least one.
expect(find.text('Needle'), findsAtLeastNWidgets(1));
// Long-press the sender name (unique to the email tile) to enter
// selection mode.
await tester.longPress(find.text('Bob'));
await tester.pumpAndSettle();
await tester.tap(find.byIcon(Icons.select_all));
await tester.pumpAndSettle();
// After deletion the search re-runs and finds nothing.
repo.setSearchResults([]);
await tester.tap(find.byIcon(Icons.delete));
await tester.pumpAndSettle();
// Should have popped back to the mailbox list.
expect(find.byType(EmailListScreen), findsNothing);
expect(find.byType(MailboxListScreen), findsOneWidget);
},
);
testWidgets('shows preview snippet when email has preview', (tester) async {
final email = Email(
id: 'acc-1:99',