aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-31 12:08:42 +0100
committerDanilo M. <danix@danix.xyz>2026-10-31 12:08:42 +0100
commitb1f6660b9f1471659ead81cf527d13d93dd57146 (patch)
tree34ec1d186839c12a7288ad99c1f22f25b43b9d6f /tests/test_mainwindow.cpp
parent3474936404df92ac08534c010aed94f97e6fca15 (diff)
downloadqtmaildir-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/test_mainwindow.cpp')
-rw-r--r--tests/test_mainwindow.cpp91
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".