aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-14 19:16:16 +0200
committerDanilo M. <danix@danix.xyz>2026-08-14 19:16:16 +0200
commit4a4f82f7709ab6ee5a84ac0f3b191470b6424c36 (patch)
tree4c839102a1726d36060935ddd4b04b109a27305c /tests/test_mainwindow.cpp
parentf897153a1196f23fe0d82dc703d98df1363bf3fc (diff)
downloadqtmaildir-4a4f82f7709ab6ee5a84ac0f3b191470b6424c36.tar.gz
qtmaildir-4a4f82f7709ab6ee5a84ac0f3b191470b6424c36.zip
feat(pane): always render one message, never the conversationthread-view-removed
Selecting a thread root used to render the whole conversation, stubs plus the last messages expanded, but only until the thread had been expanded once. After that the identical click rendered a single message. The user reported the inconsistency and asked for the single-message behaviour throughout, and for the conversation view to go. The cause was a timing one, not a race. The root card stands for the thread's first message and onThreadSelected already preferred to load just that, but the model learned the id only when the replies arrived, so a fresh row fell through to a whole-thread render. ThreadSummary now carries firstMessageId from the query itself, so the id is known before any expansion and the fallback is unreachable. It is free: notmuch_thread_get_toplevel_messages reads the index, not the message files, and a walk with it is indistinguishable from one without over a 36,615-thread database. Contrast recipients, which reads every file and stays Sent-only. The Sent view keeps showing what the user sent rather than the thread's opening message, which is often someone else's. There is no matched-messages iterator in libnotmuch, only a count, so that branch walks oldest-first to the first NOTMUCH_MESSAGE_FLAG_MATCH and stops: 0.146s against a 0.143s baseline over 4,515 threads. onThreadLoaded merges into renderMessages, since onMessageLoaded was already delegating to it for the actual painting. It still takes a list because MessageView renders a list; collapsing that is a separate change to a class with its own tests. NotmuchWorker::loadThread is kept and documented as having no UI caller. It is a tested way to read a thread's messages with the match set resolved, used as a helper by the worker's own tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'tests/test_mainwindow.cpp')
-rw-r--r--tests/test_mainwindow.cpp65
1 files changed, 65 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 1ff8956..eb678eb 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -170,6 +170,7 @@ private slots:
void aMalformedAccountIsReportedWithoutBlockingTheConstructor();
void aWorkerBackedWindowReturnsRealThreads();
void selectingAThreadRootShowsItInTheMessagePane();
+ void anUnexpandedRootRendersOneMessageNotTheConversation();
void autoSyncIsNotArmedWhenDisabledOrWithNothingPending();
void autoSyncSkipsWhileABackgroundSyncIsRunning();
void aSuccessfulSyncRefreshesRatherThanRerunningTheQuery();
@@ -6338,4 +6339,68 @@ void TestMainWindow::selectingAThreadRootShowsItInTheMessagePane()
QTRY_VERIFY_WITH_TIMEOUT(!pane->showingPlaceholder(), 15000);
}
+void TestMainWindow::anUnexpandedRootRendersOneMessageNotTheConversation()
+{
+ // Item 66, the half that reproduced. Clicking a thread root that has never
+ // been expanded used to render the whole conversation, because the model
+ // learned the thread's first message only when the replies loaded. The
+ // identical click rendered ONE message afterwards. The user reported the
+ // inconsistency and asked for the single-message behaviour throughout.
+ WorkerBackedWindow backed;
+ QVERIFY(backed.fixture().addMessage(
+ QStringLiteral("inbox"), QStringLiteral("root@example.org"),
+ QStringLiteral("A conversation"), QStringLiteral("sender@example.org"),
+ QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"),
+ QStringLiteral("The first message.")));
+ QVERIFY(backed.fixture().addMessage(
+ QStringLiteral("inbox"), QStringLiteral("reply@example.org"),
+ QStringLiteral("Re: A conversation"),
+ QStringLiteral("other@example.org"),
+ QStringLiteral("Fri, 14 Aug 2026 11:00:00 +0200"),
+ QStringLiteral("The reply."), true,
+ QStringLiteral("root@example.org")));
+ QVERIFY2(backed.build(), qPrintable(backed.error()));
+
+ MainWindow window(backed.config());
+
+ QLineEdit *queryEdit =
+ window.findChild<QLineEdit *>(QStringLiteral("queryEdit"));
+ QVERIFY2(queryEdit, "no query bar: the window was never built");
+ auto *view = window.findChild<ThreadListView *>();
+ QVERIFY2(view, "no thread list view");
+ auto *model = window.findChild<ThreadListModel *>();
+ QVERIFY2(model, "no thread list model");
+ auto *pane = window.findChild<MessageView *>();
+ QVERIFY2(pane, "no message view");
+
+ queryEdit->setText(QStringLiteral("tag:inbox"));
+ queryEdit->returnPressed();
+ QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(QModelIndex()) == 1, 15000);
+
+ const QModelIndex root = model->index(0, 0, QModelIndex());
+ QVERIFY(root.isValid());
+ QVERIFY2(model->hasChildren(root), "the two messages did not thread");
+
+ // NEVER expanded. That is the whole point: this is the state in which the
+ // old code fell back to the conversation render.
+ QVERIFY2(!view->isExpanded(root), "the test expanded the thread itself");
+
+ // The root's message id is known anyway, because the query carries it now.
+ QVERIFY2(!model->data(root, ThreadListModel::MessageIdRole)
+ .toString()
+ .isEmpty(),
+ "an unexpanded root still has no message id: the query is not "
+ "carrying firstMessageId");
+
+ view->setCurrentIndex(root);
+ QTRY_VERIFY_WITH_TIMEOUT(!pane->showingPlaceholder(), 15000);
+
+ // ONE message, not a conversation. headerSearchOffers() is populated only
+ // when the header states a single message's own From/To/Cc; for a thread
+ // the header says "N messages in thread" and carries no such offers, so an
+ // empty list here is exactly the conversation render this replaced.
+ QVERIFY2(!pane->headerSearchOffers().isEmpty(),
+ "the pane rendered a conversation, not a single message");
+}
+
#include "test_mainwindow.moc"