Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
controlstestutils.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5
6#include <QtTest/qsignalspy.h>
7#include <QtQml/qqmlcomponent.h>
8#include <QtQuickControls2/qquickstyle.h>
9#include <QtQuickTemplates2/private/qquickabstractbutton_p.h>
10#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
11#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
12#include <QtQuickTemplates2/private/qquickpopup_p.h>
13#include <QtQuickTemplates2/private/qquickpopupitem_p_p.h>
14#include <QtQuickTemplates2/private/qquickmenu_p_p.h>
15#include <QtQuickTemplates2/private/qquickmenuitem_p.h>
16#include <QtQuickTemplates2/private/qquickmenuitem_p_p.h>
17
18QQuickControlsTestUtils::QQuickControlsApplicationHelper::QQuickControlsApplicationHelper(QQmlDataTest *testCase,
19 const QString &testFilePath, const QVariantMap &initialProperties, const QStringList &qmlImportPaths)
20 : QQuickApplicationHelper(testCase, testFilePath, initialProperties, qmlImportPaths)
21{
22 if (ready)
23 appWindow = qobject_cast<QQuickApplicationWindow*>(cleanup.data());
24}
25
26/*!
27 \internal
28
29 If \a style is different from the current style, this function will
30 recreate the QML engine, clear type registrations and set the new style.
31
32 Returns \c true if successful or if \c style is already set.
33*/
35{
36 // If it's not the first time a style has been set and the new style is not different, do nothing.
37 if (!currentStyle.isEmpty() && style == currentStyle)
38 return true;
39
40 engine.reset();
41 currentStyle = style;
42 qmlClearTypeRegistrations();
43 engine.reset(new QQmlEngine);
44 QQuickStyle::setStyle(style);
45
46 QQmlComponent component(engine.data());
47 component.setData(QString::fromUtf8("import QtQuick\nimport QtQuick.Controls\n Control { }").toUtf8(), QUrl());
48 if (!component.isReady())
49 qWarning() << "Failed to load component:" << component.errorString();
50 return component.isReady();
51}
52
53void QQuickControlsTestUtils::forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath,
54 const QString &sourcePath, const QString &targetPath, const QStringList &skipList,
56{
57 // We cannot use QQmlComponent to load QML files directly from the source tree.
58 // For styles that use internal QML types (eg. material/Ripple.qml), the source
59 // dir would be added as an "implicit" import path overriding the actual import
60 // path (qtbase/qml/QtQuick/Controls.2/Material). => The QML engine fails to load
61 // the style C++ plugin from the implicit import path (the source dir).
62 //
63 // Therefore we only use the source tree for finding out the set of QML files that
64 // a particular style implements, and then we locate the respective QML files in
65 // the engine's import path. This way we can use QQmlComponent to load each QML file
66 // for benchmarking.
67
68 const QFileInfoList entries = QDir(qqc2ImportPath + QLatin1Char('/') + sourcePath).entryInfoList(
69 QStringList(QStringLiteral("*.qml")), QDir::Files);
70 for (const QFileInfo &entry : entries) {
71 QString name = entry.baseName();
72 if (!skipList.contains(name)) {
73 const auto importPathList = engine->importPathList();
74 for (const QString &importPath : importPathList) {
75 QString name = entry.dir().dirName() + QLatin1Char('/') + entry.fileName();
76 QString filePath = importPath + QLatin1Char('/') + targetPath + QLatin1Char('/') + entry.fileName();
77 if (filePath.startsWith(QLatin1Char(':')))
78 filePath.prepend(QStringLiteral("qrc"));
79 if (QFile::exists(filePath)) {
80 callback(name, QUrl::fromLocalFile(filePath));
81 break;
82 } else {
83 QUrl url(filePath);
84 filePath = QQmlFile::urlToLocalFileOrQrc(filePath);
85 if (!filePath.isEmpty() && QFile::exists(filePath)) {
86 callback(name, url);
87 break;
88 }
89 }
90 }
91 }
92 }
93}
94
95void QQuickControlsTestUtils::addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath,
96 const QString &sourcePath, const QString &targetPath, const QStringList &skipList)
97{
98 forEachControl(engine, qqc2ImportPath, sourcePath, targetPath, skipList, [&](const QString &relativePath, const QUrl &absoluteUrl) {
99 QTest::newRow(qPrintable(relativePath)) << absoluteUrl;
100 });
101}
102
103bool QQuickControlsTestUtils::verifyButtonClickable(QQuickAbstractButton *button)
104{
105 if (!button->window()) {
106 qWarning() << "button" << button << "doesn't have an associated window";
107 return false;
108 }
109
110 if (!button->isEnabled()) {
111 qWarning() << "button" << button << "is not enabled";
112 return false;
113 }
114
115 if (!button->isVisible()) {
116 qWarning() << "button" << button << "is not visible";
117 return false;
118 }
119
120 if (button->width() <= 0.0) {
121 qWarning() << "button" << button << "must have a width greater than 0";
122 return false;
123 }
124
125 if (button->height() <= 0.0) {
126 qWarning() << "button" << button << "must have a height greater than 0";
127 return false;
128 }
129
130 return true;
131}
132
133bool QQuickControlsTestUtils::clickButton(QQuickAbstractButton *button)
134{
135 if (!verifyButtonClickable(button))
136 return false;
137
138 QSignalSpy spy(button, &QQuickAbstractButton::clicked);
139 if (!spy.isValid()) {
140 qWarning() << "Button" << button << "must have a valid clicked signal";
141 return false;
142 }
143
144 const QPoint buttonCenter = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
145 QTest::mouseClick(button->window(), Qt::LeftButton, Qt::NoModifier, buttonCenter);
146 if (spy.size() != 1) {
147 QDebug warning(QtWarningMsg);
148 warning.nospace() << "The clicked signal of button " << button << " was not emitted after "
149 << "clicking at " << buttonCenter << ".";
150 const QQuickPopup *popup = popupParent(button);
151 if (popup && !popup->isOpened()) {
152 warning << " The popup it's in (" << popup << ") is no longer opened; "
153 << "the click may have missed the button and gone outside of the popup, "
154 << "causing it to close.";
155 }
156 return false;
157 }
158
159 return true;
160}
161
162/*!
163 \internal
164
165 If \a menuItem is not in a menu, use \l clickButton.
166*/
167bool QQuickControlsTestUtils::clickMenuItem(QQuickMenuItem *menuItem)
168{
169 auto *menuItemPrivate = QQuickMenuItemPrivate::get(menuItem);
170 if (!menuItemPrivate->menu) {
171 qWarning() << "MenuItem" << menuItem << "must be in a menu in order to be clicked";
172 return false;
173 }
174
175 if (menuItemPrivate->menu->enter()) {
176 /*
177 FluentWinUI3 animates its height in its enter transition. This causes issues in
178 context menu tests (tst_QQuickContextMenu) on Ubuntu (X11), because the native resize
179 events caused by the menu's height changes arrive too late, causing clicks to miss the
180 menu item and instead close the menu (which clickButton now warns about).
181
182 There doesn't appear to be a way to reliably detect and hence wait for these events.
183 We also can't disable the enter transition because the menu doesn't exist until the
184 right click event, by which point the transition has also already started.
185
186 We tried an environment variable to allow the test to disable them before they start,
187 but it was still flaky. So we now simply click the menu item programmatically for menus
188 with enter transitions.
189 */
190 menuItem->click();
191 return true;
192 }
193
194 return clickButton(menuItem);
195}
196
197bool QQuickControlsTestUtils::doubleClickButton(QQuickAbstractButton *button)
198{
199 if (!verifyButtonClickable(button))
200 return false;
201
202 QSignalSpy spy(button, &QQuickAbstractButton::clicked);
203 if (!spy.isValid()) {
204 qWarning() << "button" << button << "must have a valid doubleClicked signal";
205 return false;
206 }
207
208 const QPoint buttonCenter = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
209 QTest::mouseDClick(button->window(), Qt::LeftButton, Qt::NoModifier, buttonCenter);
210 if (spy.size() != 1) {
211 qWarning() << "doubleClicked signal of button" << button << "was not emitted after double-clicking";
212 return false;
213 }
214
215 return true;
216}
217
218/*!
219 Allows creating QQmlComponents in C++, which is useful for tests that need
220 to check if items created from the component have the correct QML context.
221*/
222Q_INVOKABLE QQmlComponent *QQuickControlsTestUtils::ComponentCreator::createComponent(const QByteArray &data)
223{
224 std::unique_ptr<QQmlComponent> component(new QQmlComponent(qmlEngine(this)));
225 component->setData(data, QUrl());
226 if (component->isError())
227 qmlWarning(this) << "Failed to create component from the following data:\n" << data;
228 return component.release();
229}
230
231QString QQuickControlsTestUtils::StyleInfo::styleName() const
232{
233 return QQuickStyle::name();
234}
235
236/*!
237 It's recommended to use try-finally (see tst_monthgrid.qml for an example)
238 or init/initTestCase and cleanup/cleanupTestCase if setting environment
239 variables, in order to restore previous values.
240*/
241QString QQuickControlsTestUtils::SystemEnvironment::value(const QString &name)
242{
243 return QString::fromLocal8Bit(qgetenv(name.toLocal8Bit()));
244}
245
246bool QQuickControlsTestUtils::SystemEnvironment::setValue(const QString &name, const QString &value)
247{
248 return qputenv(name.toLocal8Bit(), value.toLocal8Bit());
249}
250
252{
253 QString message;
254 QDebug debug(&message);
255 const auto *controlPrivate = QQuickControlPrivate::get(control);
256 const QQuickWindow *window = control->window();
257 const QString activeFocusItemStr = window
258 ? QDebug::toString(window->activeFocusItem()) : QStringLiteral("(unknown; control has no window)");
259 debug.nospace() << "control: " << control << " activeFocus: " << control->hasActiveFocus()
260 << " focusReason: " << static_cast<Qt::FocusReason>(controlPrivate->focusReason)
261 << " activeFocusItem: " << activeFocusItemStr;
262 return message;
263}
264
265bool QQuickControlsTestUtils::ApplicationAttributes::test(Qt::ApplicationAttribute attribute) const
266{
267 return QCoreApplication::testAttribute(attribute);
268}
269
270void QQuickControlsTestUtils::ApplicationAttributes::set(Qt::ApplicationAttribute attribute, bool on)
271{
272 QCoreApplication::setAttribute(attribute, on);
273}
274
276{
277#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACOS)
278 return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::Capability::MultipleWindows);
279#else
280 return false;
281#endif
282}
283
284/*!
285 \internal
286
287 Finds the popup that \a item is in, or returns \c nullptr.
288*/
289QQuickPopup *QQuickControlsTestUtils::popupParent(QQuickItem *item)
290{
291 QQuickItem *parentItem = item;
292 while (parentItem) {
293 auto *parentAsPopupItem = qobject_cast<QQuickPopupItem *>(parentItem);
294 if (parentAsPopupItem)
295 return QQuickPopupItemPrivate::get(parentAsPopupItem)->popup;
296
297 parentItem = parentItem->parentItem();
298 }
299
300 return nullptr;
301}
302
304{
305 QByteArray message;
306 QDebug debug(&message);
307 const QQuickWindow *window = popup->window();
308 const QString activeFocusItemStr = window
309 ? QDebug::toString(window->activeFocusItem()) : QStringLiteral("(unknown; popup has no window)");
310 debug.nospace() << "popup: " << popup;
311 debug.noquote() << " window's activeFocusItem: " << activeFocusItemStr;
312 return message;
313}
The QQmlComponent class encapsulates a QML component definition.
Q_INVOKABLE void set(Qt::ApplicationAttribute attribute, bool on=true)
Q_INVOKABLE bool setValue(const QString &name, const QString &value)
bool verifyButtonClickable(QQuickAbstractButton *button)
void addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, const QString &sourcePath, const QString &targetPath, const QStringList &skipList=QStringList())
void forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath, const QString &sourcePath, const QString &targetPath, const QStringList &skipList, ForEachCallback callback)
bool clickMenuItem(QQuickMenuItem *menuItem)
bool doubleClickButton(QQuickAbstractButton *button)
QQuickPopup * popupParent(QQuickItem *item)
QString visualFocusFailureMessage(QQuickControl *control)
bool clickButton(QQuickAbstractButton *button)
std::function< void(const QString &, const QUrl &)> ForEachCallback
QByteArray qActiveFocusFailureMessage(QQuickPopup *popup)