diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-14 19:16:16 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-14 19:16:16 +0200 |
| commit | 4a4f82f7709ab6ee5a84ac0f3b191470b6424c36 (patch) | |
| tree | 4c839102a1726d36060935ddd4b04b109a27305c /tests/test_mainwindow.cpp | |
| parent | f897153a1196f23fe0d82dc703d98df1363bf3fc (diff) | |
| download | qtmaildir-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.cpp | 65 |
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" |
