blob: 261040245507e3a63cf88a1aa64d462c421b1daf [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/qqmlbind_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include "../../shared/util.h"
class tst_qqmlbinding : public QQmlDataTest
{
Q_OBJECT
public:
tst_qqmlbinding();
private slots:
void binding();
void whenAfterValue();
void restoreBinding();
void restoreBindingValue();
void restoreBindingVarValue();
void restoreBindingJSValue();
void restoreBindingWithLoop();
void restoreBindingWithoutCrash();
void deletedObject();
void warningOnUnknownProperty();
void warningOnReadOnlyProperty();
void disabledOnUnknownProperty();
void disabledOnReadonlyProperty();
void delayed();
void bindingOverwriting();
void bindToQmlComponent();
void bindingDoesNoWeirdConversion();
void bindNaNToInt();
private:
QQmlEngine engine;
};
tst_qqmlbinding::tst_qqmlbinding()
{
}
void tst_qqmlbinding::binding()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("test-binding.qml"));
QScopedPointer<QQuickRectangle> rect { qobject_cast<QQuickRectangle*>(c.create()) };
QVERIFY(rect != nullptr);
QQmlBind *binding3 = qobject_cast<QQmlBind*>(rect->findChild<QQmlBind*>("binding3"));
QVERIFY(binding3 != nullptr);
QCOMPARE(rect->color(), QColor("yellow"));
QCOMPARE(rect->property("text").toString(), QString("Hello"));
QCOMPARE(binding3->when(), false);
rect->setProperty("changeColor", true);
QCOMPARE(rect->color(), QColor("red"));
QCOMPARE(binding3->when(), true);
QQmlBind *binding = qobject_cast<QQmlBind*>(rect->findChild<QQmlBind*>("binding1"));
QVERIFY(binding != nullptr);
QCOMPARE(binding->object(), qobject_cast<QObject*>(rect.get()));
QCOMPARE(binding->property(), QLatin1String("text"));
QCOMPARE(binding->value().toString(), QLatin1String("Hello"));
}
void tst_qqmlbinding::whenAfterValue()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("test-binding2.qml"));
QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(c.create())};
QVERIFY(rect != nullptr);
QCOMPARE(rect->color(), QColor("yellow"));
QCOMPARE(rect->property("text").toString(), QString("Hello"));
rect->setProperty("changeColor", true);
QCOMPARE(rect->color(), QColor("red"));
}
void tst_qqmlbinding::restoreBinding()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBinding.qml"));
QScopedPointer<QQuickRectangle> rect { qobject_cast<QQuickRectangle*>(c.create()) };
QVERIFY(rect != nullptr);
QQuickRectangle *myItem = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("myItem"));
QVERIFY(myItem != nullptr);
myItem->setY(25);
QCOMPARE(myItem->x(), qreal(100-25));
myItem->setY(13);
QCOMPARE(myItem->x(), qreal(100-13));
//Binding takes effect
myItem->setY(51);
QCOMPARE(myItem->x(), qreal(51));
myItem->setY(88);
QCOMPARE(myItem->x(), qreal(88));
//original binding restored
myItem->setY(49);
QCOMPARE(myItem->x(), qreal(100-49));
}
void tst_qqmlbinding::restoreBindingValue()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBinding2.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(!rect.isNull());
auto myItem = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("myItem"));
QVERIFY(myItem != nullptr);
QCOMPARE(myItem->height(), 100);
myItem->setProperty("when", QVariant(false));
QCOMPARE(myItem->height(), 300); // make sure the original value was restored
myItem->setProperty("when", QVariant(true));
QCOMPARE(myItem->height(), 100); // make sure the value specified in Binding is set
rect->setProperty("boundValue", 200);
QCOMPARE(myItem->height(), 200); // make sure the changed binding value is set
myItem->setProperty("when", QVariant(false));
// make sure that the original value is back, not e.g. the value from before the
// change (i.e. 100)
QCOMPARE(myItem->height(), 300);
}
void tst_qqmlbinding::restoreBindingVarValue()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBinding3.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(!rect.isNull());
auto myItem = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("myItem"));
QVERIFY(myItem != nullptr);
QCOMPARE(myItem->property("foo"), 13);
myItem->setProperty("when", QVariant(false));
QCOMPARE(myItem->property("foo"), 42); // make sure the original value was restored
myItem->setProperty("when", QVariant(true));
QCOMPARE(myItem->property("foo"), 13); // make sure the value specified in Binding is set
rect->setProperty("boundValue", 31337);
QCOMPARE(myItem->property("foo"), 31337); // make sure the changed binding value is set
myItem->setProperty("when", QVariant(false));
// make sure that the original value is back, not e.g. the value from before the
// change (i.e. 100)
QCOMPARE(myItem->property("foo"), 42);
}
void tst_qqmlbinding::restoreBindingJSValue()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBinding4.qml"));
QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(c.create()));
QVERIFY(!rect.isNull());
auto myItem = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("myItem"));
QVERIFY(myItem != nullptr);
QCOMPARE(myItem->property("fooCheck"), false);
myItem->setProperty("when", QVariant(false));
QCOMPARE(myItem->property("fooCheck"), true); // make sure the original value was restored
myItem->setProperty("when", QVariant(true));
QCOMPARE(myItem->property("fooCheck"), false); // make sure the value specified in Binding is set
rect->setProperty("boundValue", 31337);
QCOMPARE(myItem->property("fooCheck"), false); // make sure the changed binding value is set
myItem->setProperty("when", QVariant(false));
// make sure that the original value is back, not e.g. the value from before the change
QCOMPARE(myItem->property("fooCheck"), true);
}
void tst_qqmlbinding::restoreBindingWithLoop()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBindingWithLoop.qml"));
QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(c.create())};
QVERIFY(rect != nullptr);
QQuickRectangle *myItem = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("myItem"));
QVERIFY(myItem != nullptr);
myItem->setY(25);
QCOMPARE(myItem->x(), qreal(25 + 100));
myItem->setY(13);
QCOMPARE(myItem->x(), qreal(13 + 100));
//Binding takes effect
rect->setProperty("activateBinding", true);
myItem->setY(51);
QCOMPARE(myItem->x(), qreal(51));
myItem->setY(88);
QCOMPARE(myItem->x(), qreal(88));
//original binding restored
QString warning = c.url().toString() + QLatin1String(":9:5: QML Rectangle: Binding loop detected for property \"x\"");
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
rect->setProperty("activateBinding", false);
QCOMPARE(myItem->x(), qreal(88 + 100)); //if loop handling changes this could be 90 + 100
myItem->setY(49);
QCOMPARE(myItem->x(), qreal(49 + 100));
}
void tst_qqmlbinding::restoreBindingWithoutCrash()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("restoreBindingWithoutCrash.qml"));
QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(c.create())};
QVERIFY(rect != nullptr);
QQuickRectangle *myItem = qobject_cast<QQuickRectangle*>(rect->findChild<QQuickRectangle*>("myItem"));
QVERIFY(myItem != nullptr);
myItem->setY(25);
QCOMPARE(myItem->x(), qreal(100-25));
myItem->setY(13);
QCOMPARE(myItem->x(), qreal(100-13));
//Binding takes effect
myItem->setY(51);
QCOMPARE(myItem->x(), qreal(51));
myItem->setY(88);
QCOMPARE(myItem->x(), qreal(88));
//state sets a new binding
rect->setState("state1");
//this binding temporarily takes effect. We may want to change this behavior in the future
QCOMPARE(myItem->x(), qreal(112));
//Binding still controls this value
myItem->setY(104);
QCOMPARE(myItem->x(), qreal(104));
//original binding restored
myItem->setY(49);
QCOMPARE(myItem->x(), qreal(100-49));
}
//QTBUG-20692
void tst_qqmlbinding::deletedObject()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("deletedObject.qml"));
QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(c.create())};
QVERIFY(rect != nullptr);
QGuiApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
//don't crash
rect->setProperty("activateBinding", true);
}
void tst_qqmlbinding::warningOnUnknownProperty()
{
QQmlTestMessageHandler messageHandler;
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("unknownProperty.qml"));
QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(c.create()) };
QVERIFY(item);
QCOMPARE(messageHandler.messages().count(), 1);
const QString expectedMessage = c.url().toString() + QLatin1String(":6:5: QML Binding: Property 'unknown' does not exist on Item.");
QCOMPARE(messageHandler.messages().first(), expectedMessage);
}
void tst_qqmlbinding::warningOnReadOnlyProperty()
{
QQmlTestMessageHandler messageHandler;
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("readonlyProperty.qml"));
QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(c.create()) };
QVERIFY(item);
QCOMPARE(messageHandler.messages().count(), 1);
const QString expectedMessage = c.url().toString() + QLatin1String(":8:5: QML Binding: Property 'name' on Item is read-only.");
QCOMPARE(messageHandler.messages().first(), expectedMessage);
}
void tst_qqmlbinding::disabledOnUnknownProperty()
{
QQmlTestMessageHandler messageHandler;
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("disabledUnknown.qml"));
QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(c.create()) };
QVERIFY(item);
QCOMPARE(messageHandler.messages().count(), 0);
}
void tst_qqmlbinding::disabledOnReadonlyProperty()
{
QQmlTestMessageHandler messageHandler;
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("disabledReadonly.qml"));
QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(c.create()) };
QVERIFY(item);
QCOMPARE(messageHandler.messages().count(), 0);
}
void tst_qqmlbinding::delayed()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("delayed.qml"));
QScopedPointer<QQuickItem> item {qobject_cast<QQuickItem*>(c.create())};
QVERIFY(item != nullptr);
// update on creation
QCOMPARE(item->property("changeCount").toInt(), 1);
QMetaObject::invokeMethod(item.get(), "updateText");
// doesn't update immediately
QCOMPARE(item->property("changeCount").toInt(), 1);
QCoreApplication::processEvents();
// only updates once (non-delayed would update twice)
QCOMPARE(item->property("changeCount").toInt(), 2);
}
void tst_qqmlbinding::bindingOverwriting()
{
QQmlTestMessageHandler messageHandler;
QLoggingCategory::setFilterRules(QStringLiteral("qt.qml.binding.removal.info=true"));
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("bindingOverwriting.qml"));
QScopedPointer<QQuickItem> item {qobject_cast<QQuickItem*>(c.create())};
QVERIFY(item);
QLoggingCategory::setFilterRules(QString());
QCOMPARE(messageHandler.messages().count(), 2);
}
void tst_qqmlbinding::bindToQmlComponent()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("bindToQMLComponent.qml"));
QVERIFY(c.create());
}
// QTBUG-78943
void tst_qqmlbinding::bindingDoesNoWeirdConversion()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("noUnexpectedStringConversion.qml"));
QScopedPointer<QObject> o {c.create()};
QVERIFY(o);
QObject *colorRect = o->findChild<QObject*>("colorRect");
QVERIFY(colorRect);
QCOMPARE(qvariant_cast<QColor>(colorRect->property("color")), QColorConstants::Red);
QObject *colorLabel = o->findChild<QObject*>("colorLabel");
QCOMPARE(colorLabel->property("text").toString(), QLatin1String("red"));
QVERIFY(colorLabel);
}
//QTBUG-72442
void tst_qqmlbinding::bindNaNToInt()
{
QQmlEngine engine;
QQmlComponent c(&engine, testFileUrl("nanPropertyToInt.qml"));
QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(c.create()));
QVERIFY(item != nullptr);
QCOMPARE(item->property("val").toInt(), 0);
}
QTEST_MAIN(tst_qqmlbinding)
#include "tst_qqmlbinding.moc"