From 17513266b81e88b84fda250734f07c94a198fabc Mon Sep 17 00:00:00 2001 From: Anton Kreuzkamp Date: Wed, 26 Apr 2017 16:31:48 +0200 Subject: Add API to learn about QQmlBinding's dependencies Adds a method `dependencies()` to QQmlBinding, that returns a QVector of all properties the binding depends on. The API is meant to be used in debugging tools (e.g. in GammaRay). Also adds a public method subBindings() to QQmlValueTypeProxyBinding in order to be able to access their dependencies. Change-Id: Ib833703ec9e632661626c4532b8d73997f38e62b Reviewed-by: Simon Hausmann --- .../bindingdependencyapi/bindingdependencyapi.pro | 11 + .../tst_bindingdependencyapi.cpp | 360 +++++++++++++++++++++ tests/auto/qml/qml.pro | 3 +- 3 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro create mode 100644 tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp (limited to 'tests/auto') diff --git a/tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro b/tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro new file mode 100644 index 0000000000..430d87ab5b --- /dev/null +++ b/tests/auto/qml/bindingdependencyapi/bindingdependencyapi.pro @@ -0,0 +1,11 @@ +CONFIG += testcase +TARGET = tst_bindingdependencyapi +macos:CONFIG -= app_bundle + +SOURCES += tst_bindingdependencyapi.cpp + +include (../../shared/util.pri) + +TESTDATA = data/* + +QT += core-private gui-private qml-private quick-private testlib diff --git a/tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp b/tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp new file mode 100644 index 0000000000..6f24f85f6d --- /dev/null +++ b/tests/auto/qml/bindingdependencyapi/tst_bindingdependencyapi.cpp @@ -0,0 +1,360 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** 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 +#include +#include +#include +#include +#include +#include +#include "../../shared/util.h" +#include + +class tst_bindingdependencyapi : public QObject +{ + Q_OBJECT +public: + tst_bindingdependencyapi(); + +private slots: + void testSingleDep_data(); + void testSingleDep(); + void testManyDeps_data(); + void testManyDeps(); + void testConditionalDependencies_data(); + void testConditionalDependencies(); + void testBindingLoop(); + +private: + bool findProperties(const QVector &properties, QObject *obj, const QString &propertyName, const QVariant &value); +}; + +tst_bindingdependencyapi::tst_bindingdependencyapi() +{ +} + + +void tst_bindingdependencyapi::testSingleDep_data() +{ + QTest::addColumn("code"); + QTest::addColumn("referencedObjectName"); + + QTest::addRow("context property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "objectName: \"rect\"\n" + "property string labelText: \"Hello world!\"\n" + "Text { text: labelText }\n" + "}") << "rect"; + + QTest::addRow("scope property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "property string labelText: \"I am wrong!\"\n" + "Text {\n" + "objectName: \"text\"\n" + "property string labelText: \"Hello world!\"\n" + "text: labelText\n" + "}\n" + "}") << "text"; + + QTest::addRow("id object property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "id: rect\n" + "objectName: \"rect\"\n" + "property string labelText: \"Hello world!\"\n" + "Text { text: rect.labelText }\n" + "}") << "rect"; + + QTest::addRow("dynamic context property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "objectName: \"rect\"\n" + "property string labelText: \"Hello world!\"\n" + "Text { Component.onCompleted: text = Qt.binding(function() { return labelText; }); }\n" + "}") << "rect"; + + QTest::addRow("dynamic scope property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "property string labelText: \"I am wrong!\"\n" + "Text {\n" + "objectName: \"text\"\n" + "property string labelText: \"Hello world!\"\n" + "Component.onCompleted: text = Qt.binding(function() { return labelText; });\n" + "}\n" + "}") << "text"; + + QTest::addRow("dynamic id object property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "id: rect\n" + "objectName: \"rect\"\n" + "property string labelText: \"Hello world!\"\n" + "Text { Component.onCompleted: text = Qt.binding(function() { return rect.labelText; }); }\n" + "}") << "rect"; +} + +void tst_bindingdependencyapi::testSingleDep() +{ + QFETCH(QByteArray, code); + QFETCH(QString, referencedObjectName); + + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(code, QUrl()); + QObject *rect = c.create(); + QTest::qWait(10); + QVERIFY(rect != 0); + QObject *text = rect->findChildren().front(); + + QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild(referencedObjectName); + + auto data = QQmlData::get(text); + QVERIFY(data); + auto b = data->bindings; + QVERIFY(b); + auto binding = dynamic_cast(b); + QVERIFY(binding); + auto dependencies = binding->dependencies(); + QCOMPARE(dependencies.size(), 1); + auto dependency = dependencies.front(); + QVERIFY(dependency.isValid()); + QCOMPARE(quintptr(dependency.object()), quintptr(referencedObject)); + QCOMPARE(dependency.property().name(), "labelText"); + QCOMPARE(dependency.read().toString(), QStringLiteral("Hello world!")); + QCOMPARE(dependency, QQmlProperty(referencedObject, "labelText")); + + delete rect; +} + +bool tst_bindingdependencyapi::findProperties(const QVector &properties, QObject *obj, const QString &propertyName, const QVariant &value) +{ + auto dep = std::find_if(properties.cbegin(), properties.cend(), [&](const QQmlProperty &dep) { + return dep.object() == obj + && dep.property().name() == propertyName + && dep.read() == value; + }); + if (dep == properties.cend()) { + qWarning() << "Searched for property with:" << "{ object:" << obj << ", propertyName:" << propertyName << ", value:" << value << "}" << "but only found:"; + for (auto dep : properties) { + qWarning() << "{ object:" << dep.object() << ", propertyName:" << dep.property().name() << ", value:" << dep.read() << "}"; + } + return false; + } + return true; +} + +void tst_bindingdependencyapi::testManyDeps_data() +{ + QTest::addColumn("code"); + + QTest::addRow("permanent binding") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "id: rect\n" + "objectName: 'rect'\n" + "property string name: 'world'\n" + "Text {\n" + "text: config.helloWorldTemplate.arg(greeting).arg(rect.name) \n" + "property string greeting: 'Hello'\n" + "}\n" + "QtObject { id: config; objectName: 'config'; property string helloWorldTemplate: '%1 %2!' }\n" + "}"); + + QTest::addRow("dynamic binding") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "id: rect\n" + "objectName: 'rect'\n" + "property string name: 'world'\n" + "Text {\n" + "Component.onCompleted: text = Qt.binding(function() { return config.helloWorldTemplate.arg(greeting).arg(rect.name); }); \n" + "property string greeting: 'Hello'\n" + "}\n" + "QtObject { id: config; objectName: 'config'; property string helloWorldTemplate: '%1 %2!' }\n" + "}"); +} + +void tst_bindingdependencyapi::testManyDeps() +{ + QFETCH(QByteArray, code); + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(code, QUrl()); + QObject *rect = c.create(); + if (c.isError()) { + qWarning() << c.errorString(); + } + QTest::qWait(100); + QVERIFY(rect != 0); + QObject *text = rect->findChildren().front(); + QObject *configObj = rect->findChild("config"); + + auto data = QQmlData::get(text); + QVERIFY(data); + auto b = data->bindings; + QVERIFY(b); + auto binding = dynamic_cast(b); + QVERIFY(binding); + auto dependencies = binding->dependencies(); + QCOMPARE(dependencies.size(), 3); + + QVERIFY(findProperties(dependencies, rect, "name", "world")); + QVERIFY(findProperties(dependencies, text, "greeting", "Hello")); + QVERIFY(findProperties(dependencies, configObj, "helloWorldTemplate", "%1 %2!")); + + delete rect; +} + +void tst_bindingdependencyapi::testConditionalDependencies_data() +{ + QTest::addColumn("code"); + QTest::addColumn("referencedObjectName"); + + QTest::addRow("id object property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "id: rect\n" + "objectName: \"rect\"\n" + "property bool haveDep: false\n" + "property string labelText: \"Hello world!\"\n" + "Text { text: rect.haveDep ? rect.labelText : '' }\n" + "}") << "rect"; + + QTest::addRow("dynamic context property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "objectName: \"rect\"\n" + "property bool haveDep: false\n" + "property string labelText: \"Hello world!\"\n" + "Text { Component.onCompleted: text = Qt.binding(function() { return haveDep ? labelText : ''; }); }\n" + "}") << "rect"; + + QTest::addRow("dynamic scope property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "property string labelText: \"I am wrong!\"\n" + "Text {\n" + "objectName: \"text\"\n" + "property bool haveDep: false\n" + "property string labelText: \"Hello world!\"\n" + "Component.onCompleted: text = Qt.binding(function() { return haveDep ? labelText : ''; });\n" + "}\n" + "}") << "text"; + + QTest::addRow("dynamic id object property") + << QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "id: rect\n" + "objectName: \"rect\"\n" + "property bool haveDep: false\n" + "property string labelText: \"Hello world!\"\n" + "Text { Component.onCompleted: text = Qt.binding(function() { return rect.haveDep ? rect.labelText : ''; }); }\n" + "}") << "rect"; +} + +void tst_bindingdependencyapi::testConditionalDependencies() +{ + QFETCH(QByteArray, code); + QFETCH(QString, referencedObjectName); + + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(code, QUrl()); + QObject *rect = c.create(); + QTest::qWait(10); + QVERIFY(rect != 0); + QObject *text = rect->findChildren().front(); + + QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild(referencedObjectName); + + auto data = QQmlData::get(text); + QVERIFY(data); + auto b = data->bindings; + QVERIFY(b); + auto binding = dynamic_cast(b); + QVERIFY(binding); + auto dependencies = binding->dependencies(); + QCOMPARE(dependencies.size(), 1); + QVERIFY(findProperties(dependencies, referencedObject, "haveDep", false)); + + referencedObject->setProperty("haveDep", true); + dependencies = binding->dependencies(); + QCOMPARE(dependencies.size(), 2); + QVERIFY(findProperties(dependencies, referencedObject, "haveDep", true)); + QVERIFY(findProperties(dependencies, referencedObject, "labelText", "Hello world!")); + + referencedObject->setProperty("haveDep", false); + dependencies = binding->dependencies(); + QCOMPARE(dependencies.size(), 1); + QVERIFY(findProperties(dependencies, referencedObject, "haveDep", false)); + + delete rect; +} + +void tst_bindingdependencyapi::testBindingLoop() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(QByteArray("import QtQuick 2.0\n" + "Rectangle {\n" + "property string labelText: label.text\n" + "Text {\n" + "id: label\n" + "text: labelText\n" + "}\n" + "}"), QUrl()); + QObject *rect = c.create(); + if (c.isError()) { + qWarning() << c.errorString(); + } + QTest::qWait(100); + QVERIFY(rect != 0); + QObject *text = rect->findChildren().front(); + + auto data = QQmlData::get(text); + QVERIFY(data); + auto b = data->bindings; + QVERIFY(b); + auto binding = dynamic_cast(b); + QVERIFY(binding); + auto dependencies = binding->dependencies(); + QCOMPARE(dependencies.size(), 1); + auto dependency = dependencies.front(); + QVERIFY(dependency.isValid()); + QCOMPARE(quintptr(dependency.object()), quintptr(rect)); + QCOMPARE(dependency.property().name(), "labelText"); + + delete rect; +} + +QTEST_MAIN(tst_bindingdependencyapi) + +#include "tst_bindingdependencyapi.moc" diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 12a8bd3829..69af3cd13b 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -72,7 +72,8 @@ PRIVATETESTS += \ qqmlimport \ qqmlobjectmodel \ qv4mm \ - ecmascripttests + ecmascripttests \ + bindingdependencyapi } qtHaveModule(widgets) { -- cgit v1.2.3