aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickcontrolstestutils/controlstestutils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quickcontrolstestutils/controlstestutils.cpp')
-rw-r--r--src/quickcontrolstestutils/controlstestutils.cpp185
1 files changed, 185 insertions, 0 deletions
diff --git a/src/quickcontrolstestutils/controlstestutils.cpp b/src/quickcontrolstestutils/controlstestutils.cpp
new file mode 100644
index 0000000000..5330b8ebbd
--- /dev/null
+++ b/src/quickcontrolstestutils/controlstestutils.cpp
@@ -0,0 +1,185 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "controlstestutils_p.h"
+
+#include <QtTest/qsignalspy.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQuickControls2/qquickstyle.h>
+#include <QtQuickTemplates2/private/qquickabstractbutton_p.h>
+#include <QtQuickTemplates2/private/qquickapplicationwindow_p.h>
+
+QQuickControlsTestUtils::QQuickControlsApplicationHelper::QQuickControlsApplicationHelper(QQmlDataTest *testCase,
+ const QString &testFilePath, const QVariantMap &initialProperties, const QStringList &qmlImportPaths)
+ : QQuickApplicationHelper(testCase, testFilePath, initialProperties, qmlImportPaths)
+{
+ if (ready)
+ appWindow = qobject_cast<QQuickApplicationWindow*>(cleanup.data());
+}
+
+/*!
+ \internal
+
+ If \a style is different from the current style, this function will
+ recreate the QML engine, clear type registrations and set the new style.
+
+ Returns \c true if successful or if \c style is already set.
+*/
+bool QQuickControlsTestUtils::QQuickStyleHelper::updateStyle(const QString &style)
+{
+ // If it's not the first time a style has been set and the new style is not different, do nothing.
+ if (!currentStyle.isEmpty() && style == currentStyle)
+ return true;
+
+ engine.reset();
+ currentStyle = style;
+ qmlClearTypeRegistrations();
+ engine.reset(new QQmlEngine);
+ QQuickStyle::setStyle(style);
+
+ QQmlComponent component(engine.data());
+ component.setData(QString::fromUtf8("import QtQuick\nimport QtQuick.Controls\n Control { }").toUtf8(), QUrl());
+ if (!component.isReady())
+ qWarning() << "Failed to load component:" << component.errorString();
+ return component.isReady();
+}
+
+void QQuickControlsTestUtils::forEachControl(QQmlEngine *engine, const QString &qqc2ImportPath,
+ const QString &sourcePath, const QString &targetPath, const QStringList &skipList,
+ QQuickControlsTestUtils::ForEachCallback callback)
+{
+ // We cannot use QQmlComponent to load QML files directly from the source tree.
+ // For styles that use internal QML types (eg. material/Ripple.qml), the source
+ // dir would be added as an "implicit" import path overriding the actual import
+ // path (qtbase/qml/QtQuick/Controls.2/Material). => The QML engine fails to load
+ // the style C++ plugin from the implicit import path (the source dir).
+ //
+ // Therefore we only use the source tree for finding out the set of QML files that
+ // a particular style implements, and then we locate the respective QML files in
+ // the engine's import path. This way we can use QQmlComponent to load each QML file
+ // for benchmarking.
+
+ const QFileInfoList entries = QDir(qqc2ImportPath + QLatin1Char('/') + sourcePath).entryInfoList(
+ QStringList(QStringLiteral("*.qml")), QDir::Files);
+ for (const QFileInfo &entry : entries) {
+ QString name = entry.baseName();
+ if (!skipList.contains(name)) {
+ const auto importPathList = engine->importPathList();
+ for (const QString &importPath : importPathList) {
+ QString name = entry.dir().dirName() + QLatin1Char('/') + entry.fileName();
+ QString filePath = importPath + QLatin1Char('/') + targetPath + QLatin1Char('/') + entry.fileName();
+ if (filePath.startsWith(QLatin1Char(':')))
+ filePath.prepend(QStringLiteral("qrc"));
+ if (QFile::exists(filePath)) {
+ callback(name, QUrl::fromLocalFile(filePath));
+ break;
+ } else {
+ QUrl url(filePath);
+ filePath = QQmlFile::urlToLocalFileOrQrc(filePath);
+ if (!filePath.isEmpty() && QFile::exists(filePath)) {
+ callback(name, url);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void QQuickControlsTestUtils::addTestRowForEachControl(QQmlEngine *engine, const QString &qqc2ImportPath,
+ const QString &sourcePath, const QString &targetPath, const QStringList &skipList)
+{
+ forEachControl(engine, qqc2ImportPath, sourcePath, targetPath, skipList, [&](const QString &relativePath, const QUrl &absoluteUrl) {
+ QTest::newRow(qPrintable(relativePath)) << absoluteUrl;
+ });
+}
+
+bool QQuickControlsTestUtils::verifyButtonClickable(QQuickAbstractButton *button)
+{
+ if (!button->window()) {
+ qWarning() << "button" << button << "doesn't have an associated window";
+ return false;
+ }
+
+ if (!button->isEnabled()) {
+ qWarning() << "button" << button << "is not enabled";
+ return false;
+ }
+
+ if (!button->isVisible()) {
+ qWarning() << "button" << button << "is not visible";
+ return false;
+ }
+
+ if (button->width() <= 0.0) {
+ qWarning() << "button" << button << "must have a width greater than 0";
+ return false;
+ }
+
+ if (button->height() <= 0.0) {
+ qWarning() << "button" << button << "must have a height greater than 0";
+ return false;
+ }
+
+ return true;
+}
+
+bool QQuickControlsTestUtils::clickButton(QQuickAbstractButton *button)
+{
+ if (!verifyButtonClickable(button))
+ return false;
+
+ QSignalSpy spy(button, &QQuickAbstractButton::clicked);
+ if (!spy.isValid()) {
+ qWarning() << "button" << button << "must have a valid clicked signal";
+ return false;
+ }
+
+ const QPoint buttonCenter = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
+ QTest::mouseClick(button->window(), Qt::LeftButton, Qt::NoModifier, buttonCenter);
+ if (spy.size() != 1) {
+ qWarning() << "clicked signal of button" << button << "was not emitted after clicking";
+ return false;
+ }
+
+ return true;
+}
+
+bool QQuickControlsTestUtils::doubleClickButton(QQuickAbstractButton *button)
+{
+ if (!verifyButtonClickable(button))
+ return false;
+
+ QSignalSpy spy(button, &QQuickAbstractButton::clicked);
+ if (!spy.isValid()) {
+ qWarning() << "button" << button << "must have a valid doubleClicked signal";
+ return false;
+ }
+
+ const QPoint buttonCenter = button->mapToScene(QPointF(button->width() / 2, button->height() / 2)).toPoint();
+ QTest::mouseDClick(button->window(), Qt::LeftButton, Qt::NoModifier, buttonCenter);
+ if (spy.size() != 1) {
+ qWarning() << "doubleClicked signal of button" << button << "was not emitted after double-clicking";
+ return false;
+ }
+
+ return true;
+}
+
+/*!
+ Allows creating QQmlComponents in C++, which is useful for tests that need
+ to check if items created from the component have the correct QML context.
+*/
+Q_INVOKABLE QQmlComponent *QQuickControlsTestUtils::ComponentCreator::createComponent(const QByteArray &data)
+{
+ std::unique_ptr<QQmlComponent> component(new QQmlComponent(qmlEngine(this)));
+ component->setData(data, QUrl());
+ if (component->isError())
+ qmlWarning(this) << "Failed to create component from the following data:\n" << data;
+ return component.release();
+}
+
+QString QQuickControlsTestUtils::StyleInfo::styleName() const
+{
+ return QQuickStyle::name();
+}