From 1e587846a255592e5aab022c4e6e134d4ff64ed2 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 3 Aug 2026 21:07:03 +0200 Subject: feat(completion): render the popup with descriptions The free-form date hint is a footer label rather than a model row: a row would be filtered away by the first non-matching keystroke and could be selected and inserted, producing a query that errors. QCompleter::setPopup takes a QAbstractItemView, so the label cannot be laid out beside the view in a container widget. The footer sits in space reserved with setViewportMargins inside the list view instead. setItemDelegate must run after setPopup, not before: setPopup installs a plain QStyledItemDelegate of its own and discards whatever was already set, which silently drops the description column. Accepting replaces exactly the span the tokenizer identified rather than QCompleter's own completion prefix, which is a different span once a prefix or a range bound is involved. Four tests drive that path directly instead of through synthetic key events, since whether a key needs Shift is a keyboard-layout property and could not decide the question. Co-Authored-By: Claude Opus 5 --- tests/test_querycompleter.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) (limited to 'tests') diff --git a/tests/test_querycompleter.cpp b/tests/test_querycompleter.cpp index bd1e50f..56b5343 100644 --- a/tests/test_querycompleter.cpp +++ b/tests/test_querycompleter.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include "config.h" #include "querycompleter.h" @@ -48,6 +50,10 @@ private slots: void folderOffersNothing(); void mimetypeAppendsConfiguredEntries(); void rangeContextDropsRelativeDates(); + void acceptReplacesOnlyThePrefixToken(); + void acceptReplacesOnlyTheValueAfterThePrefix(); + void acceptReplacesOnlyTheEditedRangeBound(); + void acceptReplacesTheWholeBoundWhenCompletingMidWord(); }; // Copied from tests/test_config.cpp rather than shared, so the two test files @@ -298,5 +304,54 @@ void TestQueryCompleter::rangeContextDropsRelativeDates() QVERIFY(!inRange.contains(QStringLiteral("1week.."))); } +// The accept path is driven directly rather than through synthetic key +// events: whether a key needs Shift is a keyboard-layout property, so +// QTest::keyClick could never decide whether this logic is right. +static QString acceptInto(const QString &text, int cursor, const QString &value) +{ + Config config; + QLineEdit edit; + QueryCompleter completer(&edit, config); + + edit.setText(text); + edit.setCursorPosition(cursor); + completer.updateContext(); + completer.acceptCompletion(value); + + return edit.text(); +} + +void TestQueryCompleter::acceptReplacesOnlyThePrefixToken() +{ + // The neighbouring token must survive untouched. + QCOMPARE(acceptInto(QStringLiteral("tag:inbox su"), 12, + QStringLiteral("subject:")), + QStringLiteral("tag:inbox subject:")); +} + +void TestQueryCompleter::acceptReplacesOnlyTheValueAfterThePrefix() +{ + // QCompleter's own insertion would overwrite "date:tod" whole, because + // that is the token it matched on. Only "tod" may be replaced. + QCOMPARE(acceptInto(QStringLiteral("date:tod"), 8, QStringLiteral("today")), + QStringLiteral("date:today")); +} + +void TestQueryCompleter::acceptReplacesOnlyTheEditedRangeBound() +{ + const QString text = QStringLiteral("date:yesterday..to"); + QCOMPARE(acceptInto(text, text.size(), QStringLiteral("today")), + QStringLiteral("date:yesterday..today")); +} + +void TestQueryCompleter::acceptReplacesTheWholeBoundWhenCompletingMidWord() +{ + // Caret sits after "yest" but the bound runs to the "..", so accepting + // must leave no "erday" tail behind. + QCOMPARE(acceptInto(QStringLiteral("date:yesterday..today"), 9, + QStringLiteral("this_week")), + QStringLiteral("date:this_week..today")); +} + QTEST_MAIN(TestQueryCompleter) #include "test_querycompleter.moc" -- cgit v1.2.3