aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_mainwindow.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-07 10:12:10 +0200
committerDanilo M. <danix@danix.xyz>2026-09-07 10:12:10 +0200
commita00d35452fcef8c8bc723ba7045fcae38fe8f315 (patch)
tree510d0bb2d9265367648f38bbf43186f471379716 /tests/test_mainwindow.cpp
parent6326030b7179580b934ba852b0fd98577bfb464a (diff)
downloadqtmaildir-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.cpp82
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;