aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_mainwindow.cpp51
-rw-r--r--tests/test_notmuchworker.cpp41
2 files changed, 84 insertions, 8 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 17c6f8c..5eed336 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -301,6 +301,7 @@ private slots:
void applyingARuleConfirmsWithTheCountAndUndoesExactly();
void applyingARuleWhoseQueryNamesItsOwnTagWritesAllOfIt();
void aBackfillLeavesAnArmedUserCommandAlone();
+ void twoRulesAppliedBackToBackEachGetTheirUndo();
void aPurgeTakesTheRowsOutOfTheViewWithoutARefresh();
void theDashboardFollowsAWriteToTheConversationItShows();
void anEditHeldByASyncSaysSoInsteadOfClaimingItLanded();
@@ -10161,6 +10162,56 @@ void TestMainWindow::aBackfillLeavesAnArmedUserCommandAlone()
QStringList{ QStringLiteral("armed@example.org") });
}
+void TestMainWindow::twoRulesAppliedBackToBackEachGetTheirUndo()
+{
+ // Item 212, from review. A second Apply while the first is in flight must
+ // not make the first's answer look stale: its writes landed, so it needs
+ // its undo entry and its pending count like any other write.
+ WorkerBackedWindow backed;
+ QVERIFY(backed.fixture().addMessage(
+ QStringLiteral("inbox"), QStringLiteral("one@example.org"),
+ QStringLiteral("One"), QStringLiteral("deals@example.net"),
+ QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"),
+ QStringLiteral("Body.")));
+ QVERIFY2(backed.build(), qPrintable(backed.error()));
+ const QString cfg = backed.fixture().configPath();
+
+ MainWindow window(backed.config());
+ QCOMPARE(window.pendingEditCount(), 0);
+
+ TagRule first;
+ first.id = QStringLiteral("first");
+ first.query = QStringLiteral("from:deals@example.net");
+ first.add = { QStringLiteral("ruleone") };
+ TagRule second = first;
+ second.id = QStringLiteral("second");
+ second.add = { QStringLiteral("ruletwo") };
+
+ // Answer every box, however they nest.
+ QStringList asked;
+ QTimer poll;
+ poll.setInterval(20);
+ QObject::connect(&poll, &QTimer::timeout, [&asked]() {
+ auto *box = qobject_cast<QMessageBox *>(QApplication::activeModalWidget());
+ if (!box)
+ return;
+ asked.append(box->text());
+ box->button(QMessageBox::Yes)->click();
+ });
+ poll.start();
+
+ window.applyRuleToExistingMail(first);
+ window.applyRuleToExistingMail(second);
+ QTRY_COMPARE_WITH_TIMEOUT(asked.size(), 2, 15000);
+ QTRY_COMPARE_WITH_TIMEOUT(window.undoDepthForTesting(), 2, 15000);
+
+ QCOMPARE(fixtureNotmuch(cfg, { QStringLiteral("count"),
+ QStringLiteral("tag:ruleone and tag:ruletwo") }),
+ QStringLiteral("1"));
+ // One message, two (message, tag) edits outstanding.
+ QCOMPARE(window.pendingEditCount(), 2);
+}
+
/// 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".
diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp
index c4da146..68734ca 100644
--- a/tests/test_notmuchworker.cpp
+++ b/tests/test_notmuchworker.cpp
@@ -63,6 +63,7 @@ private slots:
void applyRuleResolvesItsQueryOnceForEveryTag();
void applyRuleThatChangesNothingStillAnswers();
void applyRuleAnswersAcrossAQueuedConnection();
+ void applyRuleCarriesItsFailureInTheReply();
void applyTagsToThreadsSpansMultipleThreads();
void applyTagsToThreadsWithNoThreadsDoesNothing();
@@ -1318,11 +1319,12 @@ void TestNotmuchWorker::applyRuleWritesEachTagOnlyWhereItMoves()
worker.applyRule(QStringLiteral("thread:%1").arg(threadId),
{ QStringLiteral("ruleadd") },
{ QStringLiteral("ruledrop") },
- QStringLiteral("Apply rule"), 7);
+ QStringLiteral("Apply rule"));
QCOMPARE(applied.size(), 0);
QCOMPARE(ruled.size(), 1);
- QCOMPARE(ruled.first().at(0).value<quint64>(), quint64(7));
+ QCOMPARE(ruled.first().at(0).toString(), QStringLiteral("Apply rule"));
+ QVERIFY(ruled.first().at(2).toString().isEmpty());
const auto changes = ruled.first().at(1).value<QVector<TagChange>>();
QCOMPARE(changes.size(), 2);
QCOMPARE(changes.at(0).added, QStringList{ QStringLiteral("ruleadd") });
@@ -1364,7 +1366,7 @@ void TestNotmuchWorker::applyRuleResolvesItsQueryOnceForEveryTag()
worker.applyRule(
QStringLiteral("thread:%1 and tag:backfill-src").arg(threadId), {},
{ QStringLiteral("backfill-src"), QStringLiteral("backfill-other") },
- QStringLiteral("Apply rule"), 1);
+ QStringLiteral("Apply rule"));
QCOMPARE(ruled.size(), 1);
const auto changes = ruled.first().at(1).value<QVector<TagChange>>();
@@ -1390,11 +1392,11 @@ void TestNotmuchWorker::applyRuleThatChangesNothingStillAnswers()
QSignalSpy ruled(&worker, &NotmuchWorker::ruleApplied);
worker.applyRule(QStringLiteral("thread:%1").arg(threadId), {},
{ QStringLiteral("nobody-has-this") },
- QStringLiteral("Apply rule"), 3);
+ QStringLiteral("Apply rule"));
QCOMPARE(applied.size(), 0);
QCOMPARE(ruled.size(), 1);
- QCOMPARE(ruled.first().at(0).value<quint64>(), quint64(3));
+ QVERIFY(ruled.first().at(2).toString().isEmpty());
const auto changes = ruled.first().at(1).value<QVector<TagChange>>();
for (const TagChange &change : changes)
QVERIFY(change.messageIds.isEmpty());
@@ -1417,7 +1419,8 @@ void TestNotmuchWorker::applyRuleAnswersAcrossAQueuedConnection()
int answers = 0;
QVector<TagChange> received;
connect(&worker, &NotmuchWorker::ruleApplied, &receiver,
- [&](quint64, const QVector<TagChange> &changes) {
+ [&](const QString &, const QVector<TagChange> &changes,
+ const QString &) {
++answers;
received = changes;
});
@@ -1426,8 +1429,7 @@ void TestNotmuchWorker::applyRuleAnswersAcrossAQueuedConnection()
Q_ARG(QString, QStringLiteral("thread:%1").arg(threadId)),
Q_ARG(QStringList, QStringList{ QStringLiteral("crossed") }),
Q_ARG(QStringList, QStringList{}),
- Q_ARG(QString, QStringLiteral("Apply rule")),
- Q_ARG(quint64, 9)));
+ Q_ARG(QString, QStringLiteral("Apply rule"))));
QTRY_COMPARE_WITH_TIMEOUT(answers, 1, 5000);
QCOMPARE(received.size(), 1);
@@ -1442,6 +1444,29 @@ void TestNotmuchWorker::applyRuleAnswersAcrossAQueuedConnection()
QStringLiteral("restore") });
}
+void TestNotmuchWorker::applyRuleCarriesItsFailureInTheReply()
+{
+ // A backfill's failure travels in its own reply. Through errorOccurred()
+ // the window would revert whatever unrelated user write was in flight,
+ // and its message would be overwritten by "nothing to change".
+ QTemporaryDir dir;
+ QVERIFY(dir.isValid());
+ NotmuchWorker worker(dir.filePath(QStringLiteral("no-such-config")));
+ QSignalSpy errors(&worker, &NotmuchWorker::errorOccurred);
+ QSignalSpy ruled(&worker, &NotmuchWorker::ruleApplied);
+
+ worker.applyRule(QStringLiteral("tag:inbox"), { QStringLiteral("x") }, {},
+ QStringLiteral("Apply rule \"x\""));
+
+ QCOMPARE(errors.size(), 0);
+ QCOMPARE(ruled.size(), 1);
+ QCOMPARE(ruled.first().at(0).toString(), QStringLiteral("Apply rule \"x\""));
+ QVERIFY(!ruled.first().at(2).toString().isEmpty());
+ const auto changes = ruled.first().at(1).value<QVector<TagChange>>();
+ for (const TagChange &change : changes)
+ QVERIFY(change.messageIds.isEmpty());
+}
+
void TestNotmuchWorker::applyTagsToThreadsSpansMultipleThreads()
{
const QString threadA = threadIdOf(QStringLiteral("Release notes"));