diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-07 10:12:10 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-07 10:12:10 +0200 |
| commit | a00d35452fcef8c8bc723ba7045fcae38fe8f315 (patch) | |
| tree | 510d0bb2d9265367648f38bbf43186f471379716 /tests/test_mainwindow.cpp | |
| parent | 6326030b7179580b934ba852b0fd98577bfb464a (diff) | |
| download | qtmaildir-a00d35452fcef8c8bc723ba7045fcae38fe8f315.tar.gz qtmaildir-a00d35452fcef8c8bc723ba7045fcae38fe8f315.zip | |
fix: refresh the current query when the index changes
Item 192's second half, answered by the user: indexing is not repainting.
The sent copy became findable the moment it was sent and a Sent view already
on screen still did not show it, because nothing re-ran the query. The model
cannot insert the row optimistically either, since item 170's constraint
applies: the query never returned that thread.
NotmuchWorker::indexChanged() is emitted at the end of both indexDraftFile()
and removeIndexedFile(), the only two entry points that change what a path
query would return without any query having run. MainWindow connects it to
refreshCurrentQuery(), which covers all three gestures a path view can miss:
a sent copy indexed, a draft saved, a draft's entry dropped on send. Wiring
only the indexing half would have left a ghost draft row visible in a Drafts
view after a send.
The signal carries nothing, so it cannot invite an optimistic insert. It is
emitted after the database closes, so a refresh reaching notmuch on the next
turn of the event loop cannot race the write handle. refreshCurrentQuery()
rather than runCurrentQuery(): a send must not clear the selection, the
expansions or the undo stack of the window behind the composer.
aSentMessageAppearsInASentViewAlreadyOnScreen drives a real send through a
worker-backed window, asserts the Sent view is empty first, and asserts the
row arrives with no second returnPressed() and no sync. Mutation-checked by
disabling the connection.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWeBw3UqxpBktSc1AkZ6ir
Diffstat (limited to 'tests/test_mainwindow.cpp')
| -rw-r--r-- | tests/test_mainwindow.cpp | 82 |
1 files changed, 82 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index f97b9bd..1c54ebb 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -545,6 +545,7 @@ private slots: void aNewMessageSeedsTheHtmlToggleFromConfig(); void disablingInputsCoversEveryFieldAndTheToolbar(); void aFiledSentCopyIsAnnouncedForIndexing(); + void aSentMessageAppearsInASentViewAlreadyOnScreen(); void aFailedSendCanBeRetriedWithoutFilingTheWrongCopy(); void anUnchangedMessageIsNotWrittenAgain(); void closingInsideTheDebounceStillSavesTheDraft(); @@ -15136,6 +15137,87 @@ void TestMainWindow::aFiledSentCopyIsAnnouncedForIndexing() .arg(announced, sentCur))); } +/// Item 192's second half, from the user: indexing is not repainting. +/// +/// The sent copy became findable the instant it was indexed and the Sent view +/// on screen still did not show it, because nothing re-ran the query. A +/// `path:` view is built once and holds whatever the index said then; the +/// model cannot insert the row optimistically either, since item 170's +/// constraint applies, the query never returned that thread. +/// +/// So the worker reports indexChanged() and the window refreshes. Asserted +/// end to end through a real send rather than by emitting the signal: what is +/// unproven is that the send path reaches the refresh, and a test that emits +/// the signal itself proves only that the connection compiles. +/// +/// refreshCurrentQuery(), which is why the query bar is never touched again +/// after the first run: a test that re-ran the query by hand would pass +/// against the defect. +void TestMainWindow::aSentMessageAppearsInASentViewAlreadyOnScreen() +{ + WorkerBackedWindow backed; + // One message, in a folder the Sent view does NOT cover. It exists so the + // fixture indexes at all (notmuch refuses an empty tree) and so a Sent + // view showing zero rows is a real answer rather than an empty database. + QVERIFY(backed.fixture().addMessage( + QStringLiteral("work/inbox"), QStringLiteral("sv0@example.org"), + QStringLiteral("Unrelated"), QStringLiteral("other@example.org"), + // Friday, verified with `date -d 2026-08-14 +%A`. Qt::RFC2822Date + // validates the weekday against the date. + QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"), + QStringLiteral("Body text."), false)); + QVERIFY2(backed.buildWithAccounts( + { { QStringLiteral("work"), QStringLiteral("work"), + QString(), QStringLiteral("/bin/true"), + QStringLiteral("you@example.org"), QString(), + QStringLiteral("Sent") } }, + QStringLiteral("send_delay_ms=0")), + qPrintable(backed.error())); + + MainWindow window(backed.config()); + auto *model = window.findChild<ThreadListModel *>(); + auto *queryEdit = + window.findChild<QLineEdit *>(QStringLiteral("queryEdit")); + QVERIFY(model && queryEdit); + + // The Sent view as the application builds it: a path query over the + // account's sent folder. Run BEFORE the send, which is the whole point, + // and never run again by this test. + queryEdit->setText(QStringLiteral("path:\"work/Sent/**\"")); + queryEdit->returnPressed(); + + // The mail root is what the composer needs and it arrives asynchronously, + // so this waits on it as well: openComposer() refuses without one and the + // send would never happen, which reads exactly like the defect. + QTRY_VERIFY_WITH_TIMEOUT(!window.mailRootForTesting().isEmpty(), 15000); + QApplication::processEvents(); + + // The guard. An empty view here is the state the refresh must change, and + // a test that started from a non-empty one could not tell a refresh from + // the row that was already there. + QCOMPARE(model->rowCount(QModelIndex()), 0); + + auto *compose = window.findChild<QAction *>(QStringLiteral("compose")); + QVERIFY(compose); + compose->trigger(); + QTRY_VERIFY_WITH_TIMEOUT(window.openComposerCount() == 1, 15000); + ComposeWindow *composer = window.openComposersForTest().value(0); + QVERIFY(composer); + + auto *to = composer->findChild<QLineEdit *>(QStringLiteral("to")); + auto *body = composer->findChild<QPlainTextEdit *>(QStringLiteral("body")); + auto *sendAction = + composer->findChild<QAction *>(QStringLiteral("compose_send")); + QVERIFY(to && body && sendAction); + to->setText(QStringLiteral("someone@example.org")); + body->setPlainText(QStringLiteral("Text.")); + sendAction->trigger(); + + // No further gesture: no second returnPressed(), no sync. The row must + // arrive because the index changed and the window noticed. + QTRY_VERIFY_WITH_TIMEOUT(model->rowCount(QModelIndex()) == 1, 15000); +} + void TestMainWindow::disablingInputsCoversEveryFieldAndTheToolbar() { ComposeFixture fixture; |
