/****************************************************************************
**
** 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 "interfaces.h"
#include <qtest.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlproperty.h>
#include <QtQml/private/qqmlproperty_p.h>
#include <private/qqmlbinding_p.h>
#include <private/qqmlboundsignal_p.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#if QT_CONFIG(regularexpression)
#include <QtCore/qregularexpression.h>
#endif
#include <QtCore/private/qobject_p.h>
#include "../../shared/util.h"
#include "qobject.h"
#include <QtQml/QQmlPropertyMap>

#include <QDebug>
class MyQmlObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QPoint pointProperty MEMBER m_point)
public:
    MyQmlObject(QObject *parent = nullptr) : QObject(parent) {}

private:
    QPoint m_point;
};

QML_DECLARE_TYPE(MyQmlObject);

class MyQObject : public QObject
{
    Q_OBJECT
public:
    MyQObject(QObject *parent = nullptr) : QObject(parent), m_i(0) {}

    int inc() { return ++m_i; }

private:
  int m_i;
};


class MyAttached : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int foo READ foo WRITE setFoo)
public:
    MyAttached(QObject *parent) : QObject(parent), m_foo(13) {}

    int foo() const { return m_foo; }
    void setFoo(int f) { m_foo = f; }

private:
    int m_foo;
};

class MyContainer : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<MyQmlObject> children READ children)
public:
    MyContainer() {}

    QQmlListProperty<MyQmlObject> children() { return QQmlListProperty<MyQmlObject>(this, m_children); }

    static MyAttached *qmlAttachedProperties(QObject *o) {
        return new MyAttached(o);
    }

private:
    QList<MyQmlObject*> m_children;
};

QML_DECLARE_TYPE(MyContainer);
QML_DECLARE_TYPEINFO(MyContainer, QML_HAS_ATTACHED_PROPERTIES)

class tst_qqmlproperty : public QQmlDataTest
{
    Q_OBJECT
public:
    tst_qqmlproperty() {}

private slots:
    void initTestCase();

    // Constructors
    void qmlmetaproperty();
    void qmlmetaproperty_object();
    void qmlmetaproperty_object_string();
    void qmlmetaproperty_object_context();
    void qmlmetaproperty_object_string_context();

    // Methods
    void name();
    void read();
    void write();
    void reset();

    // Functionality
    void writeObjectToList();
    void writeListToList();

    //writeToReadOnly();

    void urlHandling_data();
    void urlHandling();

    void variantMapHandling_data();
    void variantMapHandling();

    // Bugs
    void crashOnValueProperty();
    void aliasPropertyBindings_data();
    void aliasPropertyBindings();
    void noContext();
    void assignEmptyVariantMap();
    void warnOnInvalidBinding();
    void registeredCompositeTypeProperty();
    void deeplyNestedObject();
    void readOnlyDynamicProperties();
    void aliasToIdWithMatchingQmlFileNameOnCaseInsensitiveFileSystem();
    void nullPropertyBinding();
    void interfaceBinding();

    void floatToStringPrecision_data();
    void floatToStringPrecision();

    void copy();

    void bindingToAlias();

    void nestedQQmlPropertyMap();

    void underscorePropertyChangeHandler();

    void signalExpressionWithoutObject();
private:
    QQmlEngine engine;
};

void tst_qqmlproperty::qmlmetaproperty()
{
    QQmlProperty prop;

    QObject *obj = new QObject;

    QQmlAbstractBinding::Ptr binding(QQmlBinding::create(nullptr, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
    QVERIFY(binding);
    QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(obj, QObjectPrivate::get(obj)->signalIndex("destroyed()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
    QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
    QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

    QCOMPARE(prop.name(), QString());
    QCOMPARE(prop.read(), QVariant());
    QCOMPARE(prop.write(QVariant()), false);
    QCOMPARE(prop.hasNotifySignal(), false);
    QCOMPARE(prop.needsNotifySignal(), false);
    QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
    QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
    QCOMPARE(prop.connectNotifySignal(obj, 0), false);
    QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
    QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
    QCOMPARE(prop.connectNotifySignal(obj, -1), false);
    QVERIFY(!prop.method().isValid());
    QCOMPARE(prop.type(), QQmlProperty::Invalid);
    QCOMPARE(prop.isProperty(), false);
    QCOMPARE(prop.isWritable(), false);
    QCOMPARE(prop.isDesignable(), false);
    QCOMPARE(prop.isResettable(), false);
    QCOMPARE(prop.isSignalProperty(), false);
    QCOMPARE(prop.isValid(), false);
    QCOMPARE(prop.object(), (QObject *)nullptr);
    QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
    QCOMPARE(prop.propertyType(), 0);
    QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
    QVERIFY(!prop.property().name());
    QVERIFY(!QQmlPropertyPrivate::binding(prop));
    QQmlPropertyPrivate::setBinding(prop, binding.data());
    QVERIFY(binding->ref == 1);
    QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
    QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
    QVERIFY(sigExprWatcher.wasDeleted());
    QCOMPARE(prop.index(), -1);
    QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
    QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

    delete obj;
}

// 1 = equal, 0 = unknown, -1 = not equal.
static int compareVariantAndListReference(const QVariant &v, QQmlListReference &r)
{
    if (QLatin1String(v.typeName()) != QLatin1String("QQmlListReference"))
        return -1;

    QQmlListReference lhs = v.value<QQmlListReference>();
    if (lhs.isValid() != r.isValid())
        return -1;

    if (lhs.canCount() != r.canCount())
        return -1;

    if (!lhs.canCount()) {
        if (lhs.canAt() != r.canAt())
            return -1; // not equal.
        return 0; // not sure if they're equal or not, and no way to tell.
    }

    // if we get here, we must be able to count.
    if (lhs.count() != r.count())
        return -1;

    if (lhs.canAt() != r.canAt())
        return -1;

    if (!lhs.canAt())
        return 0; // can count, but can't check element equality.

    for (int i = 0; i < lhs.count(); ++i) {
        if (lhs.at(i) != r.at(i)) {
            return -1; // different elements :. not equal.
        }
    }

    return 1; // equal.
}

void tst_qqmlproperty::registeredCompositeTypeProperty()
{
    // Composite type properties
    {
        QQmlEngine engine;
        QQmlComponent component(&engine, testFileUrl("registeredCompositeTypeProperty.qml"));
        QObject *obj = component.create();
        QVERIFY(obj);

        // create property accessors and check types.
        QQmlProperty p1(obj, "first");
        QQmlProperty p2(obj, "second");
        QQmlProperty p3(obj, "third");
        QQmlProperty p1e(obj, "first", &engine);
        QQmlProperty p2e(obj, "second", &engine);
        QQmlProperty p3e(obj, "third", &engine);
        QCOMPARE(p1.propertyType(), p2.propertyType());
        QVERIFY(p1.propertyType() != p3.propertyType());

        // check that the values are retrievable from CPP
        QVariant first = obj->property("first");
        QVariant second = obj->property("second");
        QVariant third = obj->property("third");
        QVERIFY(first.isValid());
        QVERIFY(second.isValid());
        QVERIFY(third.isValid());
        // ensure that conversion from qobject-derived-ptr to qobject-ptr works.
        QVERIFY(first.value<QObject*>());
        QVERIFY(second.value<QObject*>());
        QVERIFY(third.value<QObject*>());

        // check that the values retrieved via QQmlProperty match those retrieved via QMetaProperty::read().
        QCOMPARE(p1.read().value<QObject*>(), first.value<QObject*>());
        QCOMPARE(p2.read().value<QObject*>(), second.value<QObject*>());
        QCOMPARE(p3.read().value<QObject*>(), third.value<QObject*>());
        QCOMPARE(p1e.read().value<QObject*>(), first.value<QObject*>());
        QCOMPARE(p2e.read().value<QObject*>(), second.value<QObject*>());
        QCOMPARE(p3e.read().value<QObject*>(), third.value<QObject*>());

        delete obj;
    }

    // List-of-composite-type type properties
    {
        QQmlEngine engine;
        QQmlComponent component(&engine, testFileUrl("registeredCompositeTypeProperty.qml"));
        QObject *obj = component.create();
        QVERIFY(obj);

        // create list property accessors and check types
        QQmlProperty lp1e(obj, "fclist", &engine);
        QQmlProperty lp2e(obj, "sclistOne", &engine);
        QQmlProperty lp3e(obj, "sclistTwo", &engine);
        QVERIFY(lp1e.propertyType() != lp2e.propertyType());
        QCOMPARE(lp2e.propertyType(), lp3e.propertyType());

        // check that the list values are retrievable from CPP
        QVariant firstList = obj->property("fclist");
        QVariant secondList = obj->property("sclistOne");
        QVariant thirdList = obj->property("sclistTwo");
        QVERIFY(firstList.isValid());
        QVERIFY(secondList.isValid());
        QVERIFY(thirdList.isValid());

        // check that the value returned by QQmlProperty::read() is equivalent to the list reference.
        QQmlListReference r1(obj, "fclist", &engine);
        QQmlListReference r2(obj, "sclistOne", &engine);
        QQmlListReference r3(obj, "sclistTwo", &engine);
        QCOMPARE(compareVariantAndListReference(lp1e.read(), r1), 1);
        QCOMPARE(compareVariantAndListReference(lp2e.read(), r2), 1);
        QCOMPARE(compareVariantAndListReference(lp3e.read(), r3), 1);

        delete obj;
    }
}

class PropertyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int defaultProperty READ defaultProperty)
    Q_PROPERTY(QRect rectProperty READ rectProperty)
    Q_PROPERTY(QRect wrectProperty READ wrectProperty WRITE setWRectProperty)
    Q_PROPERTY(QUrl url READ url WRITE setUrl)
    Q_PROPERTY(QVariantMap variantMap READ variantMap WRITE setVariantMap)
    Q_PROPERTY(int resettableProperty READ resettableProperty WRITE setResettableProperty RESET resetProperty)
    Q_PROPERTY(int propertyWithNotify READ propertyWithNotify WRITE setPropertyWithNotify NOTIFY oddlyNamedNotifySignal)
    Q_PROPERTY(MyQmlObject *qmlObject READ qmlObject)
    Q_PROPERTY(MyQObject *qObject READ qObject WRITE setQObject NOTIFY qObjectChanged)
    Q_PROPERTY(QString stringProperty READ stringProperty WRITE setStringProperty)
    Q_PROPERTY(QChar qcharProperty READ qcharProperty WRITE setQcharProperty)
    Q_PROPERTY(QChar constQChar READ constQChar STORED false CONSTANT FINAL)

    Q_CLASSINFO("DefaultProperty", "defaultProperty")
public:
    PropertyObject() : m_resetProperty(9), m_qObject(nullptr), m_stringProperty("foo") {}

    int defaultProperty() { return 10; }
    QRect rectProperty() { return QRect(10, 10, 1, 209); }

    QRect wrectProperty() { return m_rect; }
    void setWRectProperty(const QRect &r) { m_rect = r; }

    QUrl url() { return m_url; }
    void setUrl(const QUrl &u) { m_url = u; }

    QVariantMap variantMap() const { return m_variantMap; }
    void setVariantMap(const QVariantMap &variantMap) { m_variantMap = variantMap; }

    int resettableProperty() const { return m_resetProperty; }
    void setResettableProperty(int r) { m_resetProperty = r; }
    void resetProperty() { m_resetProperty = 9; }

    int propertyWithNotify() const { return m_propertyWithNotify; }
    void setPropertyWithNotify(int i) { m_propertyWithNotify = i; emit oddlyNamedNotifySignal(); }

    MyQmlObject *qmlObject() { return &m_qmlObject; }

    MyQObject *qObject() { return m_qObject; }
    void setQObject(MyQObject *object)
    {
        if (m_qObject != object) {
            m_qObject = object;
            emit qObjectChanged();
        }
    }

    QString stringProperty() const { return m_stringProperty;}
    QChar qcharProperty() const { return m_qcharProperty; }

    QChar constQChar() const { return 0x25cf; /* Unicode: black circle */ }

    void setStringProperty(QString arg) { m_stringProperty = arg; }
    void setQcharProperty(QChar arg) { m_qcharProperty = arg; }

signals:
    void clicked();
    void oddlyNamedNotifySignal();
    void qObjectChanged();

private:
    int m_resetProperty;
    QRect m_rect;
    QUrl m_url;
    QVariantMap m_variantMap;
    int m_propertyWithNotify;
    MyQmlObject m_qmlObject;
    MyQObject *m_qObject;
    QString m_stringProperty;
    QChar m_qcharProperty;
};

QML_DECLARE_TYPE(PropertyObject);

void tst_qqmlproperty::qmlmetaproperty_object()
{
    QObject object; // Has no default property
    PropertyObject dobject; // Has default property

    {
        QQmlProperty prop(&object);

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&object, QObjectPrivate::get(&object)->signalIndex("destroyed()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString());
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Invalid);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), false);
        QCOMPARE(prop.object(), (QObject *)nullptr);
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QVERIFY(!prop.property().name());
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), -1);
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject);

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QObjectPrivate::get(&dobject)->signalIndex("clicked()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("defaultProperty"));
        QCOMPARE(prop.read(), QVariant(10));
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), true);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Property);
        QCOMPARE(prop.isProperty(), true);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), true);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::Normal);
        QCOMPARE(prop.propertyType(), (int)QVariant::Int);
        QCOMPARE(prop.propertyTypeName(), "int");
        QCOMPARE(QString(prop.property().name()), QString("defaultProperty"));
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Unable to assign null to int");
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding);
        QCOMPARE(QQmlPropertyPrivate::binding(prop), binding.data());
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfProperty("defaultProperty"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }
}

void tst_qqmlproperty::qmlmetaproperty_object_string()
{
    QObject object;
    PropertyObject dobject;

    {
        QQmlProperty prop(&object, QString("defaultProperty"));

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&object, QObjectPrivate::get(&object)->signalIndex("destroyed()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString());
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Invalid);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), false);
        QCOMPARE(prop.object(), (QObject *)nullptr);
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QVERIFY(!prop.property().name());
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), -1);
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, QString("defaultProperty"));

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QObjectPrivate::get(&dobject)->signalIndex("clicked()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("defaultProperty"));
        QCOMPARE(prop.read(), QVariant(10));
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), true);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Property);
        QCOMPARE(prop.isProperty(), true);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), true);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::Normal);
        QCOMPARE(prop.propertyType(), (int)QVariant::Int);
        QCOMPARE(prop.propertyTypeName(), "int");
        QCOMPARE(QString(prop.property().name()), QString("defaultProperty"));
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Unable to assign null to int");
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding);
        QCOMPARE(QQmlPropertyPrivate::binding(prop), binding.data());
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfProperty("defaultProperty"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, QString("onClicked"));

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QQmlPropertyPrivate::get(prop)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("onClicked"));
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant("Hello")), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QCOMPARE(QString(prop.method().methodSignature()), QString("clicked()"));
        QCOMPARE(prop.type(), QQmlProperty::SignalProperty);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), true);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QCOMPARE(prop.property().name(), (const char *)nullptr);
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(!sigExprWatcher.wasDeleted());
        QCOMPARE(QQmlPropertyPrivate::signalExpression(prop), sigExpr);
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfMethod("clicked()"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, QString("onPropertyWithNotifyChanged"));

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QQmlPropertyPrivate::get(prop)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("onOddlyNamedNotifySignal"));
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant("Hello")), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QCOMPARE(QString(prop.method().methodSignature()), QString("oddlyNamedNotifySignal()"));
        QCOMPARE(prop.type(), QQmlProperty::SignalProperty);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), true);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QCOMPARE(prop.property().name(), (const char *)nullptr);
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(!sigExprWatcher.wasDeleted());
        QCOMPARE(QQmlPropertyPrivate::signalExpression(prop), sigExpr);
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfMethod("oddlyNamedNotifySignal()"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }
}

void tst_qqmlproperty::qmlmetaproperty_object_context()
{
    QObject object; // Has no default property
    PropertyObject dobject; // Has default property

    {
        QQmlProperty prop(&object, engine.rootContext());

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&object, QObjectPrivate::get(&object)->signalIndex("destroyed()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString());
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Invalid);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), false);
        QCOMPARE(prop.object(), (QObject *)nullptr);
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QVERIFY(!prop.property().name());
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), -1);
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, engine.rootContext());

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QObjectPrivate::get(&dobject)->signalIndex("clicked()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("defaultProperty"));
        QCOMPARE(prop.read(), QVariant(10));
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), true);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Property);
        QCOMPARE(prop.isProperty(), true);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), true);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::Normal);
        QCOMPARE(prop.propertyType(), (int)QVariant::Int);
        QCOMPARE(prop.propertyTypeName(), "int");
        QCOMPARE(QString(prop.property().name()), QString("defaultProperty"));
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Unable to assign null to int");
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding);
        QCOMPARE(QQmlPropertyPrivate::binding(prop), binding.data());
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfProperty("defaultProperty"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }
}

void tst_qqmlproperty::qmlmetaproperty_object_string_context()
{
    QObject object;
    PropertyObject dobject;

    {
        QQmlProperty prop(&object, QString("defaultProperty"), engine.rootContext());

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&object, QObjectPrivate::get(&object)->signalIndex("destroyed()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString());
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Invalid);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), false);
        QCOMPARE(prop.object(), (QObject *)nullptr);
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QVERIFY(!prop.property().name());
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), -1);
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, QString("defaultProperty"), engine.rootContext());

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QObjectPrivate::get(&dobject)->signalIndex("clicked()"), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("defaultProperty"));
        QCOMPARE(prop.read(), QVariant(10));
        QCOMPARE(prop.write(QVariant()), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), true);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QVERIFY(!prop.method().isValid());
        QCOMPARE(prop.type(), QQmlProperty::Property);
        QCOMPARE(prop.isProperty(), true);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), true);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), false);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::Normal);
        QCOMPARE(prop.propertyType(), (int)QVariant::Int);
        QCOMPARE(prop.propertyTypeName(), "int");
        QCOMPARE(QString(prop.property().name()), QString("defaultProperty"));
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Unable to assign null to int");
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding);
        QCOMPARE(QQmlPropertyPrivate::binding(prop), binding.data());
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(sigExprWatcher.wasDeleted());
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfProperty("defaultProperty"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, QString("onClicked"), engine.rootContext());

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QQmlPropertyPrivate::get(prop)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("onClicked"));
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant("Hello")), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QCOMPARE(QString(prop.method().methodSignature()), QString("clicked()"));
        QCOMPARE(prop.type(), QQmlProperty::SignalProperty);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), true);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QCOMPARE(prop.property().name(), (const char *)nullptr);
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(!sigExprWatcher.wasDeleted());
        QCOMPARE(QQmlPropertyPrivate::signalExpression(prop), sigExpr);
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfMethod("clicked()"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }

    {
        QQmlProperty prop(&dobject, QString("onPropertyWithNotifyChanged"), engine.rootContext());

        QQmlAbstractBinding::Ptr binding(QQmlBinding::create(&QQmlPropertyPrivate::get(prop)->core, QLatin1String("null"), nullptr, QQmlContextData::get(engine.rootContext())));
        static_cast<QQmlBinding *>(binding.data())->setTarget(prop);
        QVERIFY(binding);
        QQmlBoundSignalExpression *sigExpr = new QQmlBoundSignalExpression(&dobject, QQmlPropertyPrivate::get(prop)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1);
        QQmlJavaScriptExpression::DeleteWatcher sigExprWatcher(sigExpr);
        QVERIFY(sigExpr != nullptr && !sigExprWatcher.wasDeleted());

        QObject *obj = new QObject;

        QCOMPARE(prop.name(), QString("onOddlyNamedNotifySignal"));
        QCOMPARE(prop.read(), QVariant());
        QCOMPARE(prop.write(QVariant("Hello")), false);
        QCOMPARE(prop.hasNotifySignal(), false);
        QCOMPARE(prop.needsNotifySignal(), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, SLOT(deleteLater())), false);
        QCOMPARE(prop.connectNotifySignal(obj, 0), false);
        QCOMPARE(prop.connectNotifySignal(nullptr, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, obj->metaObject()->indexOfMethod("deleteLater()")), false);
        QCOMPARE(prop.connectNotifySignal(obj, -1), false);
        QCOMPARE(QString(prop.method().methodSignature()), QString("oddlyNamedNotifySignal()"));
        QCOMPARE(prop.type(), QQmlProperty::SignalProperty);
        QCOMPARE(prop.isProperty(), false);
        QCOMPARE(prop.isWritable(), false);
        QCOMPARE(prop.isDesignable(), false);
        QCOMPARE(prop.isResettable(), false);
        QCOMPARE(prop.isSignalProperty(), true);
        QCOMPARE(prop.isValid(), true);
        QCOMPARE(prop.object(), qobject_cast<QObject*>(&dobject));
        QCOMPARE(prop.propertyTypeCategory(), QQmlProperty::InvalidCategory);
        QCOMPARE(prop.propertyType(), 0);
        QCOMPARE(prop.propertyTypeName(), (const char *)nullptr);
        QCOMPARE(prop.property().name(), (const char *)nullptr);
        QVERIFY(!QQmlPropertyPrivate::binding(prop));
        QQmlPropertyPrivate::setBinding(prop, binding.data());
        QVERIFY(binding->ref == 1);
        QVERIFY(!QQmlPropertyPrivate::signalExpression(prop));
        QQmlPropertyPrivate::takeSignalExpression(prop, sigExpr);
        QVERIFY(!sigExprWatcher.wasDeleted());
        QCOMPARE(QQmlPropertyPrivate::signalExpression(prop), sigExpr);
        QCOMPARE(prop.index(), dobject.metaObject()->indexOfMethod("oddlyNamedNotifySignal()"));
        QCOMPARE(QQmlPropertyPrivate::propertyIndex(prop).valueTypeIndex(), -1);
        QVERIFY(!QQmlPropertyPrivate::propertyIndex(prop).hasValueTypeIndex());

        delete obj;
    }
}

void tst_qqmlproperty::name()
{
    {
        QQmlProperty p;
        QCOMPARE(p.name(), QString());
    }

    {
        PropertyObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.name(), QString("defaultProperty"));
    }

    {
        QObject o;
        QQmlProperty p(&o, QString("objectName"));
        QCOMPARE(p.name(), QString("objectName"));
    }

    {
        PropertyObject o;
        QQmlProperty p(&o, "onClicked");
        QCOMPARE(p.name(), QString("onClicked"));
    }

    {
        QObject o;
        QQmlProperty p(&o, "onClicked");
        QCOMPARE(p.name(), QString());
    }

    {
        PropertyObject o;
        QQmlProperty p(&o, "onPropertyWithNotifyChanged");
        QCOMPARE(p.name(), QString("onOddlyNamedNotifySignal"));
    }

    {
        QObject o;
        QQmlProperty p(&o, "onPropertyWithNotifyChanged");
        QCOMPARE(p.name(), QString());
    }

    {
        QObject o;
        QQmlProperty p(&o, "foo");
        QCOMPARE(p.name(), QString());
    }

    {
        QQmlProperty p(nullptr, "foo");
        QCOMPARE(p.name(), QString());
    }

    {
        PropertyObject o;
        QQmlProperty p(&o, "rectProperty");
        QCOMPARE(p.name(), QString("rectProperty"));
    }

    {
        PropertyObject o;
        QQmlProperty p(&o, "rectProperty.x");
        QCOMPARE(p.name(), QString("rectProperty.x"));
    }

    {
        PropertyObject o;
        QQmlProperty p(&o, "rectProperty.foo");
        QCOMPARE(p.name(), QString());
    }
}

void tst_qqmlproperty::read()
{
    // Invalid
    {
        QQmlProperty p;
        QCOMPARE(p.read(), QVariant());
    }

    // Default prop
    {
        PropertyObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.read(), QVariant(10));
    }

    // Invalid default prop
    {
        QObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.read(), QVariant());
    }

    // Value prop by name
    {
        QObject o;

        QQmlProperty p(&o, "objectName");
        QCOMPARE(p.read(), QVariant(QString()));

        o.setObjectName("myName");

        QCOMPARE(p.read(), QVariant("myName"));
    }

    // Value prop by name (static)
    {
        QObject o;

        QCOMPARE(QQmlProperty::read(&o, "objectName"), QVariant(QString()));

        o.setObjectName("myName");

        QCOMPARE(QQmlProperty::read(&o, "objectName"), QVariant("myName"));
    }

    // Value-type prop
    {
        PropertyObject o;
        QQmlProperty p(&o, "rectProperty.x");
        QCOMPARE(p.read(), QVariant(10));
    }

    // Invalid value-type prop
    {
        PropertyObject o;
        QQmlProperty p(&o, "rectProperty.foo");
        QCOMPARE(p.read(), QVariant());
    }

    // Signal property
    {
        PropertyObject o;
        QQmlProperty p(&o, "onClicked");
        QCOMPARE(p.read(), QVariant());

        QQmlPropertyPrivate::takeSignalExpression(p, new QQmlBoundSignalExpression(&o, QQmlPropertyPrivate::get(p)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1));
        QVERIFY(nullptr != QQmlPropertyPrivate::signalExpression(p));

        QCOMPARE(p.read(), QVariant());
    }

    // Automatic signal property
    {
        PropertyObject o;
        QQmlProperty p(&o, "onPropertyWithNotifyChanged");
        QCOMPARE(p.read(), QVariant());

        QQmlPropertyPrivate::takeSignalExpression(p, new QQmlBoundSignalExpression(&o, QQmlPropertyPrivate::get(p)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1));
        QVERIFY(nullptr != QQmlPropertyPrivate::signalExpression(p));

        QCOMPARE(p.read(), QVariant());
    }

    // Deleted object
    {
        PropertyObject *o = new PropertyObject;
        QQmlProperty p(o, "rectProperty.x");
        QCOMPARE(p.read(), QVariant(10));
        delete o;
        QCOMPARE(p.read(), QVariant());
    }

    // Object property registered with Qt, but not registered with QML.
    {
        PropertyObject o;
        QQmlProperty p(&o, "qObject");
        QCOMPARE(p.propertyTypeCategory(), QQmlProperty::Object);

        QCOMPARE(p.propertyType(), qMetaTypeId<MyQObject*>());
        QVariant v = p.read();
        QVERIFY(v.canConvert(QMetaType::QObjectStar));
        QVERIFY(qvariant_cast<QObject *>(v) == o.qObject());
    }
    {
        QQmlEngine engine;
        PropertyObject o;
        QQmlProperty p(&o, "qObject", &engine);
        QCOMPARE(p.propertyTypeCategory(), QQmlProperty::Object);

        QCOMPARE(p.propertyType(), qMetaTypeId<MyQObject*>());
        QVariant v = p.read();
        QVERIFY(v.canConvert(QMetaType::QObjectStar));
        QVERIFY(qvariant_cast<QObject *>(v) == o.qObject());
    }

    // Object property
    {
        PropertyObject o;
        QQmlProperty p(&o, "qmlObject");
        QCOMPARE(p.propertyTypeCategory(), QQmlProperty::Object);
        QCOMPARE(p.propertyType(), qMetaTypeId<MyQmlObject*>());
        QVariant v = p.read();
        QCOMPARE(v.userType(), int(QMetaType::QObjectStar));
        QVERIFY(qvariant_cast<QObject *>(v) == o.qmlObject());
    }
    {
        QQmlComponent component(&engine, testFileUrl("readSynthesizedObject.qml"));
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);

        QQmlProperty p(object.data(), "test", &engine);

        QCOMPARE(p.propertyTypeCategory(), QQmlProperty::Object);
        QVERIFY(p.propertyType() != QMetaType::QObjectStar);

        QVariant v = p.read();
        QCOMPARE(v.userType(), int(QMetaType::QObjectStar));
        QCOMPARE(qvariant_cast<QObject *>(v)->property("a").toInt(), 10);
        QCOMPARE(qvariant_cast<QObject *>(v)->property("b").toInt(), 19);
    }
    {   // static
        QQmlComponent component(&engine, testFileUrl("readSynthesizedObject.qml"));
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);

        QVariant v = QQmlProperty::read(object.data(), "test", &engine);
        QCOMPARE(v.userType(), int(QMetaType::QObjectStar));
        QCOMPARE(qvariant_cast<QObject *>(v)->property("a").toInt(), 10);
        QCOMPARE(qvariant_cast<QObject *>(v)->property("b").toInt(), 19);
    }

    // Attached property
    {
        QQmlComponent component(&engine);
        component.setData("import Test 1.0\nMyContainer { }", QUrl());
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);

        QQmlProperty p(object.data(), "MyContainer.foo", qmlContext(object.data()));
        QCOMPARE(p.read(), QVariant(13));
    }
    {
        QQmlComponent component(&engine);
        component.setData("import Test 1.0\nMyContainer { MyContainer.foo: 10 }", QUrl());
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);

        QQmlProperty p(object.data(), "MyContainer.foo", qmlContext(object.data()));
        QCOMPARE(p.read(), QVariant(10));
    }
    {
        QQmlComponent component(&engine);
        component.setData("import Test 1.0 as Foo\nFoo.MyContainer { Foo.MyContainer.foo: 10 }", QUrl());
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);

        QQmlProperty p(object.data(), "Foo.MyContainer.foo", qmlContext(object.data()));
        QCOMPARE(p.read(), QVariant(10));
    }
    {   // static
        QQmlComponent component(&engine);
        component.setData("import Test 1.0 as Foo\nFoo.MyContainer { Foo.MyContainer.foo: 10 }", QUrl());
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(QQmlProperty::read(object.data(), "Foo.MyContainer.foo",
                                    qmlContext(object.data())), QVariant(10));
    }
}

void tst_qqmlproperty::write()
{
    // Invalid
    {
        QQmlProperty p;
        QCOMPARE(p.write(QVariant(10)), false);
    }

    // Read-only default prop
    {
        PropertyObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.write(QVariant(10)), false);
    }

    // Invalid default prop
    {
        QObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.write(QVariant(10)), false);
    }

    // Read-only prop by name
    {
        PropertyObject o;
        QQmlProperty p(&o, QString("defaultProperty"));
        QCOMPARE(p.write(QVariant(10)), false);
    }

    // Writable prop by name
    {
        PropertyObject o;
        QQmlProperty p(&o, QString("objectName"));
        QCOMPARE(o.objectName(), QString());
        QCOMPARE(p.write(QVariant(QString("myName"))), true);
        QCOMPARE(o.objectName(), QString("myName"));
    }

    // Writable prop by name (static)
    {
        PropertyObject o;
        QCOMPARE(QQmlProperty::write(&o, QString("objectName"), QVariant(QString("myName"))), true);
        QCOMPARE(o.objectName(), QString("myName"));
    }

    // Deleted object
    {
        PropertyObject *o = new PropertyObject;
        QQmlProperty p(o, QString("objectName"));
        QCOMPARE(p.write(QVariant(QString("myName"))), true);
        QCOMPARE(o->objectName(), QString("myName"));

        delete o;

        QCOMPARE(p.write(QVariant(QString("myName"))), false);
    }

    // Signal property
    {
        PropertyObject o;
        QQmlProperty p(&o, "onClicked");
        QCOMPARE(p.write(QVariant("console.log(1921)")), false);

        QQmlPropertyPrivate::takeSignalExpression(p, new QQmlBoundSignalExpression(&o, QQmlPropertyPrivate::get(p)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1));
        QVERIFY(nullptr != QQmlPropertyPrivate::signalExpression(p));

        QCOMPARE(p.write(QVariant("console.log(1921)")), false);

        QVERIFY(nullptr != QQmlPropertyPrivate::signalExpression(p));
    }

    // Automatic signal property
    {
        PropertyObject o;
        QQmlProperty p(&o, "onPropertyWithNotifyChanged");
        QCOMPARE(p.write(QVariant("console.log(1921)")), false);

        QQmlPropertyPrivate::takeSignalExpression(p, new QQmlBoundSignalExpression(&o, QQmlPropertyPrivate::get(p)->signalIndex(), QQmlContextData::get(engine.rootContext()), nullptr, QLatin1String("null"), QString(), -1, -1));
        QVERIFY(nullptr != QQmlPropertyPrivate::signalExpression(p));

        QCOMPARE(p.write(QVariant("console.log(1921)")), false);

        QVERIFY(nullptr != QQmlPropertyPrivate::signalExpression(p));
    }

    // Value-type property
    {
        PropertyObject o;
        QQmlProperty p(&o, "wrectProperty");

        QCOMPARE(o.wrectProperty(), QRect());
        QCOMPARE(p.write(QRect(1, 13, 99, 8)), true);
        QCOMPARE(o.wrectProperty(), QRect(1, 13, 99, 8));

        QQmlProperty p2(&o, "wrectProperty.x");
        QCOMPARE(p2.read(), QVariant(1));
        QCOMPARE(p2.write(QVariant(6)), true);
        QCOMPARE(p2.read(), QVariant(6));
        QCOMPARE(o.wrectProperty(), QRect(6, 13, 99, 8));
    }

    // URL-property
    {
        PropertyObject o;
        QQmlProperty p(&o, "url");

        QCOMPARE(p.write(QUrl("main.qml")), true);
        QCOMPARE(o.url(), QUrl("main.qml"));

        QQmlProperty p2(&o, "url", engine.rootContext());

        QUrl result = engine.baseUrl().resolved(QUrl("main.qml"));
        QVERIFY(result != QUrl("main.qml"));

        QCOMPARE(p2.write(QUrl("main.qml")), true);
        QCOMPARE(o.url(), result);
    }
    {   // static
        PropertyObject o;

        QCOMPARE(QQmlProperty::write(&o, "url", QUrl("main.qml")), true);
        QCOMPARE(o.url(), QUrl("main.qml"));

        QUrl result = engine.baseUrl().resolved(QUrl("main.qml"));
        QVERIFY(result != QUrl("main.qml"));

        QCOMPARE(QQmlProperty::write(&o, "url", QUrl("main.qml"), engine.rootContext()), true);
        QCOMPARE(o.url(), result);
    }

    // Char/string-property
    {
        PropertyObject o;
        QQmlProperty qcharProperty(&o, "qcharProperty");
        QQmlProperty stringProperty(&o, "stringProperty");

        const int black_circle = 0x25cf;

        QCOMPARE(qcharProperty.write(QString("foo")), false);
        QCOMPARE(qcharProperty.write('Q'), true);
        QCOMPARE(qcharProperty.read(), QVariant('Q'));
        QCOMPARE(qcharProperty.write(QChar(black_circle)), true);
        QCOMPARE(qcharProperty.read(), QVariant(QChar(black_circle)));

        QCOMPARE(o.stringProperty(), QString("foo")); // Default value
        QCOMPARE(stringProperty.write(QString("bar")), true);
        QCOMPARE(o.stringProperty(), QString("bar"));
        QCOMPARE(stringProperty.write(QVariant(1234)), true);
        QCOMPARE(stringProperty.read().toString(), QString::number(1234));
        QCOMPARE(stringProperty.write('A'), true);
        QCOMPARE(stringProperty.read().toString(), QString::number('A'));
        QCOMPARE(stringProperty.write(QChar(black_circle)), true);
        QCOMPARE(stringProperty.read(), QVariant(QChar(black_circle)));

        { // QChar -> QString
            QQmlComponent component(&engine);
            component.setData("import Test 1.0\nPropertyObject { stringProperty: constQChar }", QUrl());
            QScopedPointer<QObject> object(component.create());
            PropertyObject *propertyObject = qobject_cast<PropertyObject*>(object.data());
            QVERIFY(propertyObject != nullptr);
            if (propertyObject) {
                QQmlProperty stringProperty(propertyObject, "stringProperty");
                QCOMPARE(stringProperty.read(), QVariant(QString(propertyObject->constQChar())));
            }
        }

    }

    // VariantMap-property
    QVariantMap vm;
    vm.insert("key", "value");

    {
        PropertyObject o;
        QQmlProperty p(&o, "variantMap");

        QCOMPARE(p.write(vm), true);
        QCOMPARE(o.variantMap(), vm);

        QQmlProperty p2(&o, "variantMap", engine.rootContext());

        QCOMPARE(p2.write(vm), true);
        QCOMPARE(o.variantMap(), vm);
    }
    {   // static
        PropertyObject o;

        QCOMPARE(QQmlProperty::write(&o, "variantMap", vm), true);
        QCOMPARE(o.variantMap(), vm);

        QCOMPARE(QQmlProperty::write(&o, "variantMap", vm, engine.rootContext()), true);
        QCOMPARE(o.variantMap(), vm);
    }

    // Attached property
    {
        QQmlComponent component(&engine);
        component.setData("import Test 1.0\nMyContainer { }", QUrl());
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QQmlProperty p(object, "MyContainer.foo", qmlContext(object));
        p.write(QVariant(99));
        QCOMPARE(p.read(), QVariant(99));
        delete object;
    }
    {
        QQmlComponent component(&engine);
        component.setData("import Test 1.0 as Foo\nFoo.MyContainer { Foo.MyContainer.foo: 10 }", QUrl());
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QQmlProperty p(object, "Foo.MyContainer.foo", qmlContext(object));
        p.write(QVariant(99));
        QCOMPARE(p.read(), QVariant(99));
        delete object;
    }
    // Writable pointer to QObject derived
    {
        PropertyObject o;
        QQmlProperty p(&o, QString("qObject"));
        QCOMPARE(o.qObject(), (QObject*)nullptr);
        QObject *newObject = new MyQObject(this);
        QCOMPARE(p.write(QVariant::fromValue(newObject)), true);
        QCOMPARE(o.qObject(), newObject);
        QVariant data = p.read();
        QCOMPARE(data.value<QObject*>(), newObject);
        QCOMPARE(data.value<MyQObject*>(), newObject);
        // Incompatible types can not be written.
        QCOMPARE(p.write(QVariant::fromValue(new MyQmlObject(this))), false);
        QVariant newData = p.read();
        QCOMPARE(newData.value<QObject*>(), newObject);
        QCOMPARE(newData.value<MyQObject*>(), newObject);
    }
    {
        QQmlEngine engine;
        PropertyObject o;
        QQmlProperty p(&o, QString("qObject"), &engine);
        QCOMPARE(o.qObject(), (QObject*)nullptr);
        QObject *newObject = new MyQObject(this);
        QCOMPARE(p.write(QVariant::fromValue(newObject)), true);
        QCOMPARE(o.qObject(), newObject);
        QVariant data = p.read();
        QCOMPARE(data.value<QObject*>(), newObject);
        QCOMPARE(data.value<MyQObject*>(), newObject);
        // Incompatible types can not be written.
        QCOMPARE(p.write(QVariant::fromValue(new MyQmlObject(this))), false);
        QVariant newData = p.read();
        QCOMPARE(newData.value<QObject*>(), newObject);
        QCOMPARE(newData.value<MyQObject*>(), newObject);
    }
}

void tst_qqmlproperty::reset()
{
    // Invalid
    {
        QQmlProperty p;
        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }

    // Read-only default prop
    {
        PropertyObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }

    // Invalid default prop
    {
        QObject o;
        QQmlProperty p(&o);
        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }

    // Non-resettable-only prop by name
    {
        PropertyObject o;
        QQmlProperty p(&o, QString("defaultProperty"));
        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }

    // Resettable prop by name
    {
        PropertyObject o;
        QQmlProperty p(&o, QString("resettableProperty"));

        QCOMPARE(p.read(), QVariant(9));
        QCOMPARE(p.write(QVariant(11)), true);
        QCOMPARE(p.read(), QVariant(11));

        QCOMPARE(p.isResettable(), true);
        QCOMPARE(p.reset(), true);

        QCOMPARE(p.read(), QVariant(9));
    }

    // Deleted object
    {
        PropertyObject *o = new PropertyObject;

        QQmlProperty p(o, QString("resettableProperty"));

        QCOMPARE(p.isResettable(), true);
        QCOMPARE(p.reset(), true);

        delete o;

        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }

    // Signal property
    {
        PropertyObject o;
        QQmlProperty p(&o, "onClicked");

        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }

    // Automatic signal property
    {
        PropertyObject o;
        QQmlProperty p(&o, "onPropertyWithNotifyChanged");

        QCOMPARE(p.isResettable(), false);
        QCOMPARE(p.reset(), false);
    }
}

void tst_qqmlproperty::writeObjectToList()
{
    QQmlComponent containerComponent(&engine);
    containerComponent.setData("import Test 1.0\nMyContainer { children: MyQmlObject {} }", QUrl());
    QScopedPointer<QObject> object(containerComponent.create());
    MyContainer *container = qobject_cast<MyContainer*>(object.data());
    QVERIFY(container != nullptr);
    QQmlListReference list(container, "children");
    QCOMPARE(list.count(), 1);

    QScopedPointer<MyQmlObject> childObject(new MyQmlObject);
    QQmlProperty prop(container, "children");
    prop.write(QVariant::fromValue(childObject.data()));
    QCOMPARE(list.count(), 1);
    QCOMPARE(list.at(0), qobject_cast<QObject*>(childObject.data()));
}

void tst_qqmlproperty::writeListToList()
{
    QQmlComponent containerComponent(&engine);
    containerComponent.setData("import Test 1.0\nMyContainer { children: MyQmlObject {} }", QUrl());
    QScopedPointer<QObject> object(containerComponent.create());
    MyContainer *container = qobject_cast<MyContainer*>(object.data());
    QVERIFY(container != nullptr);
    QQmlListReference list(container, "children");
    QCOMPARE(list.count(), 1);

    QList<QObject*> objList;
    objList << new MyQmlObject(this) << new MyQmlObject(this)
            << new MyQmlObject(this) << new MyQmlObject(this);
    QQmlProperty prop(container, "children");
    prop.write(QVariant::fromValue(objList));
    QCOMPARE(list.count(), 4);

    //XXX need to try this with read/write prop (for read-only it correctly doesn't write)
    /*QList<MyQmlObject*> typedObjList;
    typedObjList << new MyQmlObject();
    prop.write(QVariant::fromValue(&typedObjList));
    QCOMPARE(container->children()->size(), 1);*/
}

void tst_qqmlproperty::urlHandling_data()
{
    QTest::addColumn<QByteArray>("input");
    QTest::addColumn<QString>("scheme");
    QTest::addColumn<QString>("path");
    QTest::addColumn<QByteArray>("encoded");

    QTest::newRow("unspecifiedFile")
        << QByteArray("main.qml")
        << QString("")
        << QString("main.qml")
        << QByteArray("main.qml");

    QTest::newRow("specifiedFile")
        << QByteArray("file:///main.qml")
        << QString("file")
        << QString("/main.qml")
        << QByteArray("file:///main.qml");

    QTest::newRow("httpFile")
        << QByteArray("http://www.example.com/main.qml")
        << QString("http")
        << QString("/main.qml")
        << QByteArray("http://www.example.com/main.qml");

    QTest::newRow("pathFile")
        << QByteArray("http://www.example.com/resources/main.qml")
        << QString("http")
        << QString("/resources/main.qml")
        << QByteArray("http://www.example.com/resources/main.qml");

    QTest::newRow("encodableName")
        << QByteArray("http://www.example.com/main file.qml")
        << QString("http")
        << QString("/main file.qml")
        << QByteArray("http://www.example.com/main%20file.qml");

    QTest::newRow("preencodedName")
        << QByteArray("http://www.example.com/resources%7Cmain%20file.qml")
        << QString("http")
        << QString("/resources|main file.qml")
        << QByteArray("http://www.example.com/resources%7Cmain%20file.qml");

    QTest::newRow("encodableQuery")
        << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now working?")
        << QString("http")
        << QString("/main.qml")
        << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now%20working?");

    // Although 'text%2Fqml' is pre-encoded, it will be decoded to allow correct QUrl classification
    QTest::newRow("preencodedQuery")
        << QByteArray("http://www.example.com/main.qml?type=text%2Fqml&comment=now working%3F")
        << QString("http")
        << QString("/main.qml")
        << QByteArray("http://www.example.com/main.qml?type=text/qml&comment=now%20working%3F");

    QTest::newRow("encodableFragment")
        << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000|volume+50%")
        << QString("http")
        << QString("/main.qml")
        << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000%7Cvolume+50%25");

    QTest::newRow("improperlyEncodedFragment")
        << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000%7Cvolume%2B50%")
        << QString("http")
        << QString("/main.qml")
        << QByteArray("http://www.example.com/main.qml?type=text/qml#start+30000%257Cvolume%252B50%25");
}

void tst_qqmlproperty::urlHandling()
{
    QFETCH(QByteArray, input);
    QFETCH(QString, scheme);
    QFETCH(QString, path);
    QFETCH(QByteArray, encoded);

    QString inputString(QString::fromUtf8(input));

    {
        PropertyObject o;
        QQmlProperty p(&o, "url");

        // Test url written as QByteArray
        QCOMPARE(p.write(input), true);
        QUrl byteArrayResult(o.url());

        QCOMPARE(byteArrayResult.scheme(), scheme);
        QCOMPARE(byteArrayResult.path(), path);
        QCOMPARE(byteArrayResult.toString(QUrl::FullyEncoded), QString::fromUtf8(encoded));
        QCOMPARE(byteArrayResult.toEncoded(), encoded);
    }

    {
        PropertyObject o;
        QQmlProperty p(&o, "url");

        // Test url written as QString
        QCOMPARE(p.write(inputString), true);
        QUrl stringResult(o.url());

        QCOMPARE(stringResult.scheme(), scheme);
        QCOMPARE(stringResult.path(), path);
        QCOMPARE(stringResult.toString(QUrl::FullyEncoded), QString::fromUtf8(encoded));
        QCOMPARE(stringResult.toEncoded(), encoded);
    }
}

void tst_qqmlproperty::variantMapHandling_data()
{
    QTest::addColumn<QVariantMap>("vm");

    // Object literals
    {
        QVariantMap m;
        QTest::newRow("{}") << m;
    }
    {
        QVariantMap m;
        m["a"] = QVariantMap();
        QTest::newRow("{ a:{} }") << m;
    }
    {
        QVariantMap m, m2;
        m2["b"] = 10;
        m2["c"] = 20;
        m["a"] = m2;
        QTest::newRow("{ a:{b:10, c:20} }") << m;
    }
    {
        QVariantMap m;
        m["a"] = 10;
        m["b"] = QVariantList() << 20 << 30;
        QTest::newRow("{ a:10, b:[20, 30]}") << m;
    }

    // Cyclic objects
    {
        QVariantMap m;
        m["p"] = QVariantMap();
        QTest::newRow("var o={}; o.p=o") << m;
    }
    {
        QVariantMap m;
        m["p"] = 123;
        m["q"] = QVariantMap();
        QTest::newRow("var o={}; o.p=123; o.q=o") << m;
    }
}

void tst_qqmlproperty::variantMapHandling()
{
    QFETCH(QVariantMap, vm);

    PropertyObject o;
    QQmlProperty p(&o, "variantMap");

    QCOMPARE(p.write(vm), true);
    QCOMPARE(o.variantMap(), vm);
}

void tst_qqmlproperty::crashOnValueProperty()
{
    QQmlEngine *engine = new QQmlEngine;
    QQmlComponent component(engine);

    component.setData("import Test 1.0\nPropertyObject { wrectProperty.x: 10 }", QUrl());
    QScopedPointer<QObject> object(component.create());
    PropertyObject *propertyObject = qobject_cast<PropertyObject*>(object.data());
    QVERIFY(propertyObject != nullptr);

    QQmlProperty p(propertyObject, "wrectProperty.x", qmlContext(propertyObject));
    QCOMPARE(p.name(), QString("wrectProperty.x"));

    QCOMPARE(p.read(), QVariant(10));

    //don't crash once the engine is deleted
    delete engine;
    engine = nullptr;

    QCOMPARE(p.propertyTypeName(), "int");
    QCOMPARE(p.read(), QVariant(10));
    p.write(QVariant(20));
    QCOMPARE(p.read(), QVariant(20));
}

void tst_qqmlproperty::aliasPropertyBindings_data()
{
    QTest::addColumn<QString>("file");
    QTest::addColumn<QString>("subObject");

    QTest::newRow("same object") << "aliasPropertyBindings.qml" << "";
    QTest::newRow("different objects") << "aliasPropertyBindings2.qml" << "innerObject";
}

// QTBUG-13719, QTBUG-58271
void tst_qqmlproperty::aliasPropertyBindings()
{
    QFETCH(QString, file);
    QFETCH(QString, subObject);

    QQmlComponent component(&engine, testFileUrl(file));

    QObject *object = component.create();
    QVERIFY(object != nullptr);

    // the object where realProperty lives
    QObject *realPropertyObject = object;
    if (!subObject.isEmpty())
       realPropertyObject = object->property(subObject.toLatin1()).value<QObject*>();

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 90.);
    QCOMPARE(object->property("aliasProperty").toReal(), 90.);

    object->setProperty("test", 10);

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 110.);
    QCOMPARE(object->property("aliasProperty").toReal(), 110.);

    QQmlProperty realProperty(realPropertyObject, QLatin1String("realProperty"));
    QQmlProperty aliasProperty(object, QLatin1String("aliasProperty"));

    // Check there is a binding on these two properties
    QVERIFY(QQmlPropertyPrivate::binding(realProperty) != nullptr);
    QVERIFY(QQmlPropertyPrivate::binding(aliasProperty) != nullptr);

    // Check that its the same binding on these two properties
    QCOMPARE(QQmlPropertyPrivate::binding(realProperty),
             QQmlPropertyPrivate::binding(aliasProperty));

    // Change the binding
    object->setProperty("state", QString("switch"));

    QVERIFY(QQmlPropertyPrivate::binding(realProperty) != nullptr);
    QVERIFY(QQmlPropertyPrivate::binding(aliasProperty) != nullptr);
    QCOMPARE(QQmlPropertyPrivate::binding(realProperty),
             QQmlPropertyPrivate::binding(aliasProperty));

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 96.);
    QCOMPARE(object->property("aliasProperty").toReal(), 96.);

    // Check the old binding really has not effect any more
    object->setProperty("test", 4);

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 96.);
    QCOMPARE(object->property("aliasProperty").toReal(), 96.);

    object->setProperty("test2", 9);

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 288.);
    QCOMPARE(object->property("aliasProperty").toReal(), 288.);

    // Revert
    object->setProperty("state", QString(""));

    QVERIFY(QQmlPropertyPrivate::binding(realProperty) != nullptr);
    QVERIFY(QQmlPropertyPrivate::binding(aliasProperty) != nullptr);
    QCOMPARE(QQmlPropertyPrivate::binding(realProperty),
             QQmlPropertyPrivate::binding(aliasProperty));

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 20.);
    QCOMPARE(object->property("aliasProperty").toReal(), 20.);

    object->setProperty("test2", 3);

    QCOMPARE(realPropertyObject->property("realProperty").toReal(), 20.);
    QCOMPARE(object->property("aliasProperty").toReal(), 20.);

    delete object;
}

void tst_qqmlproperty::copy()
{
    PropertyObject object;

    QScopedPointer<QQmlProperty> property(
                new QQmlProperty(&object, QLatin1String("defaultProperty")));
    QCOMPARE(property->name(), QString("defaultProperty"));
    QCOMPARE(property->read(), QVariant(10));
    QCOMPARE(property->type(), QQmlProperty::Property);
    QCOMPARE(property->propertyTypeCategory(), QQmlProperty::Normal);
    QCOMPARE(property->propertyType(), (int)QVariant::Int);

    QQmlProperty p1(*property);
    QCOMPARE(p1.name(), QString("defaultProperty"));
    QCOMPARE(p1.read(), QVariant(10));
    QCOMPARE(p1.type(), QQmlProperty::Property);
    QCOMPARE(p1.propertyTypeCategory(), QQmlProperty::Normal);
    QCOMPARE(p1.propertyType(), (int)QVariant::Int);

    QQmlProperty p2(&object, QLatin1String("url"));
    QCOMPARE(p2.name(), QString("url"));
    p2 = *property;
    QCOMPARE(p2.name(), QString("defaultProperty"));
    QCOMPARE(p2.read(), QVariant(10));
    QCOMPARE(p2.type(), QQmlProperty::Property);
    QCOMPARE(p2.propertyTypeCategory(), QQmlProperty::Normal);
    QCOMPARE(p2.propertyType(), (int)QVariant::Int);

    property.reset();

    QCOMPARE(p1.name(), QString("defaultProperty"));
    QCOMPARE(p1.read(), QVariant(10));
    QCOMPARE(p1.type(), QQmlProperty::Property);
    QCOMPARE(p1.propertyTypeCategory(), QQmlProperty::Normal);
    QCOMPARE(p1.propertyType(), (int)QVariant::Int);

    QCOMPARE(p2.name(), QString("defaultProperty"));
    QCOMPARE(p2.read(), QVariant(10));
    QCOMPARE(p2.type(), QQmlProperty::Property);
    QCOMPARE(p2.propertyTypeCategory(), QQmlProperty::Normal);
    QCOMPARE(p2.propertyType(), (int)QVariant::Int);

    p1 = QQmlProperty();
    QQmlPropertyPrivate *p2d = QQmlPropertyPrivate::get(p2);
    QCOMPARE(p2d->count(), 1);

    // Use a pointer to avoid compiler warning about self-assignment.
    QQmlProperty *p2p = &p2;
    *p2p = p2;

    QCOMPARE(p2d->count(), 1);
}

void tst_qqmlproperty::noContext()
{
    QQmlComponent compA(&engine, testFileUrl("NoContextTypeA.qml"));
    QQmlComponent compB(&engine, testFileUrl("NoContextTypeB.qml"));

    QObject *a = compA.create();
    QVERIFY(a != nullptr);
    QObject *b = compB.create();
    QVERIFY(b != nullptr);

    QVERIFY(QQmlProperty::write(b, "myTypeA", QVariant::fromValue(a), &engine));

    delete a;
    delete b;
}

void tst_qqmlproperty::assignEmptyVariantMap()
{
    PropertyObject o;

    QVariantMap map;
    map.insert("key", "value");
    o.setVariantMap(map);
    QCOMPARE(o.variantMap().count(), 1);
    QCOMPARE(o.variantMap().isEmpty(), false);


    QQmlComponent component(&engine, testFileUrl("assignEmptyVariantMap.qml"));
    QObject *obj = component.createWithInitialProperties({{"o", QVariant::fromValue(&o)}});
    QVERIFY(obj);

    QCOMPARE(o.variantMap().count(), 0);
    QCOMPARE(o.variantMap().isEmpty(), true);

    delete obj;
}

void tst_qqmlproperty::warnOnInvalidBinding()
{
    QUrl testUrl(testFileUrl("invalidBinding.qml"));
    QString expectedWarning;

    // V4 error message for property-to-property binding
    expectedWarning = testUrl.toString() + QString::fromLatin1(":6:5: Unable to assign QQuickText to QQuickRectangle");
    QTest::ignoreMessage(QtWarningMsg, expectedWarning.toLatin1().constData());

    // V8 error message for function-to-property binding
    expectedWarning = testUrl.toString() + QString::fromLatin1(":7:5: Unable to assign QQuickText to QQuickRectangle");
    QTest::ignoreMessage(QtWarningMsg, expectedWarning.toLatin1().constData());

#if QT_CONFIG(regularexpression)
    // V8 error message for invalid binding to anchor
    const QRegularExpression warning(
                "^" + testUrl.toString()
                + ":14:9: Unable to assign QQuickItem_QML_\\d+ to QQuickAnchorLine$");
    QTest::ignoreMessage(QtWarningMsg, warning);
#endif

    QQmlComponent component(&engine, testUrl);
    QObject *obj = component.create();
    QVERIFY(obj);
    delete obj;
}

void tst_qqmlproperty::deeplyNestedObject()
{
    PropertyObject o;
    QQmlProperty p(&o, "qmlObject.pointProperty.x");
    QCOMPARE(p.isValid(), true);

    p.write(14);
    QCOMPARE(p.read(), QVariant(14));
}

void tst_qqmlproperty::readOnlyDynamicProperties()
{
    QQmlComponent comp(&engine, testFileUrl("readonlyPrimitiveVsVar.qml"));
    QObject *obj = comp.create();
    QVERIFY(obj != nullptr);

    QVERIFY(!obj->metaObject()->property(obj->metaObject()->indexOfProperty("r_var")).isWritable());
    QVERIFY(!obj->metaObject()->property(obj->metaObject()->indexOfProperty("r_int")).isWritable());
    QVERIFY(obj->metaObject()->property(obj->metaObject()->indexOfProperty("w_var")).isWritable());
    QVERIFY(obj->metaObject()->property(obj->metaObject()->indexOfProperty("w_int")).isWritable());

    delete obj;
}

void tst_qqmlproperty::aliasToIdWithMatchingQmlFileNameOnCaseInsensitiveFileSystem()
{
    const QUrl url = testFileUrl("aliasToIdWithMatchingQmlFileName.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, url);
    QScopedPointer<QObject> root(component.create());

    QQmlProperty property(root.data(), "testType.objectName", QQmlEngine::contextForObject(root.data()));
    QVERIFY(property.isValid());
}

// QTBUG-77027
void tst_qqmlproperty::nullPropertyBinding()
{
    const QUrl url = testFileUrl("nullPropertyBinding.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, url);
    QScopedPointer<QObject> root(component.create());
    QVERIFY(root);
    QTest::ignoreMessage(QtMsgType::QtInfoMsg, "undefined");
    QMetaObject::invokeMethod(root.get(), "tog");
    QTest::ignoreMessage(QtMsgType::QtInfoMsg, "defined");
    QMetaObject::invokeMethod(root.get(), "tog");
    QTest::ignoreMessage(QtMsgType::QtInfoMsg, "undefined");
    QMetaObject::invokeMethod(root.get(), "tog");
}

void tst_qqmlproperty::interfaceBinding()
{
    qmlRegisterInterface<Interface>("Interface");
    qmlRegisterType<A>("io.qt.bugreports", 1, 0, "A");
    qmlRegisterType<B>("io.qt.bugreports", 1, 0, "B");
    qmlRegisterType<C>("io.qt.bugreports", 1, 0, "C");
    qmlRegisterType<InterfaceConsumer>("io.qt.bugreports", 1, 0, "InterfaceConsumer");

    const QVector<QUrl> urls = {
        testFileUrl("interfaceBinding.qml"),
        testFileUrl("interfaceBinding2.qml")
    };

    for (const QUrl &url : urls) {
        QQmlEngine engine;
        QQmlComponent component(&engine, url);
        QScopedPointer<QObject> root(component.create());
        QVERIFY(root);
        QCOMPARE(root->findChild<QObject*>("a1")->property("testValue").toInt(), 42);
        QCOMPARE(root->findChild<QObject*>("a2")->property("testValue").toInt(), 43);
        QCOMPARE(root->findChild<QObject*>("a3")->property("testValue").toInt(), 44);
    }
}

void tst_qqmlproperty::floatToStringPrecision_data()
{
    QTest::addColumn<QString>("propertyName");
    QTest::addColumn<double>("number");
    QTest::addColumn<QString>("qtString");
    QTest::addColumn<QString>("jsString");

    QTest::newRow("3.4")           << "a" << 3.4           << "3.4"          << "3.4";
    QTest::newRow("0.035003945")   << "b" << 0.035003945   << "0.035003945"  << "0.035003945";
    QTest::newRow("0.0000012345")  << "c" << 0.0000012345  << "1.2345e-06"   << "0.0000012345";
    QTest::newRow("0.00000012345") << "d" << 0.00000012345 << "1.2345e-07"   << "1.2345e-7";
    QTest::newRow("1e20")          << "e" << 1e20          << "1e+20"        << "100000000000000000000";
    QTest::newRow("1e21")          << "f" << 1e21          << "1e+21"        << "1e+21";
}

void tst_qqmlproperty::floatToStringPrecision()
{
    QQmlComponent comp(&engine, testFileUrl("floatToStringPrecision.qml"));
    QScopedPointer<QObject> obj(comp.create());
    QVERIFY(obj != nullptr);

    QFETCH(QString, propertyName);
    QFETCH(double, number);
    QFETCH(QString, qtString);
    QFETCH(QString, jsString);

    QByteArray name = propertyName.toLatin1();
    QCOMPARE(obj->property(name).toDouble(), number);
    QCOMPARE(obj->property(name).toString(), qtString);

    QByteArray name1 = (propertyName + QLatin1Char('1')).toLatin1();
    QCOMPARE(obj->property(name1).toDouble(), number);
    QCOMPARE(obj->property(name1).toString(), qtString);

    QByteArray name2 = (propertyName + QLatin1Char('2')).toLatin1();
    QCOMPARE(obj->property(name2).toDouble(), number);
    QCOMPARE(obj->property(name2).toString(), jsString);
}

void tst_qqmlproperty::initTestCase()
{
    QQmlDataTest::initTestCase();
    qmlRegisterType<MyQmlObject>("Test",1,0,"MyQmlObject");
    qmlRegisterType<PropertyObject>("Test",1,0,"PropertyObject");
    qmlRegisterType<MyContainer>("Test",1,0,"MyContainer");
}

// QTBUG-60908
void tst_qqmlproperty::bindingToAlias()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("aliasToBinding.qml"));
    QScopedPointer<QObject> o(component.create());
    QVERIFY(!o.isNull());
}

void tst_qqmlproperty::nestedQQmlPropertyMap()
{
    QQmlPropertyMap mainPropertyMap;
    QQmlPropertyMap nestedPropertyMap;
    QQmlPropertyMap deeplyNestedPropertyMap;

    mainPropertyMap.insert("nesting1", QVariant::fromValue(&nestedPropertyMap));
    nestedPropertyMap.insert("value", 42);
    nestedPropertyMap.insert("nesting2", QVariant::fromValue(&deeplyNestedPropertyMap));
    deeplyNestedPropertyMap.insert("value", "success");

    QQmlProperty value{&mainPropertyMap, "nesting1.value"};
    QCOMPARE(value.read().toInt(), 42);

    QQmlProperty success{&mainPropertyMap, "nesting1.nesting2.value"};
    QCOMPARE(success.read().toString(), QLatin1String("success"));
}

void tst_qqmlproperty::underscorePropertyChangeHandler()
{
    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.setData(R"(
        import QtQuick 2.12

        Item {
            property int __withUnderScore
        }
    )", QUrl::fromLocalFile("."));
    QScopedPointer<QObject> root { component.create() };
    QVERIFY(root);
    QQmlProperty changeHandler(root.get(), "on__WithUnderScoreChanged");
    QVERIFY(changeHandler.isValid());
    QVERIFY(changeHandler.isSignalProperty());
}

void tst_qqmlproperty::signalExpressionWithoutObject()
{
    QQmlProperty invalid;
    QQmlPropertyPrivate::setSignalExpression(invalid, nullptr);
    QQmlBoundSignalExpression *expr = QQmlPropertyPrivate::signalExpression(invalid);
    QVERIFY(!expr);
}

QTEST_MAIN(tst_qqmlproperty)

#include "tst_qqmlproperty.moc"
