diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-29 12:45:39 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-29 12:45:39 +0200 |
| commit | ca8b140de023d47ff7b34261051749ed9b254d4d (patch) | |
| tree | 93e082d5991a7e5125fc5317a7d88536edd8b177 /tests/test_mainwindow.cpp | |
| parent | 063be87405277aef3122c448b064241fd15f2a92 (diff) | |
| download | qtmaildir-ca8b140de023d47ff7b34261051749ed9b254d4d.tar.gz qtmaildir-ca8b140de023d47ff7b34261051749ed9b254d4d.zip | |
feat: give the trash its own actions on the message bar
The pane's bar offered Reply and Forward on a message the user had thrown
away, which are the two things a trashed message is least likely to want,
while Restore and the purges lived only in menus.
The bar now has a third branch, asked before the draft one: a deleted draft
must come out of the trash before it can be edited. It is keyed on the
SELECTION being in a trash folder, the same predicate the menu entries use,
rather than on the trash VIEW, which disagree on mail reached from a search.
It carries Restore, Delete permanently and Empty trash, and only Restore is
tinted: the two purges are one act at two scopes and need no colour to tell
them from each other, only from the one action that gives mail back.
Delete moves here from the main toolbar in the same change (item 186). It
acts on the displayed message, like Reply and Forward, so it belongs on the
pane's bar by the rule items 139 to 141 settled for those two. It stays in
the Message menu and the context menu.
Delete permanently is new. It is Empty trash scoped to the selection, the
same purgeMessages() call with the ids resolved from the selection rather
than from a query, so it inherits both of that action's safeguards: it
confirms, naming the count, and it carries no default shortcut. One combined
thread:/id: query resolves a mixed selection, so a conversation and a reply
selected together still ask once.
The bar is refilled when the conversation digest arrives as well as on
selection, since a conversation's trash-ness is not known until every path
has been reported.
Closes items 185 and 186.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NY6poqw199LfFaXe5BHKNe
Diffstat (limited to 'tests/test_mainwindow.cpp')
| -rw-r--r-- | tests/test_mainwindow.cpp | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 48ea95b..90418e3 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -514,6 +514,7 @@ private slots: void aDraftReopensWithItsOwnContent(); void editDraftIsOfferedOnlyForADraft(); void theMessageBarOffersEditOnADraft(); + void theMessageBarSwapsToTheTrashActionsOnTrashedMail(); void doubleClickingADraftOpensTheComposer(); void aResumedDraftReplacesItsFileRatherThanAddingOne(); void aResumedDraftKeepsItsBlindRecipients(); @@ -13817,6 +13818,127 @@ void TestMainWindow::editDraftIsOfferedOnlyForADraft() "Edit draft is not offered on a message in the drafts folder"); } +void TestMainWindow::theMessageBarSwapsToTheTrashActionsOnTrashedMail() +{ + // Items 185 and 186. The bar offered Reply and Forward on a message the + // user had thrown away, which are the two things a trashed message is + // least likely to want, while Restore and the two purges lived only in + // menus. Delete moved here from the main toolbar in the same change. + // + // Asserted on WHICH ACTIONS the bar carries, which has a right answer. + // The tint on Restore does not and is not tested: a probe counting + // coloured pixels passes whatever the stylesheet says, for the reasons + // CLAUDE.md records under rendering probes. + WorkerComposeFixture fixture; + QVERIFY(fixture.backed.fixture().addMessage( + QStringLiteral("acct/Trash"), QStringLiteral("trashed1@example.org"), + QStringLiteral("Thrown away"), QStringLiteral("sender@example.org"), + QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"), + QStringLiteral("Body."))); + QVERIFY2(fixture.seed({ { QStringLiteral("acct"), QStringLiteral("acct"), + QStringLiteral("Trash"), + QStringLiteral("/bin/true"), + QStringLiteral("you@example.org"), + QStringLiteral("Drafts") } }, + QStringLiteral("acct/inbox")), + qPrintable(fixture.backed.error())); + + MainWindow window(fixture.backed.config()); + auto *model = window.findChild<ThreadListModel *>(); + auto *view = window.findChild<ThreadListView *>(); + auto *queryEdit = + window.findChild<QLineEdit *>(QStringLiteral("queryEdit")); + auto *bar = window.findChild<QToolBar *>(QStringLiteral("message_toolbar")); + QVERIFY(model && view && queryEdit && bar); + + const auto selectById = [&](const QString &id) { + queryEdit->setText(QStringLiteral("id:") + id); + queryEdit->returnPressed(); + bool ready = false; + for (int attempt = 0; attempt < 150 && !ready; ++attempt) { + ready = model->rowCount(QModelIndex()) == 1 + && !window.mailRootForTesting().isEmpty(); + if (!ready) + QTest::qWait(100); + } + if (!ready) + return false; + view->setCurrentIndex(model->index(0, 0, QModelIndex())); + // The bar's trash branch reads the same predicate the menus do, and + // for a conversation row that predicate is only right once the digest + // has reported every path. Give the round trip a moment. + QTest::qWait(300); + return true; + }; + + const auto barHolds = [&](const QString &name) { + const auto actions = bar->actions(); + return std::any_of(actions.cbegin(), actions.cend(), + [&](const QAction *action) { + return action && action->objectName() == name + && action->isVisible(); + }); + }; + + // Ordinary mail first, so the trash assertions below mean something: a bar + // that never holds Reply passes the "no Reply in the trash" check by + // accident. This is also item 186's assertion, since Delete is on the bar + // here only because it was moved off the main toolbar. + QVERIFY2(selectById(QStringLiteral("compose1@example.org")), + "the inbox message was not found"); + QVERIFY2(barHolds(QStringLiteral("reply")), + "the message bar lost Reply on ordinary mail"); + QVERIFY2(barHolds(QStringLiteral("forward")), + "the message bar lost Forward on ordinary mail"); + QVERIFY2(barHolds(QStringLiteral("delete")), + "Delete did not arrive on the message bar (item 186)"); + QVERIFY2(!barHolds(QStringLiteral("restore")), + "Restore is offered on mail that was never deleted"); + QVERIFY2(!barHolds(QStringLiteral("purge")), + "Delete permanently is offered outside the trash"); + + // The main toolbar must have LOST it, or item 186 moved nothing and the + // action simply appears twice. + auto *mainBar = window.findChild<QToolBar *>(QStringLiteral("main_toolbar")); + QVERIFY(mainBar); + const auto mainActions = mainBar->actions(); + QVERIFY2(std::none_of(mainActions.cbegin(), mainActions.cend(), + [](const QAction *action) { + return action + && action->objectName() + == QStringLiteral("delete"); + }), + "Delete is still on the main toolbar as well as the message bar"); + + // And the trash, which is the whole point. + QVERIFY2(selectById(QStringLiteral("trashed1@example.org")), + "the trashed message was not found"); + QVERIFY2(barHolds(QStringLiteral("restore")), + "Restore is missing from the bar on a trashed message"); + QVERIFY2(barHolds(QStringLiteral("purge")), + "Delete permanently is missing from the bar in the trash"); + QVERIFY2(barHolds(QStringLiteral("empty_trash")), + "Empty trash is missing from the bar in the trash"); + QVERIFY2(!barHolds(QStringLiteral("reply")), + "Reply is still offered on a trashed message, which is the " + "complaint item 185 exists to fix"); + QVERIFY2(!barHolds(QStringLiteral("forward")), + "Forward is still offered on a trashed message"); + QVERIFY2(barHolds(QStringLiteral("toggle_html")), + "the view controls were lost when the bar swapped to the trash"); + + // And back, because a one-way swap is the plausible defect: the bar is + // refilled on every selection change, so returning to ordinary mail has to + // restore the reply pair rather than leaving the purges behind on mail + // they must not destroy. + QVERIFY2(selectById(QStringLiteral("compose1@example.org")), + "the inbox message was not found on the way back"); + QVERIFY2(barHolds(QStringLiteral("reply")), + "Reply did not come back after leaving the trash"); + QVERIFY2(!barHolds(QStringLiteral("purge")), + "Delete permanently stayed on the bar after leaving the trash"); +} + void TestMainWindow::theMessageBarOffersEditOnADraft() { // Item 157, and the half item 153 did not close. A draft was editable by |
