From c4a38a5871250b9c952f3eb633ab4cf2ed3d717a Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 29 Sep 2026 17:58:07 +0200 Subject: fix: accept a bracketed Message-ID in the message selector A Message-ID is written in every header a caller copies it from, and notmuch stores it without the angle brackets, so `--message ''`, the form the README itself shows, matched nothing and was reported as a miss. applySelectors() now trims the value and strips one enclosing pair of brackets before anything else sees it, so the worker's resolve and the recovery's selection target both use the bare id. Stripping for the resolve alone would open the right thread and then fail to select the message inside it, which is why the test targets a reply rather than a thread root. No refusal of a slashed id was added: notmuch 0.39 reads `/.../` as a regex for mid: but not for id:, measured as 0 matches for id:"/.*/" on an index where mid:"/.*/" matches every message. Co-Authored-By: Claude Opus 5.5 --- tests/test_mainwindow.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'tests/test_mainwindow.cpp') diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp index 66cc9b5..b9bd890 100644 --- a/tests/test_mainwindow.cpp +++ b/tests/test_mainwindow.cpp @@ -319,6 +319,7 @@ private slots: void aMessageSelectorOpensItsThread(); void aThreadSelectorThatIsNotHexIsRefused(); void anEmptySelectorSetChangesNothing(); + void aBracketedMessageSelectorOpensThatMessage(); void everyBuiltinFilterButtonCarriesAnIconAndItsText(); void theDraftsButtonIsAbsentWithoutADraftsFolder(); void aQueryInTheMenuCanActuallyBeRun(); @@ -18066,4 +18067,50 @@ void TestMainWindow::anEmptySelectorSetChangesNothing() QCOMPARE(queryEdit->text(), QStringLiteral("tag:flagged")); } +void TestMainWindow::aBracketedMessageSelectorOpensThatMessage() +{ + // A Message-ID is written in every header it appears in, so + // that is the form a caller copies. notmuch stores it WITHOUT the angle + // brackets, so the bracketed form matched nothing and the launch reported + // a miss for a message that was right there. + // + // The target is a REPLY, not the root. The recovery lands on the root + // whenever it cannot find the message it was given, so a root target + // would pass with the brackets stripped for the resolve and left on for + // the selection. + WorkerBackedWindow backed; + QVERIFY(backed.fixture().addMessage( + QStringLiteral("inbox"), QStringLiteral("one@example.org"), + QStringLiteral("First subject"), QStringLiteral("a@example.org"), + // Friday, verified with `date -d 2026-08-14 +%A`. + QStringLiteral("Fri, 14 Aug 2026 10:00:00 +0200"), + QStringLiteral("Body one."))); + QVERIFY(backed.fixture().addMessage( + QStringLiteral("inbox"), QStringLiteral("two@example.org"), + QStringLiteral("Re: First subject"), QStringLiteral("b@example.org"), + // Saturday, verified with `date -d 2026-08-15 +%A`. + QStringLiteral("Sat, 15 Aug 2026 10:00:00 +0200"), + QStringLiteral("Body two."), true, + QStringLiteral("one@example.org"))); + QVERIFY2(backed.build(), qPrintable(backed.error())); + + MainWindow window(backed.config()); + auto *model = window.findChild(); + QVERIFY(model); + auto *view = window.findChild(); + QVERIFY(view); + + LaunchSelectors selectors; + // Surrounding whitespace too, which a shell quote or a copy picks up. + selectors.messageId = QStringLiteral(" "); + window.applySelectors(selectors); + + QTRY_VERIFY_WITH_TIMEOUT(view->currentIndex().isValid() + && model->isMessageRow(view->currentIndex()) + && model->messageAt(view->currentIndex()) + .messageId + == QStringLiteral("two@example.org"), + 15000); +} + #include "test_mainwindow.moc" -- cgit v1.2.3