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

source code of qtdeclarative/src/quickcontrolstestutils/controlstestutils.cpp