summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-14 12:53:58 +0200
committerDanilo M. <danix@danix.xyz>2026-08-14 12:53:58 +0200
commitf7f868ce00e01e4026be19437242537967875ce5 (patch)
tree77900377b2d355bc6340fbc80cae4682825fa2bf /tests
parent811bea0640dbfd27ca2c47b0288716ca608e5abb (diff)
downloadqtmaildir-f7f868ce00e01e4026be19437242537967875ce5.tar.gz
qtmaildir-f7f868ce00e01e4026be19437242537967875ce5.zip
feat(search): offer the header's fields for searching
The menu lists what is searchable rather than hit-testing which line of a rich-text label was clicked, which breaks as soon as the label wraps. The values are collected by the pass that renders the header, so nothing parses the markup back into structure. From, To and Cc appear only for a single-message thread, sharing the condition with the header's own display: a thread's recipient differs message to message, and the menu must not offer what the header is not stating. The test fixture's Date: header named the wrong weekday, which Qt::RFC2822Date rejects outright, so no date offer would have been produced from it.
Diffstat (limited to 'tests')
-rw-r--r--tests/test_messageview.cpp99
1 files changed, 98 insertions, 1 deletions
diff --git a/tests/test_messageview.cpp b/tests/test_messageview.cpp
index e94fb86..6403308 100644
--- a/tests/test_messageview.cpp
+++ b/tests/test_messageview.cpp
@@ -49,6 +49,9 @@ private slots:
void detailsDialogIsOfferedForEveryThread();
void placeholderRendersAndReportsItself();
void aMessageBodyCannotRunAQuery();
+ void headerOffersSubjectDateAndSenderForOneMessage();
+ void headerOffersNoSenderForARealThread();
+ void headerOffersNothingForAnAbsentField();
private:
QWebEngineView *webViewOf(MessageView *view) const
@@ -372,7 +375,7 @@ static ThreadRenderItem oneMessage()
message.to = QStringLiteral("Recipient <recipient@example.org>");
message.cc = QStringLiteral("Copied <copied@example.org>");
message.subject = QStringLiteral("Quarterly report");
- message.date = QStringLiteral("Mon, 4 Aug 2026 09:00:00 +0200");
+ message.date = QStringLiteral("Tue, 4 Aug 2026 09:00:00 +0200");
message.plainBody = QStringLiteral("body");
ThreadRenderItem item;
@@ -566,5 +569,99 @@ void TestMessageView::aMessageBodyCannotRunAQuery()
QVERIFY(queries.isEmpty());
}
+void TestMessageView::headerOffersSubjectDateAndSenderForOneMessage()
+{
+ MessageView view;
+ view.showThread({ oneMessage() });
+
+ const QList<SearchOffer> offers = view.headerSearchOffers();
+
+ QStringList queries;
+ for (const SearchOffer &offer : offers)
+ queries << offer.query;
+ const QString shown = queries.join(QStringLiteral(" | "));
+
+ QVERIFY2(queries.contains(QStringLiteral("subject:\"Quarterly report\"")),
+ qPrintable(shown));
+ QVERIFY2(queries.contains(
+ QStringLiteral("from:\"Sender <sender@example.org>\"")),
+ qPrintable(shown));
+ QVERIFY2(queries.contains(
+ QStringLiteral("to:\"Recipient <recipient@example.org>\"")),
+ qPrintable(shown));
+ QVERIFY2(queries.contains(
+ QStringLiteral("cc:\"Copied <copied@example.org>\"")),
+ qPrintable(shown));
+
+ // The date is offered as a one-day range. Qt::RFC2822Date checks the
+ // weekday against the date, so a fixture with the wrong day silently
+ // produces no offer at all: 2026-08-04 is a Tuesday.
+ QVERIFY2(queries.contains(QStringLiteral("date:2026-08-04..2026-08-04")),
+ qPrintable(shown));
+
+ // Every offer carries a label the menu shows, naming the value so the user
+ // can see what they are about to search for.
+ for (const SearchOffer &offer : offers) {
+ QVERIFY(!offer.label.isEmpty());
+ QVERIFY(!offer.query.isEmpty());
+ }
+}
+
+void TestMessageView::headerOffersNoSenderForARealThread()
+{
+ // The header shows From/To/Cc only for a single-message thread, because a
+ // thread's recipient differs message to message. The menu shares that
+ // condition: it must never offer a value the header is not stating.
+ ThreadRenderItem first = oneMessage();
+ ThreadRenderItem second = oneMessage();
+ second.message.from = QStringLiteral("Recipient <recipient@example.org>");
+ second.message.to = QStringLiteral("Sender <sender@example.org>");
+
+ MessageView view;
+ view.showThread({ first, second });
+
+ QStringList queries;
+ for (const SearchOffer &offer : view.headerSearchOffers())
+ queries << offer.query;
+
+ // THE GUARD. A test asserting only that something is absent passes against
+ // no implementation whatever. Subject and date must still be offered,
+ // which proves the list was built before the absences below mean anything.
+ QVERIFY2(!queries.isEmpty(), "no offers at all: the list was never built");
+ QVERIFY(queries.contains(QStringLiteral("subject:\"Quarterly report\"")));
+ QVERIFY(queries.contains(QStringLiteral("date:2026-08-04..2026-08-04")));
+
+ for (const QString &query : queries) {
+ QVERIFY2(!query.startsWith(QStringLiteral("from:")),
+ qPrintable(QStringLiteral("thread offered %1").arg(query)));
+ QVERIFY2(!query.startsWith(QStringLiteral("to:")),
+ qPrintable(QStringLiteral("thread offered %1").arg(query)));
+ QVERIFY2(!query.startsWith(QStringLiteral("cc:")),
+ qPrintable(QStringLiteral("thread offered %1").arg(query)));
+ }
+}
+
+void TestMessageView::headerOffersNothingForAnAbsentField()
+{
+ // cc:"" parses cleanly and matches nothing, so an entry built from an
+ // empty header would look enabled and silently do nothing.
+ ThreadRenderItem item = oneMessage();
+ item.message.cc.clear();
+
+ MessageView view;
+ view.showThread({ item });
+
+ QStringList queries;
+ for (const SearchOffer &offer : view.headerSearchOffers())
+ queries << offer.query;
+
+ // Guard first, then the absence.
+ QVERIFY2(!queries.isEmpty(), "no offers at all: the list was never built");
+ QVERIFY(queries.contains(QStringLiteral("subject:\"Quarterly report\"")));
+
+ for (const QString &query : queries)
+ QVERIFY(!query.startsWith(QStringLiteral("cc:")));
+}
+
QTEST_MAIN(TestMessageView)
#include "test_messageview.moc"