| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Kevin Krammer <kevin.krammer@kdab.com> |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| #include <qtest.h> |
| #include <QtQml/qqmlcomponent.h> |
| #include <QtQml/qqmlengine.h> |
| #include <QtQml/private/qqmlmetatype_p.h> |
| #include <QtCore/QDebug> |
| #include <QtCore/QHash> |
| #include <QtCore/QSet> |
| |
| class tst_PropertyRequirements : public QObject |
| { |
| Q_OBJECT |
| public: |
| tst_PropertyRequirements(); |
| |
| private slots: |
| void constantOrNotifyableMain(); |
| void constantOrNotifyableFull(); |
| |
| private: |
| typedef QList<QQmlType> Failures; |
| |
| /*! |
| All properties that do not pass the check and the affected QML types |
| |
| Key: Property name formatted as C++-class-name::property-name |
| Value: List of QQmlType which have the property |
| */ |
| typedef QHash<QString, Failures> FailuresByProperty; |
| |
| enum TestDepth { |
| MainTypeOnly, //! Check only the properties of the main C++ type for each QML type |
| WithSuperClasses //! Check the super classes for each C++ type as well |
| }; |
| |
| void testAllQmlTypes(TestDepth testDepth, FailuresByProperty &failuresByProperty); |
| void testQmlType(TestDepth testDepth, const QQmlType &qmlType, FailuresByProperty &failuresByProperty); |
| }; |
| |
| |
| tst_PropertyRequirements::tst_PropertyRequirements() |
| { |
| } |
| |
| void tst_PropertyRequirements::constantOrNotifyableMain() |
| { |
| FailuresByProperty failuresByProperty; |
| |
| // test |
| testAllQmlTypes(MainTypeOnly, failuresByProperty); |
| |
| // report |
| QHash<QString, QStringList> occurrences; |
| for (auto it = failuresByProperty.constBegin(); it != failuresByProperty.constEnd(); ++it) { |
| |
| occurrences[it.value().at(0).qmlTypeName()].append(it.key()); |
| } |
| |
| QStringList messages; |
| for (auto it = occurrences.constBegin(); it != occurrences.constEnd(); ++it) { |
| const QString occurrencePattern("%1:\n\t%2"); |
| |
| QStringList properties = it.value(); |
| properties.sort(); |
| messages.append(occurrencePattern.arg(it.key(), properties.join("\n\t"))); |
| } |
| messages.sort(); |
| |
| const QString message("\nThe following QML Types have properties which are neither CONSTANT nor NOTIFYable:\n"); |
| QWARN(qPrintable(message + messages.join("\n"))); |
| |
| // TODO enable once technical debt is fixes |
| // QCOMPARE(failuresByProperty.count(), 0); |
| } |
| |
| void tst_PropertyRequirements::constantOrNotifyableFull() |
| { |
| FailuresByProperty failuresByProperty; |
| |
| // test |
| testAllQmlTypes(WithSuperClasses, failuresByProperty); |
| |
| // report |
| QStringList messages; |
| for (auto it = failuresByProperty.constBegin(); it != failuresByProperty.constEnd(); ++it) { |
| |
| QSet<QString> occurrences; |
| for (const QQmlType &qmlType : it.value()) { |
| static const QString occurrencePattern("%1 (%2)"); |
| |
| occurrences.insert(occurrencePattern.arg(qmlType.metaObject()->className(), |
| qmlType.qmlTypeName())); |
| |
| } |
| |
| static const QString messagePattern("\nProperty %1 neither CONSTANT nor NOTIFYable. Affected types:\n\t%2"); |
| QStringList occurrencesList = occurrences.values(); |
| occurrencesList.sort(); |
| messages.append(messagePattern.arg(it.key(), occurrencesList.join("\n\t"))); |
| |
| } |
| messages.sort(); |
| |
| QWARN(qPrintable(messages.join("\n"))); |
| |
| // TODO enable once technical debt is fixes |
| // QCOMPARE(failuresByProperty.count(), 0); |
| } |
| |
| void tst_PropertyRequirements::testAllQmlTypes(TestDepth testDepth, FailuresByProperty &failuresByProperty) |
| { |
| const QVector<QByteArray> qmlData { |
| "import QtQml 2.2\nQtObject {}", |
| "import QtQml.Models 2.2\nListModel {}", |
| "import QtQuick 2.5\nItem {}", |
| "import QtQuick.Window 2.2\nWindow {}", |
| "import QtQuick.Dialogs 1.2\nDialog {}", |
| "import QtQuick.Layouts 1.2\nGridLayout {}", |
| "import QtQuick.Controls 2.2\nButton {}", |
| "import QtQuick.Templates 2.2\nButton {}" |
| }; |
| |
| QQmlEngine engine; |
| QSet<QString> seenTypes; |
| |
| for (const QByteArray &data : qmlData) { |
| QQmlComponent component(&engine); |
| component.setData(data, QUrl()); |
| |
| for (const QQmlType &qmlType : QQmlMetaType::qmlTypes()) { |
| if (!seenTypes.contains(qmlType.qmlTypeName())) { |
| testQmlType(testDepth, qmlType, failuresByProperty); |
| } |
| } |
| const auto &typeNameList = QQmlMetaType::qmlTypeNames(); |
| seenTypes.unite(QSet<QString>(typeNameList.cbegin(), typeNameList.cend())); |
| } |
| } |
| |
| void tst_PropertyRequirements::testQmlType(TestDepth testDepth, const QQmlType &qmlType, FailuresByProperty &failuresByProperty) |
| { |
| QList<const QMetaObject*> inheritanceHierarchy; |
| |
| const QMetaObject *mo = qmlType.metaObject(); |
| while (mo) { |
| inheritanceHierarchy.prepend(mo); |
| mo = mo->superClass(); |
| } |
| |
| // check if this type is derived from QObject and even can have signals |
| // i.e. weed out the Q_GADGET classes |
| if (inheritanceHierarchy.isEmpty() |
| || inheritanceHierarchy.constFirst()->className() != QByteArrayLiteral("QObject")) { |
| return; |
| } |
| |
| if (testDepth == MainTypeOnly) { |
| inheritanceHierarchy.clear(); |
| inheritanceHierarchy.append(qmlType.metaObject()); |
| } |
| |
| for (const QMetaObject *metaClass : qAsConst(inheritanceHierarchy)) { |
| for (int idx = metaClass->propertyOffset(); idx < metaClass->propertyCount(); ++idx) { |
| const QMetaProperty property = metaClass->property(idx); |
| |
| // needs to be either CONSTANT or have a NOTIFY signal |
| if (!property.isConstant() && !property.hasNotifySignal()) { |
| static const QString fullNamePattern("%1::%2"); |
| const QString fullPropertyName = fullNamePattern.arg(metaClass->className(), property.name()); |
| |
| failuresByProperty[fullPropertyName].append(qmlType); |
| } |
| } |
| } |
| } |
| |
| QTEST_MAIN(tst_PropertyRequirements) |
| |
| #include "tst_propertyrequirements.moc" |