diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_mainwindow.cpp | 51 | ||||
| -rw-r--r-- | tests/test_notmuchworker.cpp | 41 |
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")); |
