// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "controlstestutils_p.h" #include #include #include #include #include QQuickControlsTestUtils::QQuickControlsApplicationHelper::QQuickControlsApplicationHelper(QQmlDataTest *testCase, const QString &testFilePath, const QVariantMap &initialProperties, const QStringList &qmlImportPaths) : QQuickApplicationHelper(testCase, testFilePath, initialProperties, qmlImportPaths) { if (ready) appWindow = qobject_cast(cleanup.data()); } 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 false; 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()); return true; } 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.count() != 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.count() != 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 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(); }