diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-28 12:33:52 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-28 12:33:52 +0200 |
| commit | 978c9b51935f8597ce307166bfba29368bd4fc23 (patch) | |
| tree | d07ee82615113e31f788c731ffdfffa626e7ec21 /tests/test_notmuchworker.cpp | |
| parent | cc551a4ed348e663601f2fb8e06b59c07a09ead4 (diff) | |
| download | qtmaildir-978c9b51935f8597ce307166bfba29368bd4fc23.tar.gz qtmaildir-978c9b51935f8597ce307166bfba29368bd4fc23.zip | |
feat: scope an action to the row it was invoked on
The five *_thread actions and their submenu are gone: the row's identity is
what decides the scope, so a second set of actions was a second answer to a
settled question. mark_thread_unread went with them, being the sixth entry in
the same submenu. tagSelected() loses its TagScope parameter, and
everySelectedRowHasTag() its own, so the direction and the write ask the same
question of the same object. ThreadListModel::scopeFor() and messageScopeFor()
are deleted; scopeForSelection() is the one resolver.
Labels name the scope. Archive, Delete, Restore, Spam, Important and the
unread toggle all say "thread" on a conversation row, and Delete, Restore and
Archive are ABSENT on a reply: a single reply cannot be removed from a
conversation.
Compose follows the same rule. Forward, Save, Reply-all and Reply without
quoting disappear on a conversation row, which shows no message to act on, and
Reply becomes "Reply to this thread": reply-all, quoting nothing, threaded off
the conversation's NEWEST message so the answer lands at its end rather than
forking the discussion at its opening post. That id is not in the model, since
an unexpanded conversation holds no nodes for its replies, so it comes from
resolveThreadMessages(); resolveQuery() states its newest-first sort rather
than inheriting notmuch's default.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012iDeN6C7y97nHYPvP6ST4L
Diffstat (limited to 'tests/test_notmuchworker.cpp')
| -rw-r--r-- | tests/test_notmuchworker.cpp | 32 |
1 files changed, 32 insertions, 0 deletions
diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp index 2e960be..45dc9a0 100644 --- a/tests/test_notmuchworker.cpp +++ b/tests/test_notmuchworker.cpp @@ -101,6 +101,7 @@ private slots: void purgeMessagesReportsWhatItDestroyed(); void purgeMessagesLeavesOtherMessagesAlone(); void purgeMessagesDoesNotClaimAnIdItCouldNotDelete(); + void resolveThreadMessagesReportsTheNewestFirst(); void resolveQueryMessagesRefusesAnEmptyQuery(); void moveMessagesKeepsTheMaildirFlags(); void moveMessagesRecoversWhenASyncRenamedTheFile(); @@ -1530,6 +1531,37 @@ void TestNotmuchWorker::purgeMessagesDoesNotClaimAnIdItCouldNotDelete() "claimed to have destroyed a message whose file was already gone"); } +void TestNotmuchWorker::resolveThreadMessagesReportsTheNewestFirst() +{ + // Item 177's "Reply to this thread" reads the FIRST id this reports as the + // conversation's newest message, so its In-Reply-To and References land + // the answer at the end of the conversation and its recipients are the + // ones currently in it. Answering the thread's opening post instead would + // fork the discussion, silently, in mail that has already gone out. + // + // Asserted here rather than in the UI because the ORDER is the contract. + // notmuch's own default already is newest-first, which is exactly why this + // needs a test: the code now states the sort explicitly, and nothing else + // would notice if that statement were dropped and the default later moved. + NotmuchWorker worker(m_fixture.configPath()); + QSignalSpy resolved(&worker, &NotmuchWorker::threadMessagesResolved); + + // Thread A, whose two messages are a day apart. A one-message thread + // answers identically whatever the sort and would assert nothing. + const QString threadId = threadIdOf(QStringLiteral("Release notes")); + QVERIFY(!threadId.isEmpty()); + + worker.resolveThreadMessages({ threadId }, QStringLiteral("reply_thread")); + + QCOMPARE(resolved.size(), 1); + const QStringList ids = resolved.first().at(0).toStringList(); + QCOMPARE(ids.size(), 2); + QCOMPARE(ids.first(), QStringLiteral("a2@example.org")); + QVERIFY2(ids.last() == QStringLiteral("a1@example.org"), + qPrintable(QStringLiteral("wrong order: %1") + .arg(ids.join(QLatin1Char(','))))); +} + void TestNotmuchWorker::resolveQueryMessagesRefusesAnEmptyQuery() { // An EMPTY query means "match everything" to notmuch, and this walk is |
