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
14QQuickControlsTestUtils::QQuickControlsApplicationHelper::QQuickControlsApplicationHelper(QQmlDataTest *testCase,
15 const QString &testFilePath, const QVariantMap &initialProperties, const QStringList &qmlImportPaths)
16 : QQuickApplicationHelper(testCase, testFilePath, initialProperties, qmlImportPaths)
17{
18 if (ready)
19 appWindow = qobject_cast<QQuickApplicationWindow*>(cleanup.data());
20}
21
22/*!
23 \internal
24
25 If \a style is different from the current style, this function will
26 recreate the QML engine, clear type registrations and set the new style.
27
28 Returns \c true if successful or if \c style is already set.
29*/
31{
32 // If it's not the first time a style has been set and the new style is not different, do nothing.
33 if (!currentStyle.isEmpty() && style == currentStyle)
34 return true;
35
36 engine.reset();
37 currentStyle = style;
38 qmlClearTypeRegistrations();
39 engine.reset(new QQmlEngine);
40 QQuickStyle::setStyle(style);
41
42 QQmlComponent component(engine.data());
43 component.setData(QString::fromUtf8("import QtQuick\nimport QtQuick.Controls\n Control { }").toUtf8(), QUrl());
44 if (!component.isReady())
45 qWarning() << "Failed to load component:" << component.errorString();
46 return component.isReady();
47}
48
49void QQuickControlsTestUtils::forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath,
50 const QString &sourcePath, const QString &targetPath, const QStringList &skipList,
52{
53 // We cannot use QQmlComponent to load QML files directly from the source tree.
54 // For styles that use internal QML types (eg. material/Ripple.qml), the source
55 // dir would be added as an "implicit" import path overriding the actual import
56 // path (qtbase/qml/QtQuick/Controls.2/Material). => The QML engine fails to load
57 // the style C++ plugin from the implicit import path (the source dir).
58 //
59 // Therefore we only use the source tree for finding out the set of QML files that
60 // a particular style implements, and then we locate the respective QML files in
61 // the engine's import path. This way we can use QQmlComponent to load each QML file
62 // for benchmarking.
63
64 const QFileInfoList entries = QDir(qqc2ImportPath + QLatin1Char('/') + sourcePath).entryInfoList(
65 QStringList(QStringLiteral("*.qml")), QDir::Files);
66 for (const QFileInfo &entry : entries) {
67 QString name = entry.baseName();
68 if (!skipList.contains(name)) {
69 const auto importPathList = engine->importPathList();
70 for (const QString &importPath : importPathList) {
71 QString name = entry.dir().dirName() + QLatin1Char('/') + entry.fileName();
72 QString filePath = importPath + QLatin1Char('/') + targetPath + QLatin1Char('/') + entry.fileName();
73 if (filePath.startsWith(QLatin1Char(':')))
74 filePath.prepend(QStringLiteral("qrc"));
75 if (QFile::exists(filePath)) {
76 callback(name, QUrl::fromLocalFile(filePath));
77 break;
78 } else {
79 QUrl url(filePath);
80 filePath = QQmlFile::urlToLocalFileOrQrc(filePath);
81 if (!filePath.isEmpty() && QFile::exists(filePath)) {
82 callback(name, url);
83 break;
84 }
85 }
86 }
87 }
88 }
89}
90
91void QQuickControlsTestUtils::addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath,
92 const QString &sourcePath, const QString &targetPath, const QStringList &skipList)
93{
94 forEachControl(engine, qqc2ImportPath, sourcePath, targetPath, skipList, [&](const QString &relativePath, const QUrl &absoluteUrl) {
95 QTest::newRow(qPrintable(relativePath)) << absoluteUrl;
96 });
97}
98
99bool QQuickControlsTestUtils::verifyButtonClickable(QQuickAbstractButton *button)
100{
101 if (!button->window()) {
102 qWarning() << "button" << button << "doesn't have an associated window";
103 return false;
104 }
105
106 if (!button->isEnabled()) {
107 qWarning() << "button" << button << "is not enabled";
108 return false;
109 }
110
111 if (!button->isVisible()) {
112 qWarning() << "button" << button << "is not visible";
113 return false;
114 }
115
116 if (button->width() <= 0.0) {
117 qWarning() << "button" << button << "must have a width greater than 0";
118 return false;
119 }
120
121 if (button->height() <= 0.0) {
122 qWarning() << "button" << button << "must have a height greater than 0";
123 return false;
124 }
125
126 return true;
127}
128
129bool QQuickControlsTestUtils::clickButton(QQuickAbstractButton *button)
130{
131 if (!verifyButtonClickable(button))
132 return false;
133
134 QSignalSpy spy(button, &QQuickAbstractButton::clicked);
135 if (!spy.isValid()) {
136 qWarning() << "button" << button << "must have a valid clicked signal";
137 return false;
138 }
139
140 const QPoint buttonCenter = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
141 QTest::mouseClick(button->window(), Qt::LeftButton, Qt::NoModifier, buttonCenter);
142 if (spy.size() != 1) {
143 qWarning() << "clicked signal of button" << button << "was not emitted after clicking";
144 return false;
145 }
146
147 return true;
148}
149
150bool QQuickControlsTestUtils::doubleClickButton(QQuickAbstractButton *button)
151{
152 if (!verifyButtonClickable(button))
153 return false;
154
155 QSignalSpy spy(button, &QQuickAbstractButton::clicked);
156 if (!spy.isValid()) {
157 qWarning() << "button" << button << "must have a valid doubleClicked signal";
158 return false;
159 }
160
161 const QPoint buttonCenter = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
162 QTest::mouseDClick(button->window(), Qt::LeftButton, Qt::NoModifier, buttonCenter);
163 if (spy.size() != 1) {
164 qWarning() << "doubleClicked signal of button" << button << "was not emitted after double-clicking";
165 return false;
166 }
167
168 return true;
169}
170
171/*!
172 Allows creating QQmlComponents in C++, which is useful for tests that need
173 to check if items created from the component have the correct QML context.
174*/
175Q_INVOKABLE QQmlComponent *QQuickControlsTestUtils::ComponentCreator::createComponent(const QByteArray &data)
176{
177 std::unique_ptr<QQmlComponent> component(new QQmlComponent(qmlEngine(this)));
178 component->setData(data, QUrl());
179 if (component->isError())
180 qmlWarning(this) << "Failed to create component from the following data:\n" << data;
181 return component.release();
182}
183
184QString QQuickControlsTestUtils::StyleInfo::styleName() const
185{
186 return QQuickStyle::name();
187}
188
189/*!
190 It's recommended to use try-finally (see tst_monthgrid.qml for an example)
191 or init/initTestCase and cleanup/cleanupTestCase if setting environment
192 variables, in order to restore previous values.
193*/
194QString QQuickControlsTestUtils::SystemEnvironment::value(const QString &name)
195{
196 return QString::fromLocal8Bit(qgetenv(name.toLocal8Bit()));
197}
198
199bool QQuickControlsTestUtils::SystemEnvironment::setValue(const QString &name, const QString &value)
200{
201 return qputenv(name.toLocal8Bit(), value.toLocal8Bit());
202}
203
205{
206 QString message;
207 QDebug debug(&message);
208 const auto *controlPrivate = QQuickControlPrivate::get(control);
209 const QQuickWindow *window = control->window();
210 const QString activeFocusItemStr = window
211 ? QDebug::toString(window->activeFocusItem()) : QStringLiteral("(unknown; control has no window)");
212 debug.nospace() << "control: " << control << " activeFocus: " << control->hasActiveFocus()
213 << " focusReason: " << static_cast<Qt::FocusReason>(controlPrivate->focusReason)
214 << " activeFocusItem: " << activeFocusItemStr;
215 return message;
216}
217
219{
220#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACOS)
221 return QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::Capability::MultipleWindows);
222#else
223 return false;
224#endif
225}
226
228{
229 QByteArray message;
230 QDebug debug(&message);
231 const QQuickWindow *window = popup->window();
232 const QString activeFocusItemStr = window
233 ? QDebug::toString(window->activeFocusItem()) : QStringLiteral("(unknown; popup has no window)");
234 debug.nospace() << "popup: " << popup;
235 debug.noquote() << " window's activeFocusItem: " << activeFocusItemStr;
236 return message;
237}
The QQmlComponent class encapsulates a QML component definition.
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 doubleClickButton(QQuickAbstractButton *button)
QString visualFocusFailureMessage(QQuickControl *control)
bool clickButton(QQuickAbstractButton *button)
std::function< void(const QString &, const QUrl &)> ForEachCallback
QByteArray qActiveFocusFailureMessage(QQuickPopup *popup)