diff options
Diffstat (limited to 'src/quickcontrolstestutils/controlstestutils.cpp')
-rw-r--r-- | src/quickcontrolstestutils/controlstestutils.cpp | 185 |
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(); +} |