blob: 82c997a5b8ebf99221ce31be5e9cc6b239f45333 [file] [log] [blame]
/****************************************************************************
**
** 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 <qtest.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <private/qqmldata_p.h>
#include <private/qqmlbinding_p.h>
#include <QtQuick/private/qquicktext_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include "../../shared/util.h"
#include <qqmlcontext.h>
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<QQmlProperty> &properties, QObject *obj, const QString &propertyName, const QVariant &value);
};
tst_bindingdependencyapi::tst_bindingdependencyapi()
{
}
void tst_bindingdependencyapi::testSingleDep_data()
{
QTest::addColumn<QByteArray>("code");
QTest::addColumn<QString>("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 != nullptr);
QObject *text = rect->findChildren<QQuickText *>().front();
QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild<QObject *>(referencedObjectName);
auto data = QQmlData::get(text);
QVERIFY(data);
auto b = data->bindings;
QVERIFY(b);
auto binding = dynamic_cast<QQmlBinding*>(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<QQmlProperty> &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<QByteArray>("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 != nullptr);
QObject *text = rect->findChildren<QQuickText *>().front();
QObject *configObj = rect->findChild<QObject *>("config");
auto data = QQmlData::get(text);
QVERIFY(data);
auto b = data->bindings;
QVERIFY(b);
auto binding = dynamic_cast<QQmlBinding*>(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<QByteArray>("code");
QTest::addColumn<QString>("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 != nullptr);
QObject *text = rect->findChildren<QQuickText *>().front();
QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild<QObject *>(referencedObjectName);
auto data = QQmlData::get(text);
QVERIFY(data);
auto b = data->bindings;
QVERIFY(b);
auto binding = dynamic_cast<QQmlBinding*>(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 != nullptr);
QObject *text = rect->findChildren<QQuickText *>().front();
auto data = QQmlData::get(text);
QVERIFY(data);
auto b = data->bindings;
QVERIFY(b);
auto binding = dynamic_cast<QQmlBinding*>(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"