diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_mainwindow.cpp | 37 | ||||
| -rw-r--r-- | tests/test_threadlistmodel.cpp | 49 |
2 files changed, 86 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 2b7008d..71795fa 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -295,6 +295,7 @@ private slots: void narrowingAnEmptyQueryBarIsAPlainSearch(); void aMalformedAccountIsReportedWithoutBlockingTheConstructor(); void aWorkerBackedWindowReturnsRealThreads(); + void aWriteSaysItIsApplyingUntilTheWorkerHasRunIt(); void aPurgeTakesTheRowsOutOfTheViewWithoutARefresh(); void theDashboardFollowsAWriteToTheConversationItShows(); void anEditHeldByASyncSaysSoInsteadOfClaimingItLanded(); @@ -9892,6 +9893,42 @@ void TestMainWindow::aWorkerBackedWindowReturnsRealThreads() QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(QModelIndex()) == 1, 15000); } +void TestMainWindow::aWriteSaysItIsApplyingUntilTheWorkerHasRunIt() +{ + // Item 211, from the user: after archiving ten thousand rows, "how do I + // know if it worked? The UI doesn't give me a feedback". The announcement + // used to vanish on a timer whether or not the write had landed. + WorkerBackedWindow backed; + QVERIFY(backed.fixture().addMessage( + QStringLiteral("inbox"), QStringLiteral("root@example.org"), + QStringLiteral("A subject"), QStringLiteral("sender@example.org"), + // Friday, verified with `date -d 2026-08-14 +%A`. + QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"), + QStringLiteral("Body text."))); + QVERIFY2(backed.build(), qPrintable(backed.error())); + + MainWindow window(backed.config()); + auto *queryEdit = window.findChild<QLineEdit *>(QStringLiteral("queryEdit")); + auto *model = window.findChild<ThreadListModel *>(); + auto *view = window.findChild<ThreadListView *>(); + auto *status = window.findChild<QLabel *>(QStringLiteral("statusMessage")); + QVERIFY(queryEdit && model && view && status); + + queryEdit->setText(QStringLiteral("tag:inbox")); + queryEdit->returnPressed(); + QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(QModelIndex()) == 1, 15000); + view->setCurrentIndex(model->index(0, 0, QModelIndex())); + + window.findChild<QAction *>(QStringLiteral("archive"))->trigger(); + + // Synchronous: the worker's reply is posted to this thread and cannot be + // delivered before the event loop runs, so this cannot race. + QVERIFY2(status->text().contains(QStringLiteral("applying")), + qPrintable(status->text())); + QTRY_VERIFY2_WITH_TIMEOUT(status->text().contains(QStringLiteral("done")), + qPrintable(status->text()), 15000); +} + /// 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_threadlistmodel.cpp b/tests/test_threadlistmodel.cpp index 28d3a8b..76ac0e8 100644 --- a/tests/test_threadlistmodel.cpp +++ b/tests/test_threadlistmodel.cpp @@ -109,6 +109,7 @@ private slots: void aConversationListsItsFirstMessageAsAChild(); void aMessageRowDoesNotListItselfBeneathItself(); void aMixedSelectionCarriesBothScopes(); + void idLookupsFollowEveryChangeToTheRows(); }; static ThreadSummary makeThread(const QString &id, const QString &subject) @@ -2355,5 +2356,53 @@ void TestThreadListModel::aMixedSelectionCarriesBothScopes() QCOMPARE(scope.threadIds, QStringList{ QStringLiteral("t2") }); } +void TestThreadListModel::idLookupsFollowEveryChangeToTheRows() +{ + // Item 211. Lookups by id go through a lazily rebuilt hash rather than a + // walk, so every way the rows can change has to invalidate it. Each step + // asks a question whose answer moved, so a stale index answers wrongly. + const QString a = QStringLiteral("a@example.org"); + const QString c = QStringLiteral("c@example.org"); + const QString d = QStringLiteral("d@example.org"); + const QString e = QStringLiteral("e@example.org"); + const QString t1 = QStringLiteral("t1"); + const QString t2 = QStringLiteral("t2"); + + ThreadListModel model; + ThreadSummary one = makeThread(t1, QStringLiteral("one")); + one.firstMessageId = a; + ThreadSummary two = makeThread(t2, QStringLiteral("two")); + two.firstMessageId = c; + model.appendBatch({ one, two }); + QCOMPARE(model.threadIdForMessage(a), t1); // builds the index + + // Expansion adds ids the index has never seen. + MessageNode first = makeNode(c, 0); + first.threadId = t2; + MessageNode reply = makeNode(d, 1); + reply.threadId = t2; + model.setThreadMessages(t2, { first, reply }); + QCOMPARE(model.threadIdForMessage(d), t2); + + // A removal renumbers: t2 moves from row 1 to row 0. + model.applyTagChange(t1, {}, { QStringLiteral("inbox") }); + model.removeThreadsWithoutTag({ t1 }, QStringLiteral("inbox")); + QVERIFY(!model.hasThread(t1)); + QVERIFY(model.threadIdForMessage(a).isEmpty()); + QCOMPARE(model.threadCountFor(t2), 2); + model.applyMessageTagChange(d, { QStringLiteral("x") }, {}); + QVERIFY(model.messageById(d).tags.contains(QStringLiteral("x"))); + + // reconcile() replaces a summary IN PLACE, with no structural signal. + two.firstMessageId = e; + two.tags = QStringList{ QStringLiteral("inbox") }; + model.reconcile({ two }); + QCOMPARE(model.threadIdForMessage(e), t2); + + model.clear(); + QVERIFY(!model.hasThread(t2)); + QVERIFY(model.messageById(d).messageId.isEmpty()); +} + QTEST_MAIN(TestThreadListModel) #include "test_threadlistmodel.moc" |
