aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_querycompleter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_querycompleter.cpp')
-rw-r--r--tests/test_querycompleter.cpp144
1 files changed, 144 insertions, 0 deletions
diff --git a/tests/test_querycompleter.cpp b/tests/test_querycompleter.cpp
index 56b5343..5ae9312 100644
--- a/tests/test_querycompleter.cpp
+++ b/tests/test_querycompleter.cpp
@@ -20,6 +20,7 @@
#include <QTemporaryDir>
#include <QLineEdit>
+#include <QListView>
#include "config.h"
#include "querycompleter.h"
@@ -54,6 +55,14 @@ private slots:
void acceptReplacesOnlyTheValueAfterThePrefix();
void acceptReplacesOnlyTheEditedRangeBound();
void acceptReplacesTheWholeBoundWhenCompletingMidWord();
+
+ // The tests above call acceptCompletion() directly and so never touch the
+ // widget. These drive the path a user actually hits.
+ void typingOpensThePopupOnALaterToken();
+ void tabAcceptsTheHighlightedCompletion();
+ void tabIsIgnoredWhileThePopupIsHidden();
+ void returnIsIgnoredWhileThePopupIsHidden();
+ void focusOpensThePopupOnlyWhenConfigured();
};
// Copied from tests/test_config.cpp rather than shared, so the two test files
@@ -353,5 +362,140 @@ void TestQueryCompleter::acceptReplacesTheWholeBoundWhenCompletingMidWord()
QStringLiteral("date:this_week..today"));
}
+// The popup is owned by the QCompleter, which is not reachable from the line
+// edit now that setCompleter is deliberately not used. It is the only list
+// view these tests create, so find it that way.
+static QListView *findPopup()
+{
+ const auto widgets = QApplication::allWidgets();
+ for (QWidget *w : widgets) {
+ if (auto *view = qobject_cast<QListView *>(w))
+ return view;
+ }
+ return nullptr;
+}
+
+void TestQueryCompleter::typingOpensThePopupOnALaterToken()
+{
+ // The regression: QLineEdit::setCompleter reset the completion prefix to
+ // the widget's whole text on every keystroke, so nothing matched and the
+ // popup stopped appearing after the first token.
+ Config config;
+ QLineEdit edit;
+ edit.show();
+ QueryCompleter completer(&edit, config);
+
+ QTest::keyClicks(&edit, QStringLiteral("tag:unread date:last"));
+
+ QListView *popup = findPopup();
+ QVERIFY(popup);
+ QVERIFY(popup->isVisible());
+
+ QStringList offered;
+ for (int row = 0; row < popup->model()->rowCount(); ++row)
+ offered << popup->model()->index(row, 0).data().toString();
+ QCOMPARE(offered, QStringList({ QStringLiteral("last_week"),
+ QStringLiteral("last_month") }));
+}
+
+void TestQueryCompleter::tabAcceptsTheHighlightedCompletion()
+{
+ // The user's exact scenario. Tab used to fall through to focus navigation,
+ // leaving the query half-typed.
+ Config config;
+ QLineEdit edit;
+ edit.show();
+ QueryCompleter completer(&edit, config);
+
+ QTest::keyClicks(&edit, QStringLiteral("tag:unread date:last"));
+ QVERIFY(findPopup() && findPopup()->isVisible());
+
+ QKeyEvent tab(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier,
+ QStringLiteral("\t"));
+ QApplication::sendEvent(&edit, &tab);
+
+ // Consumed, so focus does not move to the next widget.
+ QVERIFY(tab.isAccepted());
+ // Only the token being completed is replaced, not the whole line.
+ QCOMPARE(edit.text(), QStringLiteral("tag:unread date:last_week"));
+ QVERIFY(!findPopup()->isVisible());
+}
+
+void TestQueryCompleter::tabIsIgnoredWhileThePopupIsHidden()
+{
+ // Every key must fall through when the popup is closed, or the query bar
+ // stops behaving like a line edit.
+ Config config;
+ QLineEdit edit;
+ edit.show();
+ QueryCompleter completer(&edit, config);
+
+ edit.setText(QStringLiteral("tag:unread"));
+ if (QListView *popup = findPopup())
+ popup->hide();
+
+ QKeyEvent tab(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier,
+ QStringLiteral("\t"));
+ tab.ignore();
+ QApplication::sendEvent(&edit, &tab);
+
+ QCOMPARE(edit.text(), QStringLiteral("tag:unread"));
+}
+
+void TestQueryCompleter::returnIsIgnoredWhileThePopupIsHidden()
+{
+ // Enter accepts a completion only while the popup is up. With it closed it
+ // must still reach returnPressed, which is what runs the query.
+ Config config;
+ QLineEdit edit;
+ edit.show();
+ QueryCompleter completer(&edit, config);
+
+ edit.setText(QStringLiteral("tag:unread"));
+ if (QListView *popup = findPopup())
+ popup->hide();
+
+ bool ran = false;
+ connect(&edit, &QLineEdit::returnPressed, &edit, [&ran]() { ran = true; });
+
+ QKeyEvent ret(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier);
+ QApplication::sendEvent(&edit, &ret);
+
+ QVERIFY(ran);
+ QCOMPARE(edit.text(), QStringLiteral("tag:unread"));
+}
+
+void TestQueryCompleter::focusOpensThePopupOnlyWhenConfigured()
+{
+ // The event filter is now installed unconditionally, so the
+ // completion_on_focus check moved inside it. Both settings still behave.
+ QTemporaryDir dir;
+
+ {
+ Config off;
+ off.load(writeIni(dir, QStringLiteral("[general]\n"
+ "completion_on_focus=false\n")));
+ QLineEdit edit;
+ edit.show();
+ QueryCompleter completer(&edit, off);
+ edit.setFocus();
+ QVERIFY(!findPopup() || !findPopup()->isVisible());
+ }
+
+ {
+ Config on;
+ on.load(writeIni(dir, QStringLiteral("[general]\n"
+ "completion_on_focus=true\n")));
+ QVERIFY(on.completionOnFocus());
+ QLineEdit edit;
+ edit.show();
+ QueryCompleter completer(&edit, on);
+ QFocusEvent focusIn(QEvent::FocusIn);
+ QApplication::sendEvent(&edit, &focusIn);
+ QVERIFY(findPopup());
+ QVERIFY(findPopup()->isVisible());
+ }
+}
+
QTEST_MAIN(TestQueryCompleter)
#include "test_querycompleter.moc"