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

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