diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-31 12:08:42 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-31 12:08:42 +0100 |
| commit | b1f6660b9f1471659ead81cf527d13d93dd57146 (patch) | |
| tree | 34ec1d186839c12a7288ad99c1f22f25b43b9d6f /tests | |
| parent | 3474936404df92ac08534c010aed94f97e6fca15 (diff) | |
| download | qtmaildir-b1f6660b9f1471659ead81cf527d13d93dd57146.tar.gz qtmaildir-b1f6660b9f1471659ead81cf527d13d93dd57146.zip | |
feat: apply a saved rule to existing mail, confirmed and undoable
The dialog's Apply reaches MainWindow, which counts the messages the rule
would actually move, confirms with that count, then writes through the
worker. The rule is written one tag at a time inside one undo macro,
because the worker's effective set is per message: a single +a -b write
would make the undo strip a tag from messages that already had it. Each
undo entry is filled from its own confirmation, so it reverts exactly
what moved. Refused while a sync holds the write lock, checked again
after the confirmation closes.
Item 212.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_mainwindow.cpp | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 71795fa..4887647 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -29,6 +29,7 @@ #include <QKeyEvent> #include <QLabel> #include <QLineEdit> +#include <QMessageBox> #include <QMenuBar> #include <QMenu> #include <QPushButton> @@ -79,6 +80,7 @@ #include "tagstrip.h" #include "threaddashboard.h" #include "threadlistmodel.h" +#include "tagrules.h" #include "threadlistview.h" #include "notmuchfixture.h" @@ -296,6 +298,7 @@ private slots: void aMalformedAccountIsReportedWithoutBlockingTheConstructor(); void aWorkerBackedWindowReturnsRealThreads(); void aWriteSaysItIsApplyingUntilTheWorkerHasRunIt(); + void applyingARuleConfirmsWithTheCountAndUndoesExactly(); void aPurgeTakesTheRowsOutOfTheViewWithoutARefresh(); void theDashboardFollowsAWriteToTheConversationItShows(); void anEditHeldByASyncSaysSoInsteadOfClaimingItLanded(); @@ -9929,6 +9932,94 @@ void TestMainWindow::aWriteSaysItIsApplyingUntilTheWorkerHasRunIt() qPrintable(status->text()), 15000); } +void TestMainWindow::applyingARuleConfirmsWithTheCountAndUndoesExactly() +{ + // Items 212 and 176. Two matching messages in OPPOSITE states: `had` + // already carries promo, `lacked` does not. The confirmation must count + // what the whole rule moves, and undo must leave `had` tagged. + WorkerBackedWindow backed; + // Friday, verified with `date -d 2026-08-14 +%A`. + const QString date = QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"); + QVERIFY(backed.fixture().addMessage( + QStringLiteral("inbox"), QStringLiteral("had@example.org"), + QStringLiteral("One"), QStringLiteral("deals@example.net"), date, + QStringLiteral("Body."))); + QVERIFY(backed.fixture().addMessage( + QStringLiteral("inbox"), QStringLiteral("lacked@example.org"), + QStringLiteral("Two"), QStringLiteral("deals@example.net"), date, + QStringLiteral("Body."))); + QVERIFY2(backed.build(), qPrintable(backed.error())); + + const auto notmuch = [&backed](const QStringList &args) { + QProcess p; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("NOTMUCH_CONFIG"), + backed.fixture().configPath()); + p.setProcessEnvironment(env); + p.start(QStringLiteral("notmuch"), args); + p.waitForFinished(); + return QString::fromUtf8(p.readAllStandardOutput()).trimmed(); + }; + notmuch({ QStringLiteral("tag"), QStringLiteral("+promo"), QStringLiteral("--"), + QStringLiteral("id:had@example.org") }); + // Guards: the fixture state the assertions below depend on. + QCOMPARE(notmuch({ QStringLiteral("count"), QStringLiteral("tag:inbox") }), + QStringLiteral("2")); + QCOMPARE(notmuch({ QStringLiteral("count"), + QStringLiteral("tag:promo and id:had@example.org") }), + QStringLiteral("1")); + + MainWindow window(backed.config()); + auto *undo = window.findChild<QAction *>(QStringLiteral("undo")); + QVERIFY(undo); + + TagRule rule; + rule.id = QStringLiteral("promo"); + rule.query = QStringLiteral("from:deals@example.net"); + rule.add = { QStringLiteral("promo") }; + rule.remove = { QStringLiteral("inbox") }; + + // Answer the confirmation when it appears, recording what it said. + QString asked; + QTimer poll; + poll.setInterval(20); + QObject::connect(&poll, &QTimer::timeout, [&]() { + auto *box = qobject_cast<QMessageBox *>(QApplication::activeModalWidget()); + if (!box) + return; + asked = box->text(); + poll.stop(); + box->button(QMessageBox::Yes)->click(); + }); + poll.start(); + + window.applyRuleToExistingMail(rule); + QTRY_VERIFY_WITH_TIMEOUT(!asked.isEmpty(), 15000); + // The whole rule's count: both messages still carry inbox, so both + // would move, even though `had` already has promo. + QVERIFY2(asked.contains(QStringLiteral("2")), qPrintable(asked)); + + QTRY_COMPARE_WITH_TIMEOUT( + notmuch({ QStringLiteral("count"), QStringLiteral("tag:inbox") }), + QStringLiteral("0"), 15000); + QTRY_COMPARE_WITH_TIMEOUT( + notmuch({ QStringLiteral("count"), QStringLiteral("tag:promo") }), + QStringLiteral("2"), 15000); + + undo->trigger(); + QTRY_COMPARE_WITH_TIMEOUT( + notmuch({ QStringLiteral("count"), QStringLiteral("tag:inbox") }), + QStringLiteral("2"), 15000); + QTRY_COMPARE_WITH_TIMEOUT( + notmuch({ QStringLiteral("count"), + QStringLiteral("tag:promo and id:lacked@example.org") }), + QStringLiteral("0"), 15000); + // `had` carried promo before the backfill, so undo must not take it off. + QCOMPARE(notmuch({ QStringLiteral("count"), + QStringLiteral("tag:promo and id:had@example.org") }), + QStringLiteral("1")); +} + /// Item 181, from the user: "the thread dashboard doesn't update live with the /// modifications applied to the list pane. If I mark the thread as read, the /// dash still reports N unread". |
