/****************************************************************************
**
** Copyright (C) 2017 Crimson AS <info@crimson.no>
** 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 <QtTest/QtTest>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlexpression.h>
#include <QtQml/qqmlcontext.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
#include <QtCore/qnumeric.h>
#include <private/qqmlengine_p.h>
#include <private/qqmlvmemetaobject_p.h>
#include <private/qv4qmlcontext_p.h>
#include "testtypes.h"
#include "testhttpserver.h"
#include "../../shared/util.h"
#include <private/qv4functionobject_p.h>
#include <private/qv4scopedvalue_p.h>
#include <private/qv4jscall_p.h>
#include <private/qv4alloca_p.h>
#include <private/qv4runtime_p.h>
#include <private/qv4object_p.h>
#include <private/qv4script_p.h>
#include <private/qqmlcomponentattached_p.h>
#include <private/qv4objectiterator_p.h>
#include <private/qqmlabstractbinding_p.h>
#include <private/qqmlvaluetypeproxybinding_p.h>

#ifdef Q_CC_MSVC
#define NO_INLINE __declspec(noinline)
#else
#define NO_INLINE __attribute__((noinline))
#endif

/*
This test covers evaluation of ECMAScript expressions and bindings from within
QML.  This does not include static QML language issues.

Static QML language issues are covered in qmllanguage
*/

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

private slots:
    void initTestCase();
    void arrayIncludesValueType();
    void assignBasicTypes();
    void assignDate_data();
    void assignDate();
    void exportDate_data();
    void exportDate();
    void checkDate_data();
    void checkDate();
    void checkDateTime_data();
    void checkDateTime();
    void idShortcutInvalidates();
    void boolPropertiesEvaluateAsBool();
    void methods();
    void signalAssignment();
    void signalArguments();
    void bindingLoop();
    void basicExpressions();
    void basicExpressions_data();
    void arrayExpressions();
    void contextPropertiesTriggerReeval();
    void objectPropertiesTriggerReeval();
    void dependenciesWithFunctions();
    void deferredProperties();
    void deferredPropertiesErrors();
    void deferredPropertiesInComponents();
    void deferredPropertiesInDestruction();
    void extensionObjects();
    void overrideExtensionProperties();
    void attachedProperties();
    void enums();
    void valueTypeFunctions();
    void constantsOverrideBindings();
    void outerBindingOverridesInnerBinding();
    void aliasPropertyAndBinding();
    void aliasPropertyReset();
    void nonExistentAttachedObject();
    void scope();
    void importScope();
    void signalParameterTypes();
    void objectsCompareAsEqual();
    void componentCreation_data();
    void componentCreation();
    void dynamicCreation_data();
    void dynamicCreation();
    void dynamicDestruction();
    void objectToString();
    void objectHasOwnProperty();
    void selfDeletingBinding();
    void extendedObjectPropertyLookup();
    void extendedObjectPropertyLookup2();
    void uncreatableExtendedObjectFailureCheck();
    void extendedObjectPropertyLookup3();
    void scriptErrors();
    void functionErrors();
    void propertyAssignmentErrors();
    void signalTriggeredBindings();
    void listProperties();
    void exceptionClearsOnReeval();
    void exceptionSlotProducesWarning();
    void exceptionBindingProducesWarning();
    void compileInvalidBinding();
    void transientErrors();
    void shutdownErrors();
    void compositePropertyType();
    void jsObject();
    void undefinedResetsProperty();
    void listToVariant();
    void listAssignment();
    void multiEngineObject();
    void deletedObject();
    void attachedPropertyScope();
    void scriptConnect();
    void scriptDisconnect();
    void ownership();
    void cppOwnershipReturnValue();
    void ownershipCustomReturnValue();
    void ownershipRootObject();
    void ownershipConsistency();
    void ownershipQmlIncubated();
    void qlistqobjectMethods();
    void strictlyEquals();
    void compiled();
    void numberAssignment();
    void propertySplicing();
    void signalWithUnknownTypes();
    void signalWithJSValueInVariant_data();
    void signalWithJSValueInVariant();
    void signalWithJSValueInVariant_twoEngines_data();
    void signalWithJSValueInVariant_twoEngines();
    void signalWithQJSValue_data();
    void signalWithQJSValue();
    void singletonType_data();
    void singletonType();
    void singletonTypeCaching_data();
    void singletonTypeCaching();
    void singletonTypeImportOrder();
    void singletonTypeResolution();
    void importScripts_data();
    void importScripts();
    void importCreationContext();
    void scarceResources();
    void scarceResources_data();
    void scarceResources_other();
    void propertyChangeSlots();
    void propertyVar_data();
    void propertyVar();
    void propertyQJSValue_data();
    void propertyQJSValue();
    void propertyVarCpp();
    void propertyVarOwnership();
    void propertyVarImplicitOwnership();
    void propertyVarReparent();
    void propertyVarReparentNullContext();
    void propertyVarCircular();
    void propertyVarCircular2();
    void propertyVarInheritance();
    void propertyVarInheritance2();
    void elementAssign();
    void objectPassThroughSignals();
    void objectConversion();
    void booleanConversion();
    void handleReferenceManagement();
    void stringArg();
    void readonlyDeclaration();
    void sequenceConversionRead();
    void sequenceConversionWrite();
    void sequenceConversionArray();
    void sequenceConversionIndexes();
    void sequenceConversionThreads();
    void sequenceConversionBindings();
    void sequenceConversionCopy();
    void assignSequenceTypes();
    void sequenceSort_data();
    void sequenceSort();
    void dateParse();
    void utcDate();
    void negativeYear();
    void qtbug_22464();
    void qtbug_21580();
    void singleV8BindingDestroyedDuringEvaluation();
    void bug1();
#ifndef QT_NO_WIDGETS
    void bug2();
#endif
    void dynamicCreationCrash();
    void dynamicCreationOwnership();
    void regExpBug();
    void nullObjectBinding();
    void nullObjectInitializer();
    void deletedEngine();
    void libraryScriptAssert();
    void variantsAssignedUndefined();
    void variants();
    void qtbug_9792();
    void qtcreatorbug_1289();
    void noSpuriousWarningsAtShutdown();
    void canAssignNullToQObject();
    void functionAssignment_fromBinding();
    void functionAssignment_fromJS();
    void functionAssignment_fromJS_data();
    void functionAssignmentfromJS_invalid();
    void functionAssignment_afterBinding();
    void eval();
    void function();
    void topLevelGeneratorFunction();
    void generatorCrashNewProperty();
    void generatorCallsGC();
    void qtbug_10696();
    void qtbug_11606();
    void qtbug_11600();
    void qtbug_21864();
    void qobjectConnectionListExceptionHandling();
    void nonscriptable();
    void deleteLater();
    void objectNameChangedSignal();
    void destroyedSignal();
    void in();
    void typeOf();
    void qtbug_24448();
    void sharedAttachedObject();
    void objectName();
    void writeRemovesBinding();
    void aliasBindingsAssignCorrectly();
    void aliasBindingsOverrideTarget();
    void aliasWritesOverrideBindings();
    void aliasToCompositeElement();
    void realToInt();
    void urlProperty();
    void urlPropertyWithEncoding();
    void urlListPropertyWithEncoding();
    void dynamicString();
    void include();
    void includeRemoteSuccess();
    void signalHandlers();
    void qtbug_37351();
    void doubleEvaluate();
    void forInLoop();
    void nonNotifyable();
    void nonNotifyableConstant();
    void deleteWhileBindingRunning();
    void callQtInvokables();
    void resolveClashingProperties();
    void invokableObjectArg();
    void invokableObjectRet();
    void invokableEnumRet();
    void qtbug_20344();
    void qtbug_22679();
    void qtbug_22843_data();
    void qtbug_22843();
    void rewriteMultiLineStrings();
    void revisionErrors();
    void revision();
    void invokableWithQObjectDerived();
    void realTypePrecision();
    void registeredFlagMethod();
    void deleteLaterObjectMethodCall();
    void automaticSemicolon();
    void compatibilitySemicolon();
    void incrDecrSemicolon1();
    void incrDecrSemicolon2();
    void incrDecrSemicolon_error1();
    void unaryExpression();
    void switchStatement();
    void withStatement();
    void tryStatement();
    void replaceBinding();
    void bindingBoundFunctions();
    void deleteRootObjectInCreation();
    void onDestruction();
    void onDestructionViaGC();
    void bindingSuppression();
    void signalEmitted();
    void threadSignal();
    void qqmldataDestroyed();
    void secondAlias();
    void varAlias();
    void overrideDataAssert();
    void fallbackBindings_data();
    void fallbackBindings();
    void propertyOverride();
    void concatenatedStringPropertyAccess();
    void jsOwnedObjectsDeletedOnEngineDestroy();
    void updateCall();
    void numberParsing();
    void stringParsing();
    void push_and_shift();
    void qtbug_32801();
    void thisObject();
    void qtbug_33754();
    void qtbug_34493();
    void singletonFromQMLToCpp();
    void singletonFromQMLAndBackAndCompare();
    void setPropertyOnInvalid();
    void miscTypeTest();
    void stackLimits();
    void idsAsLValues();
    void qtbug_34792();
    void noCaptureWhenWritingProperty();
    void singletonWithEnum();
    void lazyBindingEvaluation();
    void varPropertyAccessOnObjectWithInvalidContext();
    void importedScriptsAccessOnObjectWithInvalidContext();
    void importedScriptsWithoutQmlMode();
    void contextObjectOnLazyBindings();
    void garbageCollectionDuringCreation();
    void qtbug_39520();
    void readUnregisteredQObjectProperty();
    void writeUnregisteredQObjectProperty();
    void switchExpression();
    void qtbug_46022();
    void qtbug_52340();
    void qtbug_54589();
    void qtbug_54687();
    void stringify_qtbug_50592();
    void instanceof_data();
    void instanceof();
    void constkw_data();
    void constkw();
    void redefineGlobalProp();
    void freeze_empty_object();
    void singleBlockLoops();
    void qtbug_60547();
    void delayLoadingArgs();
    void manyArguments();
    void forInIterator();
    void localForInIterator();
    void shadowedFunctionName();
    void anotherNaN();
    void callPropertyOnUndefined();
    void jumpStrictNotEqualUndefined();
    void removeBindingsWithNoDependencies();
    void preserveBindingWithUnresolvedNames();
    void temporaryDeadZone();
    void importLexicalVariables_data();
    void importLexicalVariables();
    void hugeObject();
    void templateStringTerminator();
    void arrayAndException();
    void numberToStringWithRadix();
    void tailCallWithArguments();
    void deleteSparseInIteration();
    void saveAccumulatorBeforeToInt32();
    void intMinDividedByMinusOne();
    void undefinedPropertiesInObjectWrapper();
    void hugeRegexpQuantifiers();
    void singletonTypeWrapperLookup();
    void getThisObject();
    void semicolonAfterProperty();
    void hugeStack();
    void variantConversionMethod();
    void proxyIteration();
    void proxyHandlerTraps();
    void gcCrashRegressionTest();

private:
//    static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
    static void verifyContextLifetime(QQmlContextData *ctxt);

    // When calling into JavaScript, the specific type of the return value can differ if that return
    // value is a number. This is not only the case for non-integral numbers, or numbers that do not
    // fit into the (signed) integer range, but it also depends on which optimizations are run. So,
    // to check if the return value is of a number type, use this method instead of checking against
    // a specific userType.
    static bool isJSNumberType(int userType)
    {
        return userType == (int) QVariant::Int
                || userType == (int) QVariant::UInt
                || userType == (int) QVariant::Double;
    }
};

static void gc(QQmlEngine &engine)
{
    engine.collectGarbage();
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();
}


void tst_qqmlecmascript::initTestCase()
{
    QQmlDataTest::initTestCase();
    registerTypes();
}

void tst_qqmlecmascript::arrayIncludesValueType()
{
    QQmlEngine engine;
    QQmlComponent component(&engine);
    // It is vital that QtQuick is imported below else we get a warning about
    // QQml_colorProvider and tst_qqmlecmascript::signalParameterTypes fails due
    // to some static variable being initialized with the wrong value
    component.setData(R"(
    import QtQuick 2.15
    import QtQml 2.15
    QtObject {
        id: root
        property color r: Qt.rgba(1, 0, 0)
        property color g: Qt.rgba(0, 1, 0)
        property color b: Qt.rgba(0, 0, 1)
        property var colors: [r, g, b]
        property bool success: false

        Component.onCompleted: {
            root.success = root.colors.includes(root.g)
        }
    }
    )", QUrl("testData"));
    QScopedPointer<QObject> o(component.create());
    QVERIFY(o);
    auto success = o->property("success");
    QVERIFY(success.isValid());
    QVERIFY(success.toBool());
}

void tst_qqmlecmascript::assignBasicTypes()
{
    QQmlEngine engine;
    {
    QQmlComponent component(&engine, testFileUrl("assignBasicTypes.qml"));
    MyTypeObject *object = qobject_cast<MyTypeObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3);
    QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2);
    QCOMPARE(object->relatedEnumProperty(), MyEnumContainer::RelatedValue);
    QCOMPARE(object->stringProperty(), QString("Hello World!"));
    QCOMPARE(object->uintProperty(), uint(10));
    QCOMPARE(object->intProperty(), -19);
    QCOMPARE((float)object->realProperty(), float(23.2));
    QCOMPARE((float)object->doubleProperty(), float(-19.75));
    QCOMPARE((float)object->floatProperty(), float(8.5));
    QCOMPARE(object->colorProperty(), QColor("red"));
    QCOMPARE(object->dateProperty(), QDate(1982, 11, 25));
    QCOMPARE(object->timeProperty(), QTime(11, 11, 32));
    QCOMPARE(object->dateTimeProperty(), QDateTime(QDate(2009, 5, 12), QTime(13, 22, 1), Qt::UTC));
    QCOMPARE(object->pointProperty(), QPoint(99,13));
    QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3));
    QCOMPARE(object->sizeProperty(), QSize(99, 13));
    QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2));
    QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200));
    QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99));
    QCOMPARE(object->boolProperty(), true);
    QCOMPARE(object->variantProperty(), QVariant("Hello World!"));
    QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2f));
    QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml")));
    delete object;
    }
    {
    QQmlComponent component(&engine, testFileUrl("assignBasicTypes.2.qml"));
    MyTypeObject *object = qobject_cast<MyTypeObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3);
    QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2);
    QCOMPARE(object->relatedEnumProperty(), MyEnumContainer::RelatedValue);
    QCOMPARE(object->stringProperty(), QString("Hello World!"));
    QCOMPARE(object->uintProperty(), uint(10));
    QCOMPARE(object->intProperty(), -19);
    QCOMPARE((float)object->realProperty(), float(23.2));
    QCOMPARE((float)object->doubleProperty(), float(-19.75));
    QCOMPARE((float)object->floatProperty(), float(8.5));
    QCOMPARE(object->colorProperty(), QColor("red"));
    QCOMPARE(object->dateProperty(), QDate(1982, 11, 25));
    QCOMPARE(object->timeProperty(), QTime(11, 11, 32));
    QCOMPARE(object->dateTimeProperty(), QDateTime(QDate(2009, 5, 12), QTime(13, 22, 1), Qt::UTC));
    QCOMPARE(object->pointProperty(), QPoint(99,13));
    QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3));
    QCOMPARE(object->sizeProperty(), QSize(99, 13));
    QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2));
    QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200));
    QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99));
    QCOMPARE(object->boolProperty(), true);
    QCOMPARE(object->variantProperty(), QVariant("Hello World!"));
    QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2f));
    QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml")));
    delete object;
    }
}

void tst_qqmlecmascript::assignDate_data()
{
    QTest::addColumn<QUrl>("source");
    QTest::addColumn<int>("timeOffset"); // -1 for local-time, else minutes from UTC

    QTest::newRow("Component.onComplete JS Parse") << testFileUrl("assignDate.qml") << -1;
    QTest::newRow("Component.onComplete JS") << testFileUrl("assignDate.1.qml") << 0;
    QTest::newRow("Binding JS") << testFileUrl("assignDate.2.qml") << -1;
    QTest::newRow("Binding UTC") << testFileUrl("assignDate.3.qml") << 0;
    QTest::newRow("Binding JS UTC") << testFileUrl("assignDate.4.qml") << 0;
    QTest::newRow("Binding UTC+2") << testFileUrl("assignDate.5.qml") << 120;
    QTest::newRow("Binding JS UTC+2 ") << testFileUrl("assignDate.6.qml") << 120;
}

void tst_qqmlecmascript::assignDate()
{
    QFETCH(QUrl, source);
    QFETCH(int, timeOffset);

    QQmlEngine engine;
    QQmlComponent component(&engine, source);
    QScopedPointer<QObject> obj(component.create());
    MyTypeObject *object = qobject_cast<MyTypeObject *>(obj.data());
    QVERIFY(object != nullptr);

    QDate expectedDate(2009, 5, 12);
    QDateTime expectedDateTime;
    QDateTime expectedDateTime2;
    if (timeOffset == -1) {
        expectedDateTime = QDateTime(QDate(2009, 5, 12), QTime(0, 0, 1), Qt::LocalTime);
        expectedDateTime2 = QDateTime(QDate(2009, 5, 12), QTime(23, 59, 59), Qt::LocalTime);
    } else {
        expectedDateTime = QDateTime(QDate(2009, 5, 12), QTime(0, 0, 1), Qt::OffsetFromUTC, timeOffset * 60);
        expectedDateTime2 = QDateTime(QDate(2009, 5, 12), QTime(23, 59, 59), Qt::OffsetFromUTC, timeOffset * 60);
    }

    QCOMPARE(object->dateProperty(), expectedDate);
    QCOMPARE(object->dateTimeProperty(), expectedDateTime);
    QCOMPARE(object->dateTimeProperty2(), expectedDateTime2);
    QCOMPARE(object->boolProperty(), true);
}

void tst_qqmlecmascript::exportDate_data()
{
    QTest::addColumn<QUrl>("source");
    QTest::addColumn<QDateTime>("datetime");

    // Verify that we can export datetime information to QML and that consumers can access
    // the data correctly provided they know the TZ info associated with the value

    const QDate date(2009, 5, 12);
    const QTime early(0, 0, 1);
    const QTime late(23, 59, 59);
    const int offset(((11 * 60) + 30) * 60);

    QTest::newRow("Localtime early") << testFileUrl("exportDate.qml") << QDateTime(date, early, Qt::LocalTime);
    QTest::newRow("Localtime late") << testFileUrl("exportDate.2.qml") << QDateTime(date, late, Qt::LocalTime);
    QTest::newRow("UTC early") << testFileUrl("exportDate.3.qml") << QDateTime(date, early, Qt::UTC);
    QTest::newRow("UTC late") << testFileUrl("exportDate.4.qml") << QDateTime(date, late, Qt::UTC);
    {
        QDateTime dt(date, early, Qt::OffsetFromUTC);
        dt.setOffsetFromUtc(offset);
        QTest::newRow("+11:30 early") << testFileUrl("exportDate.5.qml") << dt;
    }
    {
        QDateTime dt(date, late, Qt::OffsetFromUTC);
        dt.setOffsetFromUtc(offset);
        QTest::newRow("+11:30 late") << testFileUrl("exportDate.6.qml") << dt;
    }
    {
        QDateTime dt(date, early, Qt::OffsetFromUTC);
        dt.setOffsetFromUtc(-offset);
        QTest::newRow("-11:30 early") << testFileUrl("exportDate.7.qml") << dt;
    }
    {
        QDateTime dt(date, late, Qt::OffsetFromUTC);
        dt.setOffsetFromUtc(-offset);
        QTest::newRow("-11:30 late") << testFileUrl("exportDate.8.qml") << dt;
    }
}

void tst_qqmlecmascript::exportDate()
{
    QFETCH(QUrl, source);
    QFETCH(QDateTime, datetime);

    DateTimeExporter exporter(datetime);

    QQmlEngine e;
    e.rootContext()->setContextProperty("datetimeExporter", &exporter);

    QQmlComponent component(&e, source);
    QScopedPointer<QObject> obj(component.create());
    MyTypeObject *object = qobject_cast<MyTypeObject *>(obj.data());
    QVERIFY(object != nullptr);
    QCOMPARE(object->boolProperty(), true);
}

void tst_qqmlecmascript::checkDate_data()
{
    QTest::addColumn<QUrl>("source");
    QTest::addColumn<QDate>("date");
    // NB: JavaScript month-indices are Jan = 0 to Dec = 11; QDate's are Jan = 1 to Dec = 12.
    QTest::newRow("denormal-March")
        << testFileUrl("checkDate-denormal-March.qml")
        << QDate(2019, 3, 1);
    QTest::newRow("denormal-leap")
        << testFileUrl("checkDate-denormal-leap.qml")
        << QDate(2020, 2, 29);
    QTest::newRow("denormal-Feb")
        << testFileUrl("checkDate-denormal-Feb.qml")
        << QDate(2019, 2, 28);
    QTest::newRow("denormal-year")
        << testFileUrl("checkDate-denormal-year.qml")
        << QDate(2019, 12, 31);
    QTest::newRow("denormal-wrap")
        << testFileUrl("checkDate-denormal-wrap.qml")
        << QDate(2020, 2, 29);
    QTest::newRow("October")
        << testFileUrl("checkDate-October.qml")
        << QDate(2019, 10, 3);
}

void tst_qqmlecmascript::checkDate()
{
    QFETCH(const QUrl, source);
    QFETCH(const QDate, date);
    QQmlEngine e;
    QQmlComponent component(&e, source);
    QScopedPointer<QObject> obj(component.create());
    MyTypeObject *object = qobject_cast<MyTypeObject *>(obj.data());
    QVERIFY(object != nullptr);
    QCOMPARE(object->dateProperty(), date);
    QVERIFY(object->boolProperty());
}

void tst_qqmlecmascript::checkDateTime_data()
{
    QTest::addColumn<QUrl>("source");
    QTest::addColumn<QDateTime>("when");
    // NB: JavaScript month-indices are Jan = 0 to Dec = 11; QDate's are Jan = 1 to Dec = 12.
    QTest::newRow("denormal-March")
        << testFileUrl("checkDateTime-denormal-March.qml")
        << QDateTime(QDate(2019, 3, 1), QTime(0, 0, 0, 1), Qt::LocalTime);
    QTest::newRow("denormal-leap")
        << testFileUrl("checkDateTime-denormal-leap.qml")
        << QDateTime(QDate(2020, 2, 29), QTime(23, 59, 59, 999), Qt::LocalTime);
    QTest::newRow("denormal-hours")
        << testFileUrl("checkDateTime-denormal-hours.qml")
        << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime);
    QTest::newRow("denormal-minutes")
        << testFileUrl("checkDateTime-denormal-minutes.qml")
        << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime);
    QTest::newRow("denormal-seconds")
        << testFileUrl("checkDateTime-denormal-seconds.qml")
        << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime);
    QTest::newRow("October")
        << testFileUrl("checkDateTime-October.qml")
        << QDateTime(QDate(2019, 10, 3), QTime(12, 0), Qt::LocalTime);
}

void tst_qqmlecmascript::checkDateTime()
{
    QFETCH(const QUrl, source);
    QFETCH(const QDateTime, when);
    QQmlEngine e;
    QQmlComponent component(&e, source);
    QScopedPointer<QObject> obj(component.create());
    MyTypeObject *object = qobject_cast<MyTypeObject *>(obj.data());
    QVERIFY(object != nullptr);
    QCOMPARE(object->dateTimeProperty(), when);
    QVERIFY(object->boolProperty());
}

void tst_qqmlecmascript::idShortcutInvalidates()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("idShortcutInvalidates.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QVERIFY(object->objectProperty() != nullptr);
        delete object->objectProperty();
        QVERIFY(!object->objectProperty());
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("idShortcutInvalidates.1.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QVERIFY(object->objectProperty() != nullptr);
        delete object->objectProperty();
        QVERIFY(!object->objectProperty());
        delete object;
    }
}

void tst_qqmlecmascript::boolPropertiesEvaluateAsBool()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("boolPropertiesEvaluateAsBool.1.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->stringProperty(), QLatin1String("pass"));
        delete object;
    }
    {
        QQmlComponent component(&engine, testFileUrl("boolPropertiesEvaluateAsBool.2.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->stringProperty(), QLatin1String("pass"));
        delete object;
    }
}

void tst_qqmlecmascript::signalAssignment()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("signalAssignment.1.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->string(), QString());
        emit object->basicSignal();
        QCOMPARE(object->string(), QString("pass"));
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("signalAssignment.2.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->string(), QString());
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->string(), QString("pass 19 Hello world! 10.25 3 2"));
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("signalAssignment.3.qml"));
        QVERIFY(component.isError());
        QString expectedErrorString = component.url().toString() + QLatin1String(":4 Signal uses unnamed parameter followed by named parameter.\n");
        QCOMPARE(component.errorString(), expectedErrorString);
    }

    {
        QQmlComponent component(&engine, testFileUrl("signalAssignment.4.qml"));
        QVERIFY(component.isError());
        QString expectedErrorString = component.url().toString() + QLatin1String(":5 Signal parameter \"parseInt\" hides global variable.\n");
        QCOMPARE(component.errorString(), expectedErrorString);
    }
}

void tst_qqmlecmascript::signalArguments()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("signalArguments.1.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->string(), QString());
        emit object->basicSignal();
        QCOMPARE(object->string(), QString("pass"));
        QCOMPARE(object->property("argumentCount").toInt(), 0);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("signalArguments.2.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->string(), QString());
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->string(), QString("pass 19 Hello world! 10.25 3 2"));
        QCOMPARE(object->property("argumentCount").toInt(), 5);
        delete object;
    }
}

void tst_qqmlecmascript::methods()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("methods.1.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->methodCalled(), false);
        QCOMPARE(object->methodIntCalled(), false);
        emit object->basicSignal();
        QCOMPARE(object->methodCalled(), true);
        QCOMPARE(object->methodIntCalled(), false);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("methods.2.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->methodCalled(), false);
        QCOMPARE(object->methodIntCalled(), false);
        emit object->basicSignal();
        QCOMPARE(object->methodCalled(), false);
        QCOMPARE(object->methodIntCalled(), true);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("methods.3.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("test").toInt(), 19);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("methods.4.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("test").toInt(), 19);
        QCOMPARE(object->property("test2").toInt(), 17);
        QCOMPARE(object->property("test3").toInt(), 16);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("methods.5.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("test").toInt(), 9);
        delete object;
    }
}

void tst_qqmlecmascript::bindingLoop()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("bindingLoop.qml"));
    QString warning = component.url().toString() + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\"";
    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    delete object;
}

void tst_qqmlecmascript::basicExpressions_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QVariant>("result");
    QTest::addColumn<bool>("nest");

    QTest::newRow("Syntax error (self test)") << "{console.log({'a':1'}.a)}" << QVariant() << false;
    QTest::newRow("Context property") << "a" << QVariant(1944) << false;
    QTest::newRow("Context property") << "a" << QVariant(1944) << true;
    QTest::newRow("Context property expression") << "a * 2" << QVariant(3888) << false;
    QTest::newRow("Context property expression") << "a * 2" << QVariant(3888) << true;
    QTest::newRow("Overridden context property") << "b" << QVariant("Milk") << false;
    QTest::newRow("Overridden context property") << "b" << QVariant("Cow") << true;
    QTest::newRow("Object property") << "object.stringProperty" << QVariant("Object1") << false;
    QTest::newRow("Object property") << "object.stringProperty" << QVariant("Object1") << true;
    QTest::newRow("Overridden object property") << "objectOverride.stringProperty" << QVariant("Object2") << false;
    QTest::newRow("Overridden object property") << "objectOverride.stringProperty" << QVariant("Object3") << true;
    QTest::newRow("Default object property") << "horseLegs" << QVariant(4) << false;
    QTest::newRow("Default object property") << "antLegs" << QVariant(6) << false;
    QTest::newRow("Default object property") << "emuLegs" << QVariant(2) << false;
    QTest::newRow("Nested default object property") << "horseLegs" << QVariant(4) << true;
    QTest::newRow("Nested default object property") << "antLegs" << QVariant(7) << true;
    QTest::newRow("Nested default object property") << "emuLegs" << QVariant(2) << true;
    QTest::newRow("Nested default object property") << "humanLegs" << QVariant(2) << true;
    QTest::newRow("Context property override default object property") << "millipedeLegs" << QVariant(100) << true;
}

void tst_qqmlecmascript::basicExpressions()
{
    QFETCH(QString, expression);
    QFETCH(QVariant, result);
    QFETCH(bool, nest);

    QQmlEngine engine;

    MyQmlObject object1;
    MyQmlObject object2;
    MyQmlObject object3;
    MyDefaultObject1 default1;
    MyDefaultObject3 default3;
    object1.setStringProperty("Object1");
    object2.setStringProperty("Object2");
    object3.setStringProperty("Object3");

    QQmlContext context(engine.rootContext());
    QQmlContext nestedContext(&context);

    context.setContextObject(&default1);
    context.setContextProperty("a", QVariant(1944));
    context.setContextProperty("b", QVariant("Milk"));
    context.setContextProperty("object", &object1);
    context.setContextProperty("objectOverride", &object2);
    nestedContext.setContextObject(&default3);
    nestedContext.setContextProperty("b", QVariant("Cow"));
    nestedContext.setContextProperty("objectOverride", &object3);
    nestedContext.setContextProperty("millipedeLegs", QVariant(100));

    MyExpression expr(nest?&nestedContext:&context, expression);
    QCOMPARE(expr.evaluate(), result);
}

void tst_qqmlecmascript::arrayExpressions()
{
    QObject obj1;
    QObject obj2;
    QObject obj3;

    QQmlEngine engine;
    QQmlContext context(engine.rootContext());
    context.setContextProperty("a", &obj1);
    context.setContextProperty("b", &obj2);
    context.setContextProperty("c", &obj3);

    MyExpression expr(&context, "[a, b, c, 10]");
    QVariant result = expr.evaluate();
    QCOMPARE(result.userType(), qMetaTypeId<QJSValue>());
    QJSValue list = qvariant_cast<QJSValue>(result);
    QCOMPARE(list.property("length").toInt(), 4);
    QCOMPARE(list.property(0).toQObject(), &obj1);
    QCOMPARE(list.property(1).toQObject(), &obj2);
    QCOMPARE(list.property(2).toQObject(), &obj3);
    QCOMPARE(list.property(3).toInt(), 10);
}

// Tests that modifying a context property will reevaluate expressions
void tst_qqmlecmascript::contextPropertiesTriggerReeval()
{
    QQmlEngine engine;
    QQmlContext context(engine.rootContext());
    MyQmlObject object1;
    MyQmlObject object2;
    MyQmlObject *object3 = new MyQmlObject;

    object1.setStringProperty("Hello");
    object2.setStringProperty("World");

    context.setContextProperty("testProp", QVariant(1));
    context.setContextProperty("testObj", &object1);
    context.setContextProperty("testObj2", object3);

    {
        MyExpression expr(&context, "testProp + 1");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant(2));

        context.setContextProperty("testProp", QVariant(2));
        QCOMPARE(expr.changed, true);
        QCOMPARE(expr.evaluate(), QVariant(3));
    }

    {
        MyExpression expr(&context, "testProp + testProp + testProp");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant(6));

        context.setContextProperty("testProp", QVariant(4));
        QCOMPARE(expr.changed, true);
        QCOMPARE(expr.evaluate(), QVariant(12));
    }

    {
        MyExpression expr(&context, "testObj.stringProperty");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant("Hello"));

        context.setContextProperty("testObj", &object2);
        QCOMPARE(expr.changed, true);
        QCOMPARE(expr.evaluate(), QVariant("World"));
    }

    {
        MyExpression expr(&context, "testObj.stringProperty /**/");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant("World"));

        context.setContextProperty("testObj", &object1);
        QCOMPARE(expr.changed, true);
        QCOMPARE(expr.evaluate(), QVariant("Hello"));
    }

    {
        MyExpression expr(&context, "testObj2");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant::fromValue((QObject *)object3));
    }

    delete object3;
}

void tst_qqmlecmascript::objectPropertiesTriggerReeval()
{
    QQmlEngine engine;
    QQmlContext context(engine.rootContext());
    MyQmlObject object1;
    MyQmlObject object2;
    MyQmlObject object3;
    context.setContextProperty("testObj", &object1);

    object1.setStringProperty(QLatin1String("Hello"));
    object2.setStringProperty(QLatin1String("Dog"));
    object3.setStringProperty(QLatin1String("Cat"));

    {
        MyExpression expr(&context, "testObj.stringProperty");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant("Hello"));

        object1.setStringProperty(QLatin1String("World"));
        QCOMPARE(expr.changed, true);
        QCOMPARE(expr.evaluate(), QVariant("World"));
    }

    {
        MyExpression expr(&context, "testObj.objectProperty.stringProperty");
        QCOMPARE(expr.changed, false);
        QCOMPARE(expr.evaluate(), QVariant());

        object1.setObjectProperty(&object2);
        QCOMPARE(expr.changed, true);
        expr.changed = false;
        QCOMPARE(expr.evaluate(), QVariant("Dog"));

        object1.setObjectProperty(&object3);
        QCOMPARE(expr.changed, true);
        expr.changed = false;
        QCOMPARE(expr.evaluate(), QVariant("Cat"));

        object1.setObjectProperty(nullptr);
        QCOMPARE(expr.changed, true);
        expr.changed = false;
        QCOMPARE(expr.evaluate(), QVariant());

        object1.setObjectProperty(&object3);
        QCOMPARE(expr.changed, true);
        expr.changed = false;
        QCOMPARE(expr.evaluate(), QVariant("Cat"));

        object3.setStringProperty("Donkey");
        QCOMPARE(expr.changed, true);
        expr.changed = false;
        QCOMPARE(expr.evaluate(), QVariant("Donkey"));
    }
}

void tst_qqmlecmascript::dependenciesWithFunctions()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("dependenciesWithFunctions.qml"));

    QScopedPointer<QObject> object(component.create());
    QVERIFY2(object, qPrintable(component.errorString()));
    QVERIFY(!object->property("success").toBool());
    object->setProperty("value", 42);
    QVERIFY(object->property("success").toBool());
}

void tst_qqmlecmascript::deferredProperties()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deferredProperties.qml"));
    MyDeferredObject *object =
        qobject_cast<MyDeferredObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->value(), 0);
    QVERIFY(!object->objectProperty());
    QVERIFY(object->objectProperty2() != nullptr);
    qmlExecuteDeferred(object);
    QCOMPARE(object->value(), 10);
    QVERIFY(object->objectProperty() != nullptr);
    MyQmlObject *qmlObject =
        qobject_cast<MyQmlObject *>(object->objectProperty());
    QVERIFY(qmlObject != nullptr);
    QCOMPARE(qmlObject->value(), 10);
    object->setValue(19);
    QCOMPARE(qmlObject->value(), 19);

    delete object;
}

// Check errors on deferred properties are correctly emitted
void tst_qqmlecmascript::deferredPropertiesErrors()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deferredPropertiesErrors.qml"));
    MyDeferredObject *object =
        qobject_cast<MyDeferredObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->value(), 0);
    QVERIFY(!object->objectProperty());
    QVERIFY(!object->objectProperty2());

    QString warning = component.url().toString() + ":6:5: Unable to assign [undefined] to QObject*";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));

    qmlExecuteDeferred(object);

    delete object;
}

void tst_qqmlecmascript::deferredPropertiesInComponents()
{
    // Test that it works when the property is set inside and outside component
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deferredPropertiesInComponents.qml"));
    QObject *object = component.create();
    if (!object)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("value").value<int>(), 10);

    MyDeferredObject *defObjectA =
        qobject_cast<MyDeferredObject *>(object->property("deferredInside").value<QObject*>());
    QVERIFY(defObjectA != nullptr);
    QVERIFY(!defObjectA->objectProperty());

    qmlExecuteDeferred(defObjectA);
    QVERIFY(defObjectA->objectProperty() != nullptr);
    QCOMPARE(defObjectA->objectProperty()->property("value").value<int>(), 10);

    MyDeferredObject *defObjectB =
        qobject_cast<MyDeferredObject *>(object->property("deferredOutside").value<QObject*>());
    QVERIFY(defObjectB != nullptr);
    QVERIFY(!defObjectB->objectProperty());

    qmlExecuteDeferred(defObjectB);
    QVERIFY(defObjectB->objectProperty() != nullptr);
    QCOMPARE(defObjectB->objectProperty()->property("value").value<int>(), 10);

    delete object;
}

void tst_qqmlecmascript::deferredPropertiesInDestruction()
{
    //Test that the component does not get created at all if creation is deferred until the containing context is destroyed
    //Very specific operation ordering is needed for this to occur, currently accessing object from object destructor.
    //
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deferredPropertiesInDestruction.qml"));
    QObject *object = component.create();
    if (!object)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);
    delete object; //QTBUG-33112 was that this used to cause a crash
}

void tst_qqmlecmascript::extensionObjects()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("extensionObjects.qml"));
    MyExtendedObject *object =
        qobject_cast<MyExtendedObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->baseProperty(), 13);
    QCOMPARE(object->coreProperty(), 9);
    object->setProperty("extendedProperty", QVariant(11));
    object->setProperty("baseExtendedProperty", QVariant(92));
    QCOMPARE(object->coreProperty(), 11);
    QCOMPARE(object->baseProperty(), 92);

    MyExtendedObject *nested = qobject_cast<MyExtendedObject*>(qvariant_cast<QObject *>(object->property("nested")));
    QVERIFY(nested);
    QCOMPARE(nested->baseProperty(), 13);
    QCOMPARE(nested->coreProperty(), 9);
    nested->setProperty("extendedProperty", QVariant(11));
    nested->setProperty("baseExtendedProperty", QVariant(92));
    QCOMPARE(nested->coreProperty(), 11);
    QCOMPARE(nested->baseProperty(), 92);

    delete object;
}

void tst_qqmlecmascript::overrideExtensionProperties()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("extensionObjectsPropertyOverride.qml"));
    OverrideDefaultPropertyObject *object =
        qobject_cast<OverrideDefaultPropertyObject *>(component.create());
    QVERIFY(object != nullptr);
    QVERIFY(object->secondProperty() != nullptr);
    QVERIFY(!object->firstProperty());

    delete object;
}

void tst_qqmlecmascript::attachedProperties()
{
    QQmlEngine engine;

    {
        QQmlComponent component(&engine, testFileUrl("attachedProperty.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("a").toInt(), 19);
        QCOMPARE(object->property("b").toInt(), 19);
        QCOMPARE(object->property("c").toInt(), 19);
        QCOMPARE(object->property("d").toInt(), 19);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("attachedProperty.2.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("a").toInt(), 26);
        QCOMPARE(object->property("b").toInt(), 26);
        QCOMPARE(object->property("c").toInt(), 26);
        QCOMPARE(object->property("d").toInt(), 26);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("writeAttachedProperty.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QMetaObject::invokeMethod(object, "writeValue2");

        MyQmlAttachedObject *attached =
            qobject_cast<MyQmlAttachedObject *>(qmlAttachedPropertiesObject<MyQmlObject>(object));
        QVERIFY(attached != nullptr);

        QCOMPARE(attached->value2(), 9);
        delete object;
    }
}

void tst_qqmlecmascript::enums()
{
    QQmlEngine engine;

    // Existent enums
    {
    QQmlComponent component(&engine, testFileUrl("enums.1.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("enumProperty").toInt(), (int)MyQmlObject::EnumValue2);
    QCOMPARE(object->property("relatedEnumProperty").toInt(), (int)MyEnumContainer::RelatedValue);
    QCOMPARE(object->property("unrelatedEnumProperty").toInt(), (int)MyEnumContainer::RelatedValue);
    QCOMPARE(object->property("qtEnumProperty").toInt(), (int)Qt::CaseInsensitive);
    QCOMPARE(object->property("a").toInt(), 0);
    QCOMPARE(object->property("b").toInt(), 1);
    QCOMPARE(object->property("c").toInt(), 2);
    QCOMPARE(object->property("d").toInt(), 3);
    QCOMPARE(object->property("e").toInt(), 0);
    QCOMPARE(object->property("f").toInt(), 1);
    QCOMPARE(object->property("g").toInt(), 2);
    QCOMPARE(object->property("h").toInt(), 3);
    QCOMPARE(object->property("i").toInt(), 19);
    QCOMPARE(object->property("j").toInt(), 19);
    QCOMPARE(object->property("k").toInt(), 42);
    QCOMPARE(object->property("l").toInt(), 333);

    delete object;
    }
    // Non-existent enums
    {
    QUrl file = testFileUrl("enums.2.qml");
    QString w1 = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'MyEnum' for property 'MyUnregisteredEnumTypeObject::enumProperty'");
    QString w2 = QLatin1String("QQmlExpression: Expression ") + testFileUrl("enums.2.qml").toString() + QLatin1String(":9:5 depends on non-NOTIFYable properties:");
    QString w3 = QLatin1String("    MyUnregisteredEnumTypeObject::enumProperty");
    QString w4 = file.toString() + ":7:5: Unable to assign [undefined] to int";
    QString w5 = file.toString() + ":8:5: Unable to assign [undefined] to int";
    QString w6 = file.toString() + ":9:5: Unable to assign [undefined] to int";
    QString w7 = file.toString() + ":13:9: Unable to assign [undefined] to [unknown property type]";
    QString w8 = file.toString() + ":31:9: Unable to assign int to [unknown property type]";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w1));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w2));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w3));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w4));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w5));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w6));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w7));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w8));

    QQmlComponent component(&engine, testFileUrl("enums.2.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("a").toInt(), 0);
    QCOMPARE(object->property("b").toInt(), 0);
    QCOMPARE(object->property("c").toInt(), 0);

    QString w9 = file.toString() + ":18: Error: Cannot assign JavaScript function to [unknown property type]";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w9));
    QMetaObject::invokeMethod(object, "testAssignmentOne");

    QString w10 = file.toString() + ":21: Error: Cannot assign [undefined] to [unknown property type]";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w10));
    QMetaObject::invokeMethod(object, "testAssignmentTwo");

    QString w11 = file.toString() + ":24: Error: Cannot assign [undefined] to [unknown property type]";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w11));
    QMetaObject::invokeMethod(object, "testAssignmentThree");

    QString w12 = file.toString() + ":34: Error: Cannot assign int to an unregistered type";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w12));
    QMetaObject::invokeMethod(object, "testAssignmentFour");

    delete object;
    }
    // Enums as literals
    {
    QQmlComponent component(&engine, testFileUrl("enums.3.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    // check the values are what we expect
    QCOMPARE(object->property("a").toInt(), 4);
    QCOMPARE(object->property("b").toInt(), 5);
    QCOMPARE(object->property("c").toInt(), 9);
    QCOMPARE(object->property("d").toInt(), 13);
    QCOMPARE(object->property("e").toInt(), 2);
    QCOMPARE(object->property("f").toInt(), 3);
    QCOMPARE(object->property("h").toInt(), 2);
    QCOMPARE(object->property("i").toInt(), 3);
    QCOMPARE(object->property("j").toInt(), -1);
    QCOMPARE(object->property("k").toInt(), 42);

    // count of change signals
    QCOMPARE(object->property("ac").toInt(), 0);
    QCOMPARE(object->property("bc").toInt(), 0);
    QCOMPARE(object->property("cc").toInt(), 0);
    QCOMPARE(object->property("dc").toInt(), 0);
    QCOMPARE(object->property("ec").toInt(), 0);
    QCOMPARE(object->property("fc").toInt(), 0);
    QCOMPARE(object->property("hc").toInt(), 1); // namespace -> binding
    QCOMPARE(object->property("ic").toInt(), 1); // namespace -> binding
    QCOMPARE(object->property("jc").toInt(), 0);
    QCOMPARE(object->property("kc").toInt(), 0);

    delete object;
    }
}

void tst_qqmlecmascript::valueTypeFunctions()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("valueTypeFunctions.qml"));
    MyTypeObject *obj = qobject_cast<MyTypeObject*>(component.create());
    QVERIFY(obj != nullptr);
    QCOMPARE(obj->rectProperty(), QRect(0,0,100,100));
    QCOMPARE(obj->rectFProperty(), QRectF(0,0.5,100,99.5));

    delete obj;
}

/*
Tests that writing a constant to a property with a binding on it disables the
binding.
*/
void tst_qqmlecmascript::constantsOverrideBindings()
{
    QQmlEngine engine;

    // From ECMAScript
    {
        QQmlComponent component(&engine, testFileUrl("constantsOverrideBindings.1.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("c2").toInt(), 0);
        object->setProperty("c1", QVariant(9));
        QCOMPARE(object->property("c2").toInt(), 9);

        emit object->basicSignal();

        QCOMPARE(object->property("c2").toInt(), 13);
        object->setProperty("c1", QVariant(8));
        QCOMPARE(object->property("c2").toInt(), 13);

        delete object;
    }

    // During construction
    {
        QQmlComponent component(&engine, testFileUrl("constantsOverrideBindings.2.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("c1").toInt(), 0);
        QCOMPARE(object->property("c2").toInt(), 10);
        object->setProperty("c1", QVariant(9));
        QCOMPARE(object->property("c1").toInt(), 9);
        QCOMPARE(object->property("c2").toInt(), 10);

        delete object;
    }

#if 0
    // From C++
    {
        QQmlComponent component(&engine, testFileUrl("constantsOverrideBindings.3.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != 0);

        QCOMPARE(object->property("c2").toInt(), 0);
        object->setProperty("c1", QVariant(9));
        QCOMPARE(object->property("c2").toInt(), 9);

        object->setProperty("c2", QVariant(13));
        QCOMPARE(object->property("c2").toInt(), 13);
        object->setProperty("c1", QVariant(7));
        QCOMPARE(object->property("c1").toInt(), 7);
        QCOMPARE(object->property("c2").toInt(), 13);

        delete object;
    }
#endif

    // Using an alias
    {
        QQmlComponent component(&engine, testFileUrl("constantsOverrideBindings.4.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("c1").toInt(), 0);
        QCOMPARE(object->property("c3").toInt(), 10);
        object->setProperty("c1", QVariant(9));
        QCOMPARE(object->property("c1").toInt(), 9);
        QCOMPARE(object->property("c3").toInt(), 10);

        delete object;
    }
}

/*
Tests that assigning a binding to a property that already has a binding causes
the original binding to be disabled.
*/
void tst_qqmlecmascript::outerBindingOverridesInnerBinding()
{
    QQmlEngine engine;
    QQmlComponent component(&engine,
                           testFileUrl("outerBindingOverridesInnerBinding.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("c1").toInt(), 0);
    QCOMPARE(object->property("c2").toInt(), 0);
    QCOMPARE(object->property("c3").toInt(), 0);

    object->setProperty("c1", QVariant(9));
    QCOMPARE(object->property("c1").toInt(), 9);
    QCOMPARE(object->property("c2").toInt(), 0);
    QCOMPARE(object->property("c3").toInt(), 0);

    object->setProperty("c3", QVariant(8));
    QCOMPARE(object->property("c1").toInt(), 9);
    QCOMPARE(object->property("c2").toInt(), 8);
    QCOMPARE(object->property("c3").toInt(), 8);

    delete object;
}

/*
Access a non-existent attached object.

Tests for a regression where this used to crash.
*/
void tst_qqmlecmascript::nonExistentAttachedObject()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("nonExistentAttachedObject.qml"));

    QString warning = component.url().toString() + ":4:5: Unable to assign [undefined] to QString";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));

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

    delete object;
}

void tst_qqmlecmascript::scope()
{
    QQmlEngine engine;

    {
        QQmlComponent component(&engine, testFileUrl("scope.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test1").toInt(), 1);
        QCOMPARE(object->property("test2").toInt(), 2);
        QCOMPARE(object->property("test3").toString(), QString("1Test"));
        QCOMPARE(object->property("test4").toString(), QString("2Test"));
        QCOMPARE(object->property("test5").toInt(), 1);
        QCOMPARE(object->property("test6").toInt(), 1);
        QCOMPARE(object->property("test7").toInt(), 2);
        QCOMPARE(object->property("test8").toInt(), 2);
        QCOMPARE(object->property("test9").toInt(), 1);
        QCOMPARE(object->property("test10").toInt(), 3);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scope.2.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test1").toInt(), 19);
        QCOMPARE(object->property("test2").toInt(), 19);
        QCOMPARE(object->property("test3").toInt(), 14);
        QCOMPARE(object->property("test4").toInt(), 14);
        QCOMPARE(object->property("test5").toInt(), 24);
        QCOMPARE(object->property("test6").toInt(), 24);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scope.3.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test1").toBool(), true);
        QEXPECT_FAIL("", "Properties resolvable at compile time come before the global object, which is not 100% compatible with older QML versions", Continue);
        QCOMPARE(object->property("test2").toBool(), true);
        QCOMPARE(object->property("test3").toBool(), true);

        delete object;
    }

    // Signal argument scope
    {
        QQmlComponent component(&engine, testFileUrl("scope.4.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toInt(), 0);
        QCOMPARE(object->property("test2").toString(), QString());

        emit object->argumentSignal(13, "Argument Scope", 9, MyQmlObject::EnumValue4, Qt::RightButton);

        QCOMPARE(object->property("test").toInt(), 13);
        QCOMPARE(object->property("test2").toString(), QString("Argument Scope"));

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scope.5.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test1").toBool(), true);
        QCOMPARE(object->property("test2").toBool(), true);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scope.6.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toBool(), true);

        delete object;
    }
}

// In 4.7, non-library javascript files that had no imports shared the imports of their
// importing context
void tst_qqmlecmascript::importScope()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("importScope.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toInt(), 240);

    delete o;
}

/*
Tests that "any" type passes through a synthesized signal parameter.  This
is essentially a test of QQmlMetaType::copy()
*/
void tst_qqmlecmascript::signalParameterTypes()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalParameterTypes.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(object != nullptr);

    emit object->basicSignal();

    QCOMPARE(object->property("intProperty").toInt(), 10);
    QCOMPARE(object->property("realProperty").toReal(), 19.2);
    QVERIFY(object->property("colorProperty").value<QColor>() == QColor(255, 255, 0, 255));
    QVERIFY(object->property("variantProperty") == QVariant::fromValue(QColor(255, 0, 255, 255)));
    QVERIFY(object->property("enumProperty") == MyQmlObject::EnumValue3);
    QVERIFY(object->property("qtEnumProperty") == Qt::LeftButton);

    emit object->qjsValueEmittingSignal(QJSValue());
    QVERIFY(object->property("emittedQjsValueWasUndefined").toBool());
    emit object->qjsValueEmittingSignal(QJSValue(42));
    QVERIFY(!object->property("emittedQjsValueWasUndefined").toBool());
    QCOMPARE(object->property("emittedQjsValueAsInt").value<int>(), 42);

    delete object;
}

/*
Test that two JS objects for the same QObject compare as equal.
*/
void tst_qqmlecmascript::objectsCompareAsEqual()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("objectsCompareAsEqual.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test1").toBool(), true);
    QCOMPARE(object->property("test2").toBool(), true);
    QCOMPARE(object->property("test3").toBool(), true);
    QCOMPARE(object->property("test4").toBool(), true);
    QCOMPARE(object->property("test5").toBool(), true);

    delete object;
}

/*
Confirm bindings and alias properties can coexist.

Tests for a regression where the binding would not reevaluate.
*/
void tst_qqmlecmascript::aliasPropertyAndBinding()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("aliasPropertyAndBinding.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("c2").toInt(), 3);
    QCOMPARE(object->property("c3").toInt(), 3);

    object->setProperty("c2", QVariant(19));

    QCOMPARE(object->property("c2").toInt(), 19);
    QCOMPARE(object->property("c3").toInt(), 19);

    delete object;
}

/*
Ensure that we can write undefined value to an alias property,
and that the aliased property is reset correctly if possible.
*/
void tst_qqmlecmascript::aliasPropertyReset()
{
    QQmlEngine engine;
    QObject *object = nullptr;

    // test that a manual write (of undefined) to a resettable aliased property succeeds
    QQmlComponent c1(&engine, testFileUrl("aliasreset/aliasPropertyReset.1.qml"));
    object = c1.create();
    QVERIFY(object != nullptr);
    QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0);
    QCOMPARE(object->property("aliasIsUndefined"), QVariant(false));
    QMetaObject::invokeMethod(object, "resetAliased");
    QVERIFY(!object->property("sourceComponentAlias").value<QQmlComponent*>());
    QCOMPARE(object->property("aliasIsUndefined"), QVariant(true));
    delete object;

    // test that a manual write (of undefined) to a resettable alias property succeeds
    QQmlComponent c2(&engine, testFileUrl("aliasreset/aliasPropertyReset.2.qml"));
    object = c2.create();
    QVERIFY(object != nullptr);
    QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0);
    QCOMPARE(object->property("loaderSourceComponentIsUndefined"), QVariant(false));
    QMetaObject::invokeMethod(object, "resetAlias");
    QVERIFY(!object->property("sourceComponentAlias").value<QQmlComponent*>());
    QCOMPARE(object->property("loaderSourceComponentIsUndefined"), QVariant(true));
    delete object;

    // test that an alias to a bound property works correctly
    QQmlComponent c3(&engine, testFileUrl("aliasreset/aliasPropertyReset.3.qml"));
    object = c3.create();
    QVERIFY(object != nullptr);
    QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0);
    QCOMPARE(object->property("loaderOneSourceComponentIsUndefined"), QVariant(false));
    QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined"), QVariant(false));
    QMetaObject::invokeMethod(object, "resetAlias");
    QVERIFY(!object->property("sourceComponentAlias").value<QQmlComponent*>());
    QCOMPARE(object->property("loaderOneSourceComponentIsUndefined"), QVariant(true));
    QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined"), QVariant(false));
    delete object;

    // test that a manual write (of undefined) to a resettable alias property
    // whose aliased property's object has been deleted, does not crash.
    QQmlComponent c4(&engine, testFileUrl("aliasreset/aliasPropertyReset.4.qml"));
    object = c4.create();
    QVERIFY(object != nullptr);
    QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0);
    QObject *loader = object->findChild<QObject*>("loader");
    QVERIFY(loader != nullptr);
    delete loader;
    QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() == 0); // deletion should have caused value unset.
    QMetaObject::invokeMethod(object, "resetAlias"); // shouldn't crash.
    QVERIFY(!object->property("sourceComponentAlias").value<QQmlComponent*>());
    QMetaObject::invokeMethod(object, "setAlias");   // shouldn't crash, and shouldn't change value (since it's no longer referencing anything).
    QVERIFY(!object->property("sourceComponentAlias").value<QQmlComponent*>());
    delete object;

    // test that binding an alias property to an undefined value works correctly
    QQmlComponent c5(&engine, testFileUrl("aliasreset/aliasPropertyReset.5.qml"));
    object = c5.create();
    QVERIFY(object != nullptr);
    QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() == 0); // bound to undefined value.
    delete object;

    // test that a manual write (of undefined) to a non-resettable property fails properly
    QUrl url = testFileUrl("aliasreset/aliasPropertyReset.error.1.qml");
    QString warning1 = url.toString() + QLatin1String(":15: Error: Cannot assign [undefined] to int");
    QQmlComponent e1(&engine, url);
    object = e1.create();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("intAlias").value<int>(), 12);
    QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false));
    QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
    QMetaObject::invokeMethod(object, "resetAlias");
    QCOMPARE(object->property("intAlias").value<int>(), 12);
    QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false));
    delete object;
}

void tst_qqmlecmascript::componentCreation_data()
{
    QTest::addColumn<QString>("method");
    QTest::addColumn<QString>("creationError");
    QTest::addColumn<QString>("createdParent");

    QTest::newRow("url")
        << "url"
        << ""
        << "";
    QTest::newRow("urlMode")
        << "urlMode"
        << ""
        << "";
    QTest::newRow("urlParent")
        << "urlParent"
        << ""
        << "obj";
    QTest::newRow("urlNullParent")
        << "urlNullParent"
        << ""
        << "null";
    QTest::newRow("urlModeParent")
        << "urlModeParent"
        << ""
        << "obj";
    QTest::newRow("urlModeNullParent")
        << "urlModeNullParent"
        << ""
        << "null";
    QTest::newRow("invalidSecondArg")
        << "invalidSecondArg"
        << ":40: Error: Qt.createComponent(): Invalid arguments"
        << "";
    QTest::newRow("invalidThirdArg")
        << "invalidThirdArg"
        << ":45: Error: Qt.createComponent(): Invalid parent object"
        << "";
    QTest::newRow("invalidMode")
        << "invalidMode"
        << ":50: Error: Qt.createComponent(): Invalid arguments"
        << "";
}

/*
Test using createComponent to dynamically generate a component.
*/
void tst_qqmlecmascript::componentCreation()
{
    QFETCH(QString, method);
    QFETCH(QString, creationError);
    QFETCH(QString, createdParent);

    QQmlEngine engine;
    QUrl testUrl(testFileUrl("componentCreation.qml"));

    if (!creationError.isEmpty()) {
        QString warning = testUrl.toString() + creationError;
        QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    }

    QQmlComponent component(&engine, testUrl);
    QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
    QVERIFY(object != nullptr);

    QMetaObject::invokeMethod(object.get(), method.toUtf8());
    QQmlComponent *created = object->componentProperty();

    if (creationError.isEmpty()) {
        QVERIFY(created);

        QObject *expectedParent = reinterpret_cast<QObject *>(quintptr(-1));
        if (createdParent == QLatin1String("obj")) {
            expectedParent = object.get();
        } else if ((createdParent == QLatin1String("null")) || createdParent.isEmpty()) {
            expectedParent = nullptr;
        }
        QCOMPARE(created->parent(), expectedParent);
    }
}

void tst_qqmlecmascript::dynamicCreation_data()
{
    QTest::addColumn<QString>("method");
    QTest::addColumn<QString>("createdName");

    QTest::newRow("One") << "createOne" << "objectOne";
    QTest::newRow("Two") << "createTwo" << "objectTwo";
    QTest::newRow("Three") << "createThree" << "objectThree";
}

/*
Test using createQmlObject to dynamically generate an item
Also using createComponent is tested.
*/
void tst_qqmlecmascript::dynamicCreation()
{
    QFETCH(QString, method);
    QFETCH(QString, createdName);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("dynamicCreation.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);

    QMetaObject::invokeMethod(object, method.toUtf8());
    QObject *created = object->objectProperty();
    QVERIFY(created);
    QCOMPARE(created->objectName(), createdName);

    delete object;
}

/*
   Tests the destroy function
*/
void tst_qqmlecmascript::dynamicDestruction()
{
    QQmlEngine engine;

    {
    QQmlComponent component(&engine, testFileUrl("dynamicDeletion.qml"));
    QPointer<MyQmlObject> object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);
    QPointer<QObject> createdQmlObject = nullptr;

    QMetaObject::invokeMethod(object, "create");
    createdQmlObject = object->objectProperty();
    QVERIFY(createdQmlObject);
    QCOMPARE(createdQmlObject->objectName(), QString("emptyObject"));

    QMetaObject::invokeMethod(object, "killOther");
    QVERIFY(createdQmlObject);

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();
    QVERIFY(createdQmlObject);
    for (int ii = 0; createdQmlObject && ii < 50; ++ii) { // After 5 seconds we should give up
        if (createdQmlObject) {
            QTest::qWait(100);
            QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
            QCoreApplication::processEvents();
        }
    }
    QVERIFY(!createdQmlObject);

    QQmlEngine::setObjectOwnership(object, QQmlEngine::JavaScriptOwnership);
    QMetaObject::invokeMethod(object, "killMe");
    QVERIFY(object);
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();
    QVERIFY(!object);
    }

    {
    QQmlComponent component(&engine, testFileUrl("dynamicDeletion.2.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty")));

    QMetaObject::invokeMethod(o, "create");

    QVERIFY(qvariant_cast<QObject*>(o->property("objectProperty")) != 0);

    QMetaObject::invokeMethod(o, "destroy");

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();

    QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty")));

    delete o;
    }

    {
    // QTBUG-23451
    QPointer<QObject> createdQmlObject = nullptr;
    QQmlComponent component(&engine, testFileUrl("dynamicDeletion.3.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty")));
    QMetaObject::invokeMethod(o, "create");
    createdQmlObject = qvariant_cast<QObject*>(o->property("objectProperty"));
    QVERIFY(createdQmlObject);
    QMetaObject::invokeMethod(o, "destroy");
    QCOMPARE(qvariant_cast<bool>(o->property("test")), false);
    for (int ii = 0; createdQmlObject && ii < 50; ++ii) { // After 5 seconds we should give up
        QTest::qWait(100);
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();
    }
    QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty")));
    QCOMPARE(qvariant_cast<bool>(o->property("test")), true);
    delete o;
    }
}

/*
   tests that id.toString() works
*/
void tst_qqmlecmascript::objectToString()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qmlToString.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "testToString");
    QVERIFY(object->stringProperty().startsWith("MyQmlObject_QML_"));
    QVERIFY(object->stringProperty().endsWith(", \"objName\")"));

    delete object;
}

/*
  tests that id.hasOwnProperty() works
*/
void tst_qqmlecmascript::objectHasOwnProperty()
{
    QUrl url = testFileUrl("qmlHasOwnProperty.qml");
    QString warning1 = url.toString() + ":59: TypeError: Cannot call method 'hasOwnProperty' of undefined";
    QString warning2 = url.toString() + ":64: TypeError: Cannot call method 'hasOwnProperty' of undefined";
    QString warning3 = url.toString() + ":69: TypeError: Cannot call method 'hasOwnProperty' of undefined";

    QQmlEngine engine;
    QQmlComponent component(&engine, url);
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    // test QObjects in QML
    QMetaObject::invokeMethod(object, "testHasOwnPropertySuccess");
    QVERIFY(object->property("result").value<bool>());
    QMetaObject::invokeMethod(object, "testHasOwnPropertyFailure");
    QVERIFY(!object->property("result").value<bool>());

    // now test other types in QML
    QObject *child = object->findChild<QObject*>("typeObj");
    QVERIFY(child != nullptr);
    QMetaObject::invokeMethod(child, "testHasOwnPropertySuccess");
    QCOMPARE(child->property("valueTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("valueTypeHasOwnProperty2").toBool(), true);
    QCOMPARE(child->property("variantTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("stringTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("listTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("emptyListTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("enumTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("typenameHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("typenameHasOwnProperty2").toBool(), true);
    QCOMPARE(child->property("singletonTypeTypeHasOwnProperty").toBool(), true);
    QCOMPARE(child->property("singletonTypePropertyTypeHasOwnProperty").toBool(), true);

    QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
    QMetaObject::invokeMethod(child, "testHasOwnPropertyFailureOne");
    QCOMPARE(child->property("enumNonValueHasOwnProperty").toBool(), false);
    QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData());
    QMetaObject::invokeMethod(child, "testHasOwnPropertyFailureTwo");
    QCOMPARE(child->property("singletonTypeNonPropertyHasOwnProperty").toBool(), false);
    QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData());
    QMetaObject::invokeMethod(child, "testHasOwnPropertyFailureThree");
    QCOMPARE(child->property("listAtInvalidHasOwnProperty").toBool(), false);

    delete object;
}

/*
Tests bindings that indirectly cause their own deletion work.

This test is best run under valgrind to ensure no invalid memory access occur.
*/
void tst_qqmlecmascript::selfDeletingBinding()
{
    QQmlEngine engine;

    {
        QQmlComponent component(&engine, testFileUrl("selfDeletingBinding.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        object->setProperty("triggerDelete", true);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("selfDeletingBinding.2.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        object->setProperty("triggerDelete", true);
        delete object;
    }
}

/*
Test that extended object properties can be accessed.

This test a regression where this used to crash.  The issue was specificially
for extended objects that did not include a synthesized meta object (so non-root
and no synthesiszed properties).
*/
void tst_qqmlecmascript::extendedObjectPropertyLookup()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("extendedObjectPropertyLookup.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    delete object;
}

/*
Test that extended object properties can be accessed correctly.
*/
void tst_qqmlecmascript::extendedObjectPropertyLookup2()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("extendedObjectPropertyLookup2.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QVariant returnValue;
    QVERIFY(QMetaObject::invokeMethod(object, "getValue", Q_RETURN_ARG(QVariant, returnValue)));
    QCOMPARE(returnValue.toInt(), 42);

    delete object;
}

/*
Test failure when trying to create and uncreatable extended type object.
 */
void tst_qqmlecmascript::uncreatableExtendedObjectFailureCheck()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("uncreatableExtendedObjectFailureCheck.qml"));

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

/*
Test that an subclass of an uncreatable extended object contains all the required properties.
 */
void tst_qqmlecmascript::extendedObjectPropertyLookup3()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("extendedObjectPropertyLookup3.qml"));

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

    QVariant returnValue;
    QVERIFY(QMetaObject::invokeMethod(object, "getAbstractProperty", Q_RETURN_ARG(QVariant, returnValue)));
    QCOMPARE(returnValue.toInt(), -1);
    QVERIFY(QMetaObject::invokeMethod(object, "getImplementedProperty", Q_RETURN_ARG(QVariant, returnValue)));
    QCOMPARE(returnValue.toInt(), 883);
    QVERIFY(QMetaObject::invokeMethod(object, "getExtendedProperty", Q_RETURN_ARG(QVariant, returnValue)));
    QCOMPARE(returnValue.toInt(), 42);

    delete object;
}
/*
Test file/lineNumbers for binding/Script errors.
*/
void tst_qqmlecmascript::scriptErrors()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("scriptErrors.qml"));
    QString url = component.url().toString();

    QString warning1 = url.left(url.length() - 3) + "js:2: Error: Invalid write to global property \"a\"";
    QString warning2 = url + ":5: ReferenceError: a is not defined";
    QString warning3 = url.left(url.length() - 3) + "js:4: Error: Invalid write to global property \"a\"";
    QString warning4 = url + ":13: ReferenceError: a is not defined";
    QString warning5 = url + ":11: ReferenceError: a is not defined";
    QString warning6 = url + ":10:5: Unable to assign [undefined] to int";
    QString warning7 = url + ":15: TypeError: Cannot assign to read-only property \"trueProperty\"";
    QString warning8 = url + ":16: Error: Cannot assign to non-existent property \"fakeProperty\"";

    QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, warning5.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, warning6.toLatin1().constData());
    MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(object != nullptr);

    QTest::ignoreMessage(QtWarningMsg, warning4.toLatin1().constData());
    emit object->basicSignal();

    QTest::ignoreMessage(QtWarningMsg, warning7.toLatin1().constData());
    emit object->anotherBasicSignal();

    QTest::ignoreMessage(QtWarningMsg, warning8.toLatin1().constData());
    emit object->thirdBasicSignal();

    delete object;
}

/*
Test file/lineNumbers for inline functions.
*/
void tst_qqmlecmascript::functionErrors()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("functionErrors.qml"));
    QString url = component.url().toString();

    QString warning = url + ":5: Error: Invalid write to global property \"a\"";

    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());

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

    // test that if an exception occurs while invoking js function from cpp, it is reported as expected.
    QQmlComponent componentTwo(&engine, testFileUrl("scarceResourceFunctionFail.var.qml"));
    url = componentTwo.url().toString();
    object = componentTwo.create();
    QVERIFY(object != nullptr);

    QObject *resource = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    warning = url + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function");
    warning = warning.arg(QString::number(quintptr(resource), 16));
    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); // we expect a meaningful warning to be printed.
    QMetaObject::invokeMethod(object, "retrieveScarceResource");
    delete object;
}

/*
Test various errors that can occur when assigning a property from script
*/
void tst_qqmlecmascript::propertyAssignmentErrors()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyAssignmentErrors.qml"));

    QString url = component.url().toString();

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

    QCOMPARE(object->property("test1").toBool(), true);
    QCOMPARE(object->property("test2").toBool(), true);

    delete object;
}

/*
Test bindings still work when the reeval is triggered from within
a signal script.
*/
void tst_qqmlecmascript::signalTriggeredBindings()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalTriggeredBindings.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("base").toReal(), 50.);
    QCOMPARE(object->property("test1").toReal(), 50.);
    QCOMPARE(object->property("test2").toReal(), 50.);

    object->basicSignal();

    QCOMPARE(object->property("base").toReal(), 200.);
    QCOMPARE(object->property("test1").toReal(), 200.);
    QCOMPARE(object->property("test2").toReal(), 200.);

    object->argumentSignal(10, QString(), 10, MyQmlObject::EnumValue4, Qt::RightButton);

    QCOMPARE(object->property("base").toReal(), 400.);
    QCOMPARE(object->property("test1").toReal(), 400.);
    QCOMPARE(object->property("test2").toReal(), 400.);

    delete object;
}

/*
Test that list properties can be iterated from ECMAScript
*/
void tst_qqmlecmascript::listProperties()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("listProperties.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test1").toInt(), 21);
    QCOMPARE(object->property("test2").toInt(), 2);
    QCOMPARE(object->property("test3").toBool(), true);
    QCOMPARE(object->property("test4").toBool(), true);

    delete object;
}

void tst_qqmlecmascript::exceptionClearsOnReeval()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("exceptionClearsOnReeval.qml"));
    QString url = component.url().toString();

    QString warning = url + ":4: TypeError: Cannot read property 'objectProperty' of null";

    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toBool(), false);

    MyQmlObject object2;
    MyQmlObject object3;
    object2.setObjectProperty(&object3);
    object->setObjectProperty(&object2);

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

void tst_qqmlecmascript::exceptionSlotProducesWarning()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("exceptionProducesWarning.qml"));
    QString url = component.url().toString();

    QString warning = component.url().toString() + ":6: Error: JS exception";

    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);
    delete object;
}

void tst_qqmlecmascript::exceptionBindingProducesWarning()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("exceptionProducesWarning2.qml"));
    QString url = component.url().toString();

    QString warning = component.url().toString() + ":5: Error: JS exception";

    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);
    delete object;
}

void tst_qqmlecmascript::compileInvalidBinding()
{
    // QTBUG-23387: ensure that invalid bindings don't cause a crash.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("v8bindingException.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    delete object;
}

// Check that transient binding errors are not displayed
void tst_qqmlecmascript::transientErrors()
{
    QQmlEngine engine;

    {
    QQmlComponent component(&engine, testFileUrl("transientErrors.qml"));

    QQmlTestMessageHandler messageHandler;

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

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));

    delete object;
    }

    // One binding erroring multiple times, but then resolving
    {
    QQmlComponent component(&engine, testFileUrl("transientErrors.2.qml"));

    QQmlTestMessageHandler messageHandler;

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

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));

    delete object;
    }
}

// Check that errors during shutdown are minimized
void tst_qqmlecmascript::shutdownErrors()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("shutdownErrors.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QQmlTestMessageHandler messageHandler;

    delete object;

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
}

void tst_qqmlecmascript::compositePropertyType()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("compositePropertyType.qml"));

    QTest::ignoreMessage(QtDebugMsg, "hello world");
    QObject *object = qobject_cast<QObject *>(component.create());
    delete object;
}

// QTBUG-5759
void tst_qqmlecmascript::jsObject()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("jsObject.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toInt(), 92);

    delete object;
}

void tst_qqmlecmascript::undefinedResetsProperty()
{
    QQmlEngine engine;

    {
    QQmlComponent component(&engine, testFileUrl("undefinedResetsProperty.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("resettableProperty").toInt(), 92);

    object->setProperty("setUndefined", true);

    QCOMPARE(object->property("resettableProperty").toInt(), 13);

    object->setProperty("setUndefined", false);

    QCOMPARE(object->property("resettableProperty").toInt(), 92);

    delete object;
    }
    {
    QQmlComponent component(&engine, testFileUrl("undefinedResetsProperty.2.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("resettableProperty").toInt(), 19);

    QMetaObject::invokeMethod(object, "doReset");

    QCOMPARE(object->property("resettableProperty").toInt(), 13);

    delete object;
    }
}

// Aliases to variant properties should work
void tst_qqmlecmascript::qtbug_22464()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_22464.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

void tst_qqmlecmascript::qtbug_21580()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_21580.qml"));

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

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

// Causes a v8 binding, but not all v8 bindings to be destroyed during evaluation
void tst_qqmlecmascript::singleV8BindingDestroyedDuringEvaluation()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("singleV8BindingDestroyedDuringEvaluation.qml"));

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

// QTBUG-6781
void tst_qqmlecmascript::bug1()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("bug.1.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toInt(), 14);

    object->setProperty("a", 11);

    QCOMPARE(object->property("test").toInt(), 3);

    object->setProperty("b", true);

    QCOMPARE(object->property("test").toInt(), 9);

    delete object;
}

#ifndef QT_NO_WIDGETS
void tst_qqmlecmascript::bug2()
{
    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.setData("import Qt.test 1.0;\nQPlainTextEdit { width: 100 }", QUrl());

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

    delete object;
}
#endif

// Don't crash in createObject when the component has errors.
void tst_qqmlecmascript::dynamicCreationCrash()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("dynamicCreation.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
    QVERIFY(object != nullptr);

    QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
    QMetaObject::invokeMethod(object, "dontCrash");
    QObject *created = object->objectProperty();
    QVERIFY(!created);

    delete object;
}

// ownership transferred to JS, ensure that GC runs the dtor
void tst_qqmlecmascript::dynamicCreationOwnership()
{
    int dtorCount = 0;
    int expectedDtorCount = 1; // start at 1 since we expect mdcdo to dtor too.

    // allow the engine to go out of scope too.
    {
        QQmlEngine dcoEngine;
        QQmlComponent component(&dcoEngine, testFileUrl("dynamicCreationOwnership.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        MyDynamicCreationDestructionObject *mdcdo = object->findChild<MyDynamicCreationDestructionObject*>("mdcdo");
        QVERIFY(mdcdo != nullptr);
        mdcdo->setDtorCount(&dtorCount);

        for (int i = 1; i < 105; ++i, ++expectedDtorCount) {
            QMetaObject::invokeMethod(object, "dynamicallyCreateJsOwnedObject");
            if (i % 90 == 0) {
                // we do this once manually, but it should be done automatically
                // when the engine goes out of scope (since it should gc in dtor)
                QMetaObject::invokeMethod(object, "performGc");
            }
            if (i % 10 == 0) {
                QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
                QCoreApplication::processEvents();
            }
        }

        delete object;
    }
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();
    QCOMPARE(dtorCount, expectedDtorCount);
}

void tst_qqmlecmascript::regExpBug()
{
    QQmlEngine engine;

    //QTBUG-9367
    {
        QQmlComponent component(&engine, testFileUrl("regExp.qml"));
        MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
        QVERIFY(object != nullptr);
        QCOMPARE(object->regExp().pattern(), QLatin1String("[a-zA-z]"));
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("regularExpression.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(!object.isNull());
        QCOMPARE(object->regularExpression().pattern(), QLatin1String("[a-zA-z]"));
    }

    //QTBUG-23068
    {
        QString err = QString(QLatin1String("%1:6 Invalid property assignment: regular expression expected; use /pattern/ syntax\n")).arg(testFileUrl("regExp.2.qml").toString());
        QQmlComponent component(&engine, testFileUrl("regExp.2.qml"));
        QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
        MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
        QVERIFY(!object);
        QCOMPARE(component.errorString(), err);
    }

    {
        const QString err = QString::fromLatin1("%1:6 Invalid property assignment: "
                                                "regular expression expected; "
                                                "use /pattern/ syntax\n")
                                    .arg(testFileUrl("regularExpression.2.qml").toString());
        QQmlComponent component(&engine, testFileUrl("regularExpression.2.qml"));
        QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
        MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create());
        QVERIFY(!object);
        QCOMPARE(component.errorString(), err);
    }
}

static inline bool evaluate_error(QV4::ExecutionEngine *v4, const QV4::Value &o, const char *source)
{
    QString functionSource = QLatin1String("(function(object) { return ") +
                             QLatin1String(source) + QLatin1String(" })");

    QV4::Scope scope(v4);
    QV4::Script program(QV4::ScopedContext(scope, scope.engine->rootContext()), QV4::Compiler::ContextType::Eval, functionSource);
    program.inheritContext = true;

    QV4::ScopedFunctionObject function(scope, program.run());
    if (scope.engine->hasException) {
        scope.engine->catchException();
        return true;
    }
    QV4::JSCallData jsCallData(scope, 1);
    jsCallData->args[0] = o;
    *jsCallData->thisObject = v4->global();
    function->call(jsCallData);
    if (scope.engine->hasException) {
        scope.engine->catchException();
        return true;
    }
    return false;
}

static inline bool evaluate_value(QV4::ExecutionEngine *v4, const QV4::Value &o,
                                  const char *source, const QV4::Value &result)
{
    QString functionSource = QLatin1String("(function(object) { return ") +
                             QLatin1String(source) + QLatin1String(" })");

    QV4::Scope scope(v4);
    QV4::Script program(QV4::ScopedContext(scope, scope.engine->rootContext()), QV4::Compiler::ContextType::Eval, functionSource);
    program.inheritContext = true;

    QV4::ScopedFunctionObject function(scope, program.run());
    if (scope.engine->hasException) {
        scope.engine->catchException();
        return false;
    }
    if (!function)
        return false;

    QV4::ScopedValue value(scope);
    QV4::JSCallData jsCallData(scope, 1);
    jsCallData->args[0] = o;
    *jsCallData->thisObject = v4->global();
    value = function->call(jsCallData);
    if (scope.engine->hasException) {
        scope.engine->catchException();
        return false;
    }
    return QV4::Runtime::StrictEqual::call(value, result);
}

static inline QV4::ReturnedValue evaluate(QV4::ExecutionEngine *v4, const QV4::Value &o,
                                          const char *source)
{
    QString functionSource = QLatin1String("(function(object) { return ") +
                             QLatin1String(source) + QLatin1String(" })");

    QV4::Scope scope(v4);

    QV4::Script program(QV4::ScopedContext(scope, scope.engine->rootContext()), QV4::Compiler::ContextType::Eval, functionSource);
    program.inheritContext = true;

    QV4::ScopedFunctionObject function(scope, program.run());
    if (scope.engine->hasException) {
        scope.engine->catchException();
        return QV4::Encode::undefined();
    }
    if (!function)
        return QV4::Encode::undefined();
    QV4::JSCallData jsCallData(scope, 1);
    jsCallData->args[0] = o;
    *jsCallData->thisObject = v4->global();
    QV4::ScopedValue result(scope, function->call(jsCallData));
    if (scope.engine->hasException) {
        scope.engine->catchException();
        return QV4::Encode::undefined();
    }
    return result->asReturnedValue();
}

#define EVALUATE_ERROR(source) evaluate_error(engine, object, source)
#define EVALUATE_VALUE(source, result) evaluate_value(engine, object, source, result)
#define EVALUATE(source) evaluate(engine, object, source)

void tst_qqmlecmascript::callQtInvokables()
{
    // This object has JS ownership, as the call to method_NoArgs_QObject() in this test will return
    // it, which will set the indestructible flag to false.
    MyInvokableObject *o = new MyInvokableObject();

    QQmlEngine qmlengine;

    QV4::ExecutionEngine *engine = qmlengine.handle();
    QV4::Scope scope(engine);

    QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine, o));

    // Non-existent methods
    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_nonexistent()"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_nonexistent(10, 11)"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    // Insufficient arguments
    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_int()"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_intint(10)"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    // Excessive arguments
    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(10, 11)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(10));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_intint(10, 11, 12)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 9);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(10));
    QCOMPARE(o->actuals().at(1), QVariant(11));

    // Test return types
    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_NoArgs()", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 0);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_NoArgs_int()", QV4::Primitive::fromInt32(6)));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 1);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_NoArgs_real()", QV4::Primitive::fromDouble(19.75)));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 2);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    {
    QV4::ScopedValue ret(scope, EVALUATE("object.method_NoArgs_QPointF()"));
    QVERIFY(!ret->isUndefined());
    QCOMPARE(scope.engine->toVariant(ret, -1), QVariant(QPointF(123, 4.5)));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 3);
    QCOMPARE(o->actuals().count(), 0);
    }

    o->reset();
    {
    QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, EVALUATE("object.method_NoArgs_QObject()"));
    QVERIFY(qobjectWrapper);
    QCOMPARE(qobjectWrapper->object(), (QObject *)o);
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 4);
    QCOMPARE(o->actuals().count(), 0);
    }

    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_NoArgs_unknown()"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    {
    QV4::ScopedValue ret(scope, EVALUATE("object.method_NoArgs_QScriptValue()"));
    QVERIFY(ret->isString());
    QCOMPARE(ret->toQStringNoThrow(), QString("Hello world"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 6);
    QCOMPARE(o->actuals().count(), 0);
    }

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_NoArgs_QVariant()", QV4::ScopedValue(scope, scope.engine->newString("QML rocks"))));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 7);
    QCOMPARE(o->actuals().count(), 0);

    // Test arg types
    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(94)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(94));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(\"94\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(94));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(\"not a number\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(0));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(0));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(0));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_int(object)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 8);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(0));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_intint(122, 9)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 9);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(122));
    QCOMPARE(o->actuals().at(1), QVariant(9));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_real(94.3)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 10);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(94.3));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_real(\"94.3\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 10);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(94.3));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_real(\"not a number\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 10);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qIsNaN(o->actuals().at(0).toDouble()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_real(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 10);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(0));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_real(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 10);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qIsNaN(o->actuals().at(0).toDouble()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_real(object)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 10);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qIsNaN(o->actuals().at(0).toDouble()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QString(\"Hello world\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 11);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant("Hello world"));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QString(19)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 11);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant("19"));

    o->reset();
    {
    QString expected = "MyInvokableObject(0x" + QString::number((quintptr)o, 16) + ")";
    QVERIFY(EVALUATE_VALUE("object.method_QString(object)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 11);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(expected));
    }

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QString(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 11);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QString()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QString(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 11);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QString()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QPointF(0)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 12);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QPointF()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QPointF(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 12);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QPointF()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QPointF(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 12);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QPointF()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QPointF(object)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 12);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QPointF()));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QPointF(object.method_get_QPointF())", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 12);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QPointF(99.3, -10.2)));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QPointF(object.method_get_QPoint())", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 12);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QPointF(9, 12)));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QObject(0)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 13);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QObject(\"Hello world\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 13);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QObject(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 13);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 13);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QObject(object)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 13);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)o));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 14);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).isNull());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 14);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).isUndefined());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(19)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 14);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).strictlyEquals(QJSValue(19)));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QScriptValue([19, 20])", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 14);
    QCOMPARE(o->actuals().count(), 1);
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).isArray());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(4, null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 15);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(4));
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).isNull());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(8, undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 15);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(8));
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).isUndefined());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(3, 19)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 15);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(3));
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).strictlyEquals(QJSValue(19)));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(44, [19, 20])", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 15);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(44));
    QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).isArray());

    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_overload()"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload(10)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 16);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(10));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload(10, 11)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 17);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(10));
    QCOMPARE(o->actuals().at(1), QVariant(11));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload(\"Hello\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 18);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(QString("Hello")));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_with_enum(9)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 19);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(9));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_default(10)", QV4::Primitive::fromInt32(19)));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 20);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(10));
    QCOMPARE(o->actuals().at(1), QVariant(19));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_default(10, 13)", QV4::Primitive::fromInt32(13)));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 20);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(10));
    QCOMPARE(o->actuals().at(1), QVariant(13));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_inherited(9)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -3);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(o->actuals().at(0), QVariant(9));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QVariant(9)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 21);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(9));
    QCOMPARE(o->actuals().at(1), QVariant());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QVariant(\"Hello\", \"World\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 21);
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(QString("Hello")));
    QCOMPARE(o->actuals().at(1), QVariant(QString("World")));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonObject({foo:123})", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 22);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonObject>(o->actuals().at(0)), QJsonDocument::fromJson("{\"foo\":123}").object());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonArray([123])", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 23);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonArray>(o->actuals().at(0)), QJsonDocument::fromJson("[123]").array());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(123)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(123));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(42.35)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(42.35));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue('ciao')", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QStringLiteral("ciao")));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(true)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(true));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(false)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(false));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Null));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 24);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Undefined));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload({foo:123})", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 25);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonObject>(o->actuals().at(0)), QJsonDocument::fromJson("{\"foo\":123}").object());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload([123])", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 26);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonArray>(o->actuals().at(0)), QJsonDocument::fromJson("[123]").array());

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload(null)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 27);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Null));

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_overload(undefined)", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 27);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Undefined));

    o->reset();
    QVERIFY(EVALUATE_ERROR("object.method_unknown(null)"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), -1);
    QCOMPARE(o->actuals().count(), 0);

    o->reset();
    QVERIFY(EVALUATE_VALUE("object.method_QByteArray(\"Hello\")", QV4::Primitive::undefinedValue()));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 29);
    QCOMPARE(o->actuals().count(), 1);
    QCOMPARE(qvariant_cast<QByteArray>(o->actuals().at(0)), QByteArray("Hello"));

    o->reset();
    QV4::ScopedValue ret(scope, EVALUATE("object.method_intQJSValue(123, function() { return \"Hello world!\";})"));
    QCOMPARE(o->error(), false);
    QCOMPARE(o->invoked(), 30);
    QVERIFY(ret->isString());
    QCOMPARE(ret->toQStringNoThrow(), QString("Hello world!"));
    QCOMPARE(o->actuals().count(), 2);
    QCOMPARE(o->actuals().at(0), QVariant(123));
    QJSValue callback = qvariant_cast<QJSValue>(o->actuals().at(1));
    QVERIFY(!callback.isNull());
    QVERIFY(callback.isCallable());
}

void tst_qqmlecmascript::resolveClashingProperties()
{
    QScopedPointer<ClashingNames> o(new ClashingNames());
    QQmlEngine qmlengine;

    QV4::ExecutionEngine *engine = qmlengine.handle();
    QV4::Scope scope(engine);

    QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine, o.get()));
    QV4::ObjectIterator it(scope, object->as<QV4::Object>(), QV4::ObjectIterator::EnumerableOnly);
    QV4::ScopedValue name(scope);
    QV4::ScopedValue value(scope);

    bool seenProperty = false;
    bool seenMethod = false;
    while (true) {
        QV4::Value v;
        name = it.nextPropertyNameAsString(&v);
        if (name->isNull())
            break;
        QString key = name->toQStringNoThrow();
        if (key == QLatin1String("clashes")) {
            value = v;
            QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(engine, value));
            QString type = typeString->toQStringNoThrow();
            if (type == QLatin1String("boolean")) {
                QVERIFY(!seenProperty);
                seenProperty = true;
            } else if (type == QLatin1String("function")) {
                QVERIFY(!seenMethod);
                seenMethod = true;
            } else {
                QFAIL(qPrintable(QString::fromLatin1("found 'clashes' property of type %1")
                                 .arg(type)));
            }
        }
    }
    QVERIFY(seenProperty);
    QVERIFY(seenMethod);
}

// QTBUG-13047 (check that you can pass registered object types as args)
void tst_qqmlecmascript::invokableObjectArg()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("invokableObjectArg.qml"));

    QObject *o = component.create();
    QVERIFY(o);
    MyQmlObject *qmlobject = qobject_cast<MyQmlObject *>(o);
    QVERIFY(qmlobject);
    QCOMPARE(qmlobject->myinvokableObject, qmlobject);

    delete o;
}

// QTBUG-13047 (check that you can return registered object types from methods)
void tst_qqmlecmascript::invokableObjectRet()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("invokableObjectRet.qml"));

    QObject *o = component.create();
    QVERIFY(o);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

void tst_qqmlecmascript::invokableEnumRet()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("invokableEnumRet.qml"));

    QObject *o = component.create();
    QVERIFY(o);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

// QTBUG-5675
void tst_qqmlecmascript::listToVariant()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("listToVariant.qml"));

    MyQmlContainer container;

    QQmlContext context(engine.rootContext());
    context.setContextObject(&container);

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

    QVariant v = object->property("test");
    QCOMPARE(v.userType(), qMetaTypeId<QQmlListReference>());
    QCOMPARE(qvariant_cast<QQmlListReference>(v).object(), &container);

    delete object;
}

// QTBUG-16316
void tst_qqmlecmascript::listAssignment()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("listAssignment.qml"));
    QObject *obj = component.create();
    QCOMPARE(obj->property("list1length").toInt(), 2);
    QQmlListProperty<MyQmlObject> list1 = obj->property("list1").value<QQmlListProperty<MyQmlObject> >();
    QQmlListProperty<MyQmlObject> list2 = obj->property("list2").value<QQmlListProperty<MyQmlObject> >();
    QCOMPARE(list1.count(&list1), list2.count(&list2));
    QCOMPARE(list1.at(&list1, 0), list2.at(&list2, 0));
    QCOMPARE(list1.at(&list1, 1), list2.at(&list2, 1));
    delete obj;
}

// QTBUG-7957
void tst_qqmlecmascript::multiEngineObject()
{
    MyQmlObject obj;
    obj.setStringProperty("Howdy planet");

    QQmlEngine e1;
    e1.rootContext()->setContextProperty("thing", &obj);
    QQmlComponent c1(&e1, testFileUrl("multiEngineObject.qml"));

    QQmlEngine e2;
    e2.rootContext()->setContextProperty("thing", &obj);
    QQmlComponent c2(&e2, testFileUrl("multiEngineObject.qml"));

    QObject *o1 = c1.create();
    QObject *o2 = c2.create();

    QCOMPARE(o1->property("test").toString(), QString("Howdy planet"));
    QCOMPARE(o2->property("test").toString(), QString("Howdy planet"));

    delete o2;
    delete o1;
}

// Test that references to QObjects are cleanup when the object is destroyed
void tst_qqmlecmascript::deletedObject()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deletedObject.qml"));

    QObject *object = component.create();

    QCOMPARE(object->property("test1").toBool(), true);
    QCOMPARE(object->property("test2").toBool(), true);
    QCOMPARE(object->property("test3").toBool(), true);
    QCOMPARE(object->property("test4").toBool(), true);

    delete object;
}

void tst_qqmlecmascript::attachedPropertyScope()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("attachedPropertyScope.qml"));

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

    MyQmlAttachedObject *attached =
        qobject_cast<MyQmlAttachedObject *>(qmlAttachedPropertiesObject<MyQmlObject>(object));
    QVERIFY(attached != nullptr);

    QCOMPARE(object->property("value2").toInt(), 0);

    attached->emitMySignal();

    QCOMPARE(object->property("value2").toInt(), 9);

    delete object;
}

void tst_qqmlecmascript::scriptConnect()
{
    QQmlEngine engine;

    {
        QQmlComponent component(&engine, testFileUrl("scriptConnect.1.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toBool(), false);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toBool(), true);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptConnect.2.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toBool(), false);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toBool(), true);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptConnect.3.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toBool(), false);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toBool(), true);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptConnect.4.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->methodCalled(), false);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->methodCalled(), true);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptConnect.5.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->methodCalled(), false);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->methodCalled(), true);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptConnect.6.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toInt(), 0);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);

        delete object;
    }
}

void tst_qqmlecmascript::scriptDisconnect()
{
    QQmlEngine engine;

    {
        QQmlComponent component(&engine, testFileUrl("scriptDisconnect.1.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toInt(), 0);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 1);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->basicSignal();
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptDisconnect.2.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toInt(), 0);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 1);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->basicSignal();
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);

        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("scriptDisconnect.3.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toInt(), 0);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 1);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->basicSignal();
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 3);

        delete object;
    }
    {
        QQmlComponent component(&engine, testFileUrl("scriptDisconnect.4.qml"));

        MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
        QVERIFY(object != nullptr);

        QCOMPARE(object->property("test").toInt(), 0);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 1);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->basicSignal();
        QCOMPARE(object->property("test").toInt(), 2);
        emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton);
        QCOMPARE(object->property("test").toInt(), 3);

        delete object;
    }
}

class OwnershipObject : public QObject
{
    Q_OBJECT
public:
    OwnershipObject() { object = new QObject; }

    QPointer<QObject> object;

public slots:
    QObject *getObject() { return object; }
};

void tst_qqmlecmascript::ownership()
{
    QQmlEngine engine;
    OwnershipObject own;
    QQmlContext *context = new QQmlContext(engine.rootContext());
    context->setContextObject(&own);

    {
        QQmlComponent component(&engine, testFileUrl("ownership.qml"));

        QVERIFY(own.object != nullptr);

        QObject *object = component.create(context);

        engine.collectGarbage();

        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();

        QVERIFY(own.object.isNull());

        delete object;
    }

    own.object = new QObject(&own);

    {
        QQmlComponent component(&engine, testFileUrl("ownership.qml"));

        QVERIFY(own.object != nullptr);

        QObject *object = component.create(context);

        engine.collectGarbage();

        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();

        QVERIFY(own.object != nullptr);

        delete object;
    }

    delete context;
}

class CppOwnershipReturnValue : public QObject
{
    Q_OBJECT
public:
    CppOwnershipReturnValue() : value(nullptr) {}
    ~CppOwnershipReturnValue() { delete value; }

    Q_INVOKABLE QObject *create() {
        value = new QObject;
        QQmlEngine::setObjectOwnership(value, QQmlEngine::CppOwnership);
        return value;
    }

    Q_INVOKABLE MyQmlObject *createQmlObject() {
        MyQmlObject *rv = new MyQmlObject;
        value = rv;
        return rv;
    }

    QPointer<QObject> value;
};

// QTBUG-15695.
// Test setObjectOwnership(CppOwnership) works even when there is no QQmlData
void tst_qqmlecmascript::cppOwnershipReturnValue()
{
    CppOwnershipReturnValue source;

    {
    QQmlEngine engine;
    engine.rootContext()->setContextProperty("source", &source);

    QVERIFY(source.value.isNull());

    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0\nQtObject {\nComponent.onCompleted: { var a = source.create(); }\n}\n", QUrl());

    QObject *object = component.create();

    QVERIFY(object != nullptr);
    QVERIFY(source.value != nullptr);

    delete object;
    }

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();

    QVERIFY(source.value != nullptr);
}

// QTBUG-15697
void tst_qqmlecmascript::ownershipCustomReturnValue()
{
    QQmlEngine engine;
    CppOwnershipReturnValue source;

    {
    QQmlEngine engine;
    engine.rootContext()->setContextProperty("source", &source);

    QVERIFY(source.value.isNull());

    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0\nQtObject {\nComponent.onCompleted: { var a = source.createQmlObject(); }\n}\n", QUrl());

    QObject *object = component.create();

    QVERIFY(object != nullptr);
    QVERIFY(source.value != nullptr);

    delete object;
    }

    engine.collectGarbage();
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();

    QVERIFY(source.value.isNull());
}

//the return value from getObject will be JS ownership,
//unless strong Cpp ownership has been set
class OwnershipChangingObject : public QObject
{
    Q_OBJECT
public:
    OwnershipChangingObject(): object(nullptr) { }

    QPointer<QObject> object;

public slots:
    QObject *getObject() { return object; }
    void setObject(QObject *obj) { object = obj; }
};

void tst_qqmlecmascript::ownershipRootObject()
{
    QQmlEngine engine;
    OwnershipChangingObject own;
    QQmlContext *context = new QQmlContext(engine.rootContext());
    context->setContextObject(&own);

    QQmlComponent component(&engine, testFileUrl("ownershipRootObject.qml"));
    QPointer<QObject> object = component.create(context);
    QVERIFY(object);

    engine.collectGarbage();

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();

    QVERIFY(own.object != nullptr);

    delete context;
    delete object;
}

void tst_qqmlecmascript::ownershipConsistency()
{
    QQmlEngine engine;
    OwnershipChangingObject own;
    QQmlContext *context = new QQmlContext(engine.rootContext());
    context->setContextObject(&own);

    QString expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":19: Error: Invalid attempt to destroy() an indestructible object");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.
    expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":15: Error: Invalid attempt to destroy() an indestructible object");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.
    expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":6: Error: Invalid attempt to destroy() an indestructible object");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.
    expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":10: Error: Invalid attempt to destroy() an indestructible object");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.

    QQmlComponent component(&engine, testFileUrl("ownershipConsistency.qml"));
    QPointer<QObject> object = component.create(context);
    QVERIFY(object);

    engine.collectGarbage();

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();

    QVERIFY(own.object != nullptr);

    delete context;
    delete object;
}

void tst_qqmlecmascript::ownershipQmlIncubated()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("ownershipQmlIncubated.qml"));
    QObject *object = component.create();
    QVERIFY(object);

    QTRY_VERIFY(object->property("incubatedItem").value<QObject*>() != 0);

    QMetaObject::invokeMethod(object, "deleteIncubatedItem");

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    QCoreApplication::processEvents();

    QVERIFY(!object->property("incubatedItem").value<QObject*>());

    delete object;
}

class QListQObjectMethodsObject : public QObject
{
    Q_OBJECT
public:
    QListQObjectMethodsObject() {
        m_objects.append(new MyQmlObject());
        m_objects.append(new MyQmlObject());
    }

    ~QListQObjectMethodsObject() {
        qDeleteAll(m_objects);
    }

public slots:
    QList<QObject *> getObjects() { return m_objects; }

private:
    QList<QObject *> m_objects;
};

// Tests that returning a QList<QObject*> from a method works
void tst_qqmlecmascript::qlistqobjectMethods()
{
    QQmlEngine engine;
    QListQObjectMethodsObject obj;
    QQmlContext *context = new QQmlContext(engine.rootContext());
    context->setContextObject(&obj);

    QQmlComponent component(&engine, testFileUrl("qlistqobjectMethods.qml"));

    QObject *object = component.create(context);

    QCOMPARE(object->property("test").toInt(), 2);
    QCOMPARE(object->property("test2").toBool(), true);

    delete object;
    delete context;
}

// QTBUG-9205
void tst_qqmlecmascript::strictlyEquals()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("strictlyEquals.qml"));

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

    QCOMPARE(object->property("test1").toBool(), true);
    QCOMPARE(object->property("test2").toBool(), true);
    QCOMPARE(object->property("test3").toBool(), true);
    QCOMPARE(object->property("test4").toBool(), true);
    QCOMPARE(object->property("test5").toBool(), true);
    QCOMPARE(object->property("test6").toBool(), true);
    QCOMPARE(object->property("test7").toBool(), true);
    QCOMPARE(object->property("test8").toBool(), true);

    delete object;
}

void tst_qqmlecmascript::compiled()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("compiled.qml"));

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

    QCOMPARE(object->property("test1").toReal(), qreal(15.7));
    QCOMPARE(object->property("test2").toReal(), qreal(-6.7));
    QCOMPARE(object->property("test3").toBool(), true);
    QCOMPARE(object->property("test4").toBool(), false);
    QCOMPARE(object->property("test5").toBool(), false);
    QCOMPARE(object->property("test6").toBool(), true);

    QCOMPARE(object->property("test7").toInt(), 185);
    QCOMPARE(object->property("test8").toInt(), 167);
    QCOMPARE(object->property("test9").toBool(), true);
    QCOMPARE(object->property("test10").toBool(), false);
    QCOMPARE(object->property("test11").toBool(), false);
    QCOMPARE(object->property("test12").toBool(), true);

    QCOMPARE(object->property("test13").toString(), QLatin1String("HelloWorld"));
    QCOMPARE(object->property("test14").toString(), QLatin1String("Hello World"));
    QCOMPARE(object->property("test15").toBool(), false);
    QCOMPARE(object->property("test16").toBool(), true);

    QCOMPARE(object->property("test17").toInt(), 4);
    QCOMPARE(object->property("test18").toReal(), qreal(176));
    QCOMPARE(object->property("test19").toInt(), 6);
    QCOMPARE(object->property("test20").toReal(), qreal(6.5));
    QCOMPARE(object->property("test21").toString(), QLatin1String("6.5"));
    QCOMPARE(object->property("test22").toString(), QLatin1String("!"));
    QCOMPARE(object->property("test23").toBool(), true);
    QCOMPARE(qvariant_cast<QColor>(object->property("test24")), QColor(0x11,0x22,0x33));
    QCOMPARE(qvariant_cast<QColor>(object->property("test25")), QColor(0x11,0x22,0x33,0xAA));

    delete object;
}

// Test that numbers assigned in bindings as strings work consistently
void tst_qqmlecmascript::numberAssignment()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("numberAssignment.qml"));

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

    QCOMPARE(object->property("test1"), QVariant((qreal)6.7));
    QCOMPARE(object->property("test2"), QVariant((qreal)6.7));
    QCOMPARE(object->property("test2"), QVariant((qreal)6.7));
    QCOMPARE(object->property("test3"), QVariant((qreal)6));
    QCOMPARE(object->property("test4"), QVariant((qreal)6));

    QCOMPARE(object->property("test5"), QVariant((int)6));
    QCOMPARE(object->property("test6"), QVariant((int)7));
    QCOMPARE(object->property("test7"), QVariant((int)6));
    QCOMPARE(object->property("test8"), QVariant((int)6));

    QCOMPARE(object->property("test9"), QVariant((unsigned int)7));
    QCOMPARE(object->property("test10"), QVariant((unsigned int)7));
    QCOMPARE(object->property("test11"), QVariant((unsigned int)6));
    QCOMPARE(object->property("test12"), QVariant((unsigned int)6));

    delete object;
}

void tst_qqmlecmascript::propertySplicing()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertySplicing.qml"));

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

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

// QTBUG-16683
void tst_qqmlecmascript::signalWithUnknownTypes()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalWithUnknownTypes.qml"));

    MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(object != nullptr);

    MyQmlObject::MyType type;
    type.value = 0x8971123;
    emit object->signalWithUnknownType(type);

    MyQmlObject::MyType result = qvariant_cast<MyQmlObject::MyType>(object->variant());

    QCOMPARE(result.value, type.value);

    MyQmlObject::MyOtherType othertype;
    othertype.value = 17;
    emit object->signalWithCompletelyUnknownType(othertype);

    QVERIFY(!object->variant().isValid());

    delete object;
}

void tst_qqmlecmascript::signalWithJSValueInVariant_data()
{
    QTest::addColumn<QString>("expression");
    QTest::addColumn<QString>("compare");

    QString compareStrict("(function(a, b) { return a === b; })");
    QTest::newRow("true") << "true" << compareStrict;
    QTest::newRow("undefined") << "undefined" << compareStrict;
    QTest::newRow("null") << "null" << compareStrict;
    QTest::newRow("123") << "123" << compareStrict;
    QTest::newRow("'ciao'") << "'ciao'" << compareStrict;

    QString comparePropertiesStrict(
        "(function compareMe(a, b) {"
        "  if (typeof b != 'object')"
        "    return a === b;"
        "  var props = Object.getOwnPropertyNames(b);"
        "  for (var i = 0; i < props.length; ++i) {"
        "    var p = props[i];"
        "    return compareMe(a[p], b[p]);"
        "  }"
        "})");
    QTest::newRow("{ foo: 'bar' }") << "({ foo: 'bar' })"  << comparePropertiesStrict;
    QTest::newRow("[10,20,30]") << "[10,20,30]"  << comparePropertiesStrict;
}

void tst_qqmlecmascript::signalWithJSValueInVariant()
{
    QFETCH(QString, expression);
    QFETCH(QString, compare);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalWithJSValueInVariant.qml"));
    QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
    QVERIFY(object != nullptr);

    QJSValue value = engine.evaluate(expression);
    QVERIFY(!value.isError());
    object->setProperty("expression", expression);
    object->setProperty("compare", compare);
    object->setProperty("pass", false);

    emit object->signalWithVariant(QVariant::fromValue(value));
    QVERIFY(object->property("pass").toBool());
}

void tst_qqmlecmascript::signalWithJSValueInVariant_twoEngines_data()
{
    signalWithJSValueInVariant_data();
}

void tst_qqmlecmascript::signalWithJSValueInVariant_twoEngines()
{
    QFETCH(QString, expression);
    QFETCH(QString, compare);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalWithJSValueInVariant.qml"));
    QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
    QVERIFY(object != nullptr);

    QJSEngine engine2;
    QJSValue value = engine2.evaluate(expression);
    QVERIFY(!value.isError());
    object->setProperty("expression", expression);
    object->setProperty("compare", compare);
    object->setProperty("pass", false);

    QTest::ignoreMessage(QtWarningMsg, "JSValue can't be reassigned to another engine.");
    emit object->signalWithVariant(QVariant::fromValue(value));
    if (expression == "undefined")
        // if the engine is wrong, we return undefined to the other engine,
        // making this one case pass
        return;
    QVERIFY(!object->property("pass").toBool());
}

void tst_qqmlecmascript::signalWithQJSValue_data()
{
    signalWithJSValueInVariant_data();
}

void tst_qqmlecmascript::signalWithQJSValue()
{
    QFETCH(QString, expression);
    QFETCH(QString, compare);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalWithQJSValue.qml"));
    QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
    QVERIFY(object != nullptr);

    QJSValue value = engine.evaluate(expression);
    QVERIFY(!value.isError());
    object->setProperty("expression", expression);
    object->setProperty("compare", compare);
    object->setProperty("pass", false);

    emit object->signalWithQJSValue(value);

    QVERIFY(object->property("pass").toBool());
    QVERIFY(object->qjsvalue().strictlyEquals(value));
}

void tst_qqmlecmascript::singletonType_data()
{
    QTest::addColumn<QUrl>("testfile");
    QTest::addColumn<QString>("errorMessage");
    QTest::addColumn<QStringList>("warningMessages");
    QTest::addColumn<QStringList>("readProperties");
    QTest::addColumn<QVariantList>("readExpectedValues");
    QTest::addColumn<QStringList>("writeProperties");
    QTest::addColumn<QVariantList>("writeValues");
    QTest::addColumn<QStringList>("readBackProperties");
    QTest::addColumn<QVariantList>("readBackExpectedValues");

    QTest::newRow("qobject, register + read + method [no qualifier]")
            << testFileUrl("singletontype/qobjectSingletonTypeNoQualifier.qml")
            << QString()
            << QStringList()
            << (QStringList() << "qobjectPropertyTest" << "qobjectMethodTest")
            << (QVariantList() << 20 << 1)
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("script, register + read [no qualifier]")
            << testFileUrl("singletontype/scriptSingletonTypeNoQualifier.qml")
            << QString()
            << QStringList()
            << (QStringList() << "scriptTest")
            << (QVariantList() << 13)
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("qobject, register + read + method")
            << testFileUrl("singletontype/qobjectSingletonType.qml")
            << QString()
            << QStringList()
            << (QStringList() << "existingUriTest" << "qobjectTest" << "qobjectMethodTest"
                   << "qobjectMinorVersionMethodTest" << "qobjectMinorVersionTest"
                   << "qobjectMajorVersionTest" << "qobjectParentedTest")
            << (QVariantList() << 20 << 20 << 2 << 1 << 20 << 20 << 26)
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("script, register + read")
            << testFileUrl("singletontype/scriptSingletonType.qml")
            << QString()
            << QStringList()
            << (QStringList() << "scriptTest")
            << (QVariantList() << 14) // will have incremented, since we create a new engine each row in this test.
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("qobject, writing + readonly constraints")
            << testFileUrl("singletontype/qobjectSingletonTypeWriting.qml")
            << QString()
            << (QStringList() <<
                    QString(testFileUrl("singletontype/qobjectSingletonTypeWriting.qml").toString() + QLatin1String(":15: TypeError: Cannot assign to read-only property \"qobjectTestProperty\"")))
            << (QStringList() << "readOnlyProperty" << "writableProperty" << "writableFinalProperty")
            << (QVariantList() << 20 << 50 << 10)
            << (QStringList() << "firstProperty" << "secondProperty")
            << (QVariantList() << 30 << 30)
            << (QStringList() << "readOnlyProperty" << "writableProperty" << "writableFinalProperty")
            << (QVariantList() << 20 << 30 << 30);

    QTest::newRow("script, writing + readonly constraints")
            << testFileUrl("singletontype/scriptSingletonTypeWriting.qml")
            << QString()
            << (QStringList())
            << (QStringList() << "readBack" << "unchanged")
            << (QVariantList() << 15 << 42)
            << (QStringList() << "firstProperty" << "secondProperty")
            << (QVariantList() << 30 << 30)
            << (QStringList() << "readBack" << "unchanged")
            << (QVariantList() << 30 << 42);

    QTest::newRow("qobject singleton Type enum values in JS")
            << testFileUrl("singletontype/qobjectSingletonTypeEnums.qml")
            << QString()
            << QStringList()
            << (QStringList() << "enumValue" << "enumMethod")
            << (QVariantList() << 42 << 30)
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("qobject, invalid major version fail")
            << testFileUrl("singletontype/singletonTypeMajorVersionFail.qml")
            << QString("QQmlComponent: Component is not ready")
            << QStringList()
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("qobject, invalid minor version fail")
            << testFileUrl("singletontype/singletonTypeMinorVersionFail.qml")
            << QString("QQmlComponent: Component is not ready")
            << QStringList()
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();

    QTest::newRow("qobject, multiple in namespace")
            << testFileUrl("singletontype/singletonTypeMultiple.qml")
            << QString()
            << QStringList()
            << (QStringList() << "first" << "second")
            << (QVariantList() << 35 << 42)
            << QStringList()
            << QVariantList()
            << QStringList()
            << QVariantList();
}

void tst_qqmlecmascript::singletonType()
{
    QFETCH(QUrl, testfile);
    QFETCH(QString, errorMessage);
    QFETCH(QStringList, warningMessages);
    QFETCH(QStringList, readProperties);
    QFETCH(QVariantList, readExpectedValues);
    QFETCH(QStringList, writeProperties);
    QFETCH(QVariantList, writeValues);
    QFETCH(QStringList, readBackProperties);
    QFETCH(QVariantList, readBackExpectedValues);

    QQmlEngine cleanEngine; // so tests don't interfere which each other, as singleton types are engine-singletons only.
    QQmlComponent component(&cleanEngine, testfile);

    if (!errorMessage.isEmpty())
        QTest::ignoreMessage(QtWarningMsg, errorMessage.toLatin1().constData());

    if (warningMessages.size())
        foreach (const QString &warning, warningMessages)
            QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());

    QObject *object = component.create();
    if (!errorMessage.isEmpty()) {
        QVERIFY(!object);
    } else {
        QVERIFY(object != nullptr);
        for (int i = 0; i < readProperties.size(); ++i)
            QCOMPARE(object->property(readProperties.at(i).toLatin1().constData()), readExpectedValues.at(i));
        for (int i = 0; i < writeProperties.size(); ++i)
            QVERIFY(object->setProperty(writeProperties.at(i).toLatin1().constData(), writeValues.at(i)));
        for (int i = 0; i < readBackProperties.size(); ++i)
            QCOMPARE(object->property(readBackProperties.at(i).toLatin1().constData()), readBackExpectedValues.at(i));
        delete object;
    }
}

void tst_qqmlecmascript::singletonTypeCaching_data()
{
    QTest::addColumn<QUrl>("testfile");
    QTest::addColumn<QStringList>("readProperties");

    QTest::newRow("qobject, caching + read")
            << testFileUrl("singletontype/qobjectSingletonTypeCaching.qml")
            << (QStringList() << "existingUriTest" << "qobjectParentedTest");

    QTest::newRow("script, caching + read")
            << testFileUrl("singletontype/scriptSingletonTypeCaching.qml")
            << (QStringList() << "scriptTest");
}

void tst_qqmlecmascript::singletonTypeCaching()
{
    QFETCH(QUrl, testfile);
    QFETCH(QStringList, readProperties);

    // ensure that the singleton type instances are cached per-engine.

    QQmlEngine cleanEngine;
    QQmlComponent component(&cleanEngine, testfile);
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QList<QVariant> firstValues;
    QMetaObject::invokeMethod(object, "modifyValues");
    for (int i = 0; i < readProperties.size(); ++i)
        firstValues << object->property(readProperties.at(i).toLatin1().constData());
    delete object;

    QQmlComponent component2(&cleanEngine, testfile);
    QObject *object2 = component2.create();
    QVERIFY(object2 != nullptr);
    for (int i = 0; i < readProperties.size(); ++i)
        QCOMPARE(object2->property(readProperties.at(i).toLatin1().constData()), firstValues.at(i)); // cached, shouldn't have changed.
    delete object2;
}

void tst_qqmlecmascript::singletonTypeImportOrder()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("singletontype/singletonTypeImportOrder.qml"));
    QObject *object = component.create();
    QVERIFY(object);
    QCOMPARE(object->property("v").toInt(), 1);
    delete object;
}

void tst_qqmlecmascript::singletonTypeResolution()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("singletontype/singletonTypeResolution.qml"));
    QObject *object = component.create();
    QVERIFY(object);
    QVERIFY(object->property("success").toBool());
    delete object;
}

void tst_qqmlecmascript::verifyContextLifetime(QQmlContextData *ctxt) {
    QQmlContextData *childCtxt = ctxt->childContexts;

    if (!ctxt->importedScripts.isNullOrUndefined()) {
        QV4::ExecutionEngine *v4 = ctxt->engine->handle();
        QV4::Scope scope(v4);
        QV4::ScopedArrayObject scripts(scope, ctxt->importedScripts.value());
        QV4::Scoped<QV4::QQmlContextWrapper> qml(scope);
        for (quint32 i = 0; i < scripts->getLength(); ++i) {
            QQmlContextData *scriptContext, *newContext;
            qml = scripts->get(i);

            scriptContext = qml ? qml->getContext() : nullptr;
            qml = QV4::Encode::undefined();

            {
                QV4::Scope scope(v4);
                QV4::ScopedContext temporaryScope(scope, QV4::QmlContext::create(scope.engine->rootContext(), scriptContext, nullptr));
                Q_UNUSED(temporaryScope)
            }

            ctxt->engine->collectGarbage();
            qml = scripts->get(i);
            newContext = qml ? qml->getContext() : nullptr;
            QCOMPARE(scriptContext, newContext);
        }
    }

    while (childCtxt) {
        verifyContextLifetime(childCtxt);

        childCtxt = childCtxt->nextChild;
    }
}

void tst_qqmlecmascript::importScripts_data()
{
    QTest::addColumn<QUrl>("testfile");
    QTest::addColumn<bool>("compilationShouldSucceed");
    QTest::addColumn<QString>("errorMessage");
    QTest::addColumn<QStringList>("warningMessages");
    QTest::addColumn<QStringList>("propertyNames");
    QTest::addColumn<QVariantList>("propertyValues");

    QTest::newRow("basic functionality")
            << testFileUrl("jsimport/testImport.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("importedScriptStringValue")
                              << QLatin1String("importedScriptFunctionValue")
                              << QLatin1String("importedModuleAttachedPropertyValue")
                              << QLatin1String("importedModuleEnumValue"))
            << (QVariantList() << QVariant(QLatin1String("Hello, World!"))
                               << QVariant(20)
                               << QVariant(19)
                               << QVariant(2));

    QTest::newRow("import scoping")
            << testFileUrl("jsimport/testImportScoping.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("componentError"))
            << (QVariantList() << QVariant(5));

    QTest::newRow("parent scope shouldn't be inherited by import with imports")
            << testFileUrl("jsimportfail/failOne.qml")
            << true /* compilation should succeed */
            << QString()
            << (QStringList() << QString(testFileUrl("jsimportfail/failOne.qml").toString() + QLatin1String(":6: TypeError: Cannot call method 'greetingString' of undefined")))
            << (QStringList() << QLatin1String("importScriptFunctionValue"))
            << (QVariantList() << QVariant(QString()));

    QTest::newRow("javascript imports in an import should be private to the import scope")
            << testFileUrl("jsimportfail/failTwo.qml")
            << true /* compilation should succeed */
            << QString()
            << (QStringList() << QString(testFileUrl("jsimportfail/failTwo.qml").toString() + QLatin1String(":6: ReferenceError: ImportOneJs is not defined")))
            << (QStringList() << QLatin1String("importScriptFunctionValue"))
            << (QVariantList() << QVariant(QString()));

    QTest::newRow("module imports in an import should be private to the import scope")
            << testFileUrl("jsimportfail/failThree.qml")
            << true /* compilation should succeed */
            << QString()
            << (QStringList() << QString(testFileUrl("jsimportfail/failThree.qml").toString() + QLatin1String(":7: TypeError: Cannot read property 'JsQtTest' of undefined")))
            << (QStringList() << QLatin1String("importedModuleAttachedPropertyValue"))
            << (QVariantList() << QVariant(false));

    QTest::newRow("typenames in an import should be private to the import scope")
            << testFileUrl("jsimportfail/failFour.qml")
            << true /* compilation should succeed */
            << QString()
            << (QStringList() << QString(testFileUrl("jsimportfail/failFour.qml").toString() + QLatin1String(":6: ReferenceError: JsQtTest is not defined")))
            << (QStringList() << QLatin1String("importedModuleEnumValue"))
            << (QVariantList() << QVariant(0));

    QTest::newRow("import with imports has it's own activation scope")
            << testFileUrl("jsimportfail/failFive.qml")
            << true /* compilation should succeed */
            << QString()
            << (QStringList() << QString(testFileUrl("jsimportfail/importWithImports.js").toString() + QLatin1String(":8: ReferenceError: Component is not defined")))
            << (QStringList() << QLatin1String("componentError"))
            << (QVariantList() << QVariant(0));

    QTest::newRow("import pragma library script")
            << testFileUrl("jsimport/testImportPragmaLibrary.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("testValue"))
            << (QVariantList() << QVariant(31));

    QTest::newRow("pragma library imports shouldn't inherit parent imports or scope")
            << testFileUrl("jsimportfail/testImportPragmaLibrary.qml")
            << true /* compilation should succeed */
            << QString()
            << (QStringList() << QString(testFileUrl("jsimportfail/importPragmaLibrary.js").toString() + QLatin1String(":6: ReferenceError: Component is not defined")))
            << (QStringList() << QLatin1String("testValue"))
            << (QVariantList() << QVariant(0));

    QTest::newRow("import pragma library script which has an import")
            << testFileUrl("jsimport/testImportPragmaLibraryWithImports.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("testValue"))
            << (QVariantList() << QVariant(55));

    QTest::newRow("import pragma library script which has a pragma library import")
            << testFileUrl("jsimport/testImportPragmaLibraryWithPragmaLibraryImports.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("testValue"))
            << (QVariantList() << QVariant(16));

    QTest::newRow("import singleton type into js import")
            << testFileUrl("jsimport/testImportSingletonType.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("testValue"))
            << (QVariantList() << QVariant(20));

    QTest::newRow("import module which exports a script")
            << testFileUrl("jsimport/testJsImport.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("importedScriptStringValue")
                              << QLatin1String("renamedScriptStringValue")
                              << QLatin1String("reimportedScriptStringValue"))
            << (QVariantList() << QVariant(QString("Hello"))
                               << QVariant(QString("Hello"))
                               << QVariant(QString("Hello")));

    QTest::newRow("import module which exports a script which imports a remote module")
            << testFileUrl("jsimport/testJsRemoteImport.qml")
            << true /* compilation should succeed */
            << QString()
            << QStringList()
            << (QStringList() << QLatin1String("importedScriptStringValue")
                              << QLatin1String("renamedScriptStringValue")
                              << QLatin1String("reimportedScriptStringValue"))
            << (QVariantList() << QVariant(QString("Hello"))
                               << QVariant(QString("Hello"))
                               << QVariant(QString("Hello")));

    QTest::newRow("malformed import statement")
            << testFileUrl("jsimportfail/malformedImport.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedImport.js").toString() + QLatin1String(":1:2: Syntax error"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed file name")
            << testFileUrl("jsimportfail/malformedFile.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedFile.js").toString() + QLatin1String(":1:9: Imported file must be a script"))
            << QStringList()
            << QVariantList();

    QTest::newRow("missing file qualifier")
            << testFileUrl("jsimportfail/missingFileQualifier.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/missingFileQualifier.js").toString() + QLatin1String(":1:1: File import requires a qualifier"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed file qualifier")
            << testFileUrl("jsimportfail/malformedFileQualifier.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedFileQualifier.js").toString() + QLatin1String(":1:20: File import requires a qualifier"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed module qualifier 2")
            << testFileUrl("jsimportfail/malformedFileQualifier.2.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedFileQualifier.2.js").toString() + QLatin1String(":1:23: Invalid import qualifier"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed module uri")
            << testFileUrl("jsimportfail/malformedModule.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedModule.js").toString() + QLatin1String(":1:17: Invalid module URI"))
            << QStringList()
            << QVariantList();

    QTest::newRow("missing module version")
            << testFileUrl("jsimportfail/missingModuleVersion.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/missingModuleVersion.js").toString() + QLatin1String(":1:17: Module import requires a version"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed module version")
            << testFileUrl("jsimportfail/malformedModuleVersion.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedModuleVersion.js").toString() + QLatin1String(":1:17: Module import requires a version"))
            << QStringList()
            << QVariantList();

    QTest::newRow("missing module qualifier")
            << testFileUrl("jsimportfail/missingModuleQualifier.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/missingModuleQualifier.js").toString() + QLatin1String(":1:1: Module import requires a qualifier"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed module qualifier")
            << testFileUrl("jsimportfail/malformedModuleQualifier.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedModuleQualifier.js").toString() + QLatin1String(":1:21: Module import requires a qualifier"))
            << QStringList()
            << QVariantList();

    QTest::newRow("malformed module qualifier 2")
            << testFileUrl("jsimportfail/malformedModuleQualifier.2.qml")
            << false /* compilation should succeed */
            << QString()
            << (QStringList() << testFileUrl("jsimportfail/malformedModuleQualifier.2.js").toString() + QLatin1String(":1:24: Invalid import qualifier"))
            << QStringList()
            << QVariantList();
}

void tst_qqmlecmascript::importScripts()
{
    QFETCH(QUrl, testfile);
    QFETCH(bool, compilationShouldSucceed);
    QFETCH(QString, errorMessage);
    QFETCH(QStringList, warningMessages); // error messages if !compilationShouldSucceed
    QFETCH(QStringList, propertyNames);
    QFETCH(QVariantList, propertyValues);

    ThreadedTestHTTPServer server(dataDirectory() + "/remote");

    QQmlEngine engine;
    QString dataDir(dataDirectory() + QLatin1Char('/') + QLatin1String("lib"));
    engine.addImportPath(dataDir);

    QStringList importPathList = engine.importPathList();

    QString remotePath(server.urlString("/"));
    engine.addImportPath(remotePath);

    QQmlComponent component(&engine, testfile);

    if (!errorMessage.isEmpty())
        QTest::ignoreMessage(QtWarningMsg, errorMessage.toLatin1().constData());

    if (compilationShouldSucceed && warningMessages.size())
        foreach (const QString &warning, warningMessages)
            QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());

    if (compilationShouldSucceed)
        QTRY_VERIFY(component.isReady());
    else {
        QVERIFY(component.isError());
        QCOMPARE(warningMessages.size(), 1);
        QCOMPARE(component.errors().count(), 2);
        QCOMPARE(component.errors().at(1).toString(), warningMessages.first());
        return;
    }

    QObject *object = component.create();
    if (!errorMessage.isEmpty()) {
        QVERIFY(!object);
    } else {
        QVERIFY(object != nullptr);

        QQmlContextData *ctxt = QQmlContextData::get(engine.rootContext());
        tst_qqmlecmascript::verifyContextLifetime(ctxt);

        for (int i = 0; i < propertyNames.size(); ++i)
            QCOMPARE(object->property(propertyNames.at(i).toLatin1().constData()), propertyValues.at(i));
        delete object;
    }

    engine.setImportPathList(importPathList);
}

void tst_qqmlecmascript::importCreationContext()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("jsimport/creationContext.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());
    bool success = object->property("success").toBool();
    if (!success) {
        QSignalSpy readySpy(object.data(), SIGNAL(loaded()));
        readySpy.wait();
    }
    success = object->property("success").toBool();
    QVERIFY(success);
}

void tst_qqmlecmascript::scarceResources_other()
{
    /* These tests require knowledge of state, since we test values after
       performing signal or function invocation. */

    QPixmap origPixmap(100, 100);
    origPixmap.fill(Qt::blue);
    QString srp_name, expectedWarning;
    QQmlEngine engine;
    QV4::ExecutionEngine *v4 = engine.handle();
    ScarceResourceObject *eo = nullptr;
    QObject *srsc = nullptr;
    QObject *object = nullptr;

    /* property var semantics */

    // test that scarce resources are handled properly in signal invocation
    QQmlComponent varComponentTen(&engine, testFileUrl("scarceResourceSignal.var.qml"));
    object = varComponentTen.create();
    srsc = object->findChild<QObject*>("srsc");
    QVERIFY(srsc);
    QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // hasn't been instantiated yet.
    QCOMPARE(srsc->property("width"), QVariant(5)); // default value is 5.
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QMetaObject::invokeMethod(srsc, "testSignal");
    QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // still hasn't been instantiated
    QCOMPARE(srsc->property("width"), QVariant(10)); // but width was assigned to 10.
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should still be no other copies of it at this stage.
    QMetaObject::invokeMethod(srsc, "testSignal2"); // assigns scarceResourceCopy to the scarce pixmap.
    QVERIFY(srsc->property("scarceResourceCopy").isValid());
    QCOMPARE(srsc->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(!(eo->scarceResourceIsDetached())); // should be another copy of the resource now.
    QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point.
    delete object;

    // test that scarce resources are handled properly from js functions in qml files
    QQmlComponent varComponentEleven(&engine, testFileUrl("scarceResourceFunction.var.qml"));
    object = varComponentEleven.create();
    QVERIFY(object != nullptr);
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QMetaObject::invokeMethod(object, "retrieveScarceResource");
    QVERIFY(object->property("scarceResourceCopy").isValid()); // assigned, so should be valid.
    QCOMPARE(object->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(!eo->scarceResourceIsDetached()); // should be a copy of the resource at this stage.
    QMetaObject::invokeMethod(object, "releaseScarceResource");
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // just released, so should not be valid
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point.
    delete object;

    // test that if an exception occurs while invoking js function from cpp, that the resources are released.
    QQmlComponent varComponentTwelve(&engine, testFileUrl("scarceResourceFunctionFail.var.qml"));
    object = varComponentTwelve.create();
    QVERIFY(object != nullptr);
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    expectedWarning = varComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function");
    expectedWarning = expectedWarning.arg(QString::number(quintptr(eo), 16));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.
    QMetaObject::invokeMethod(object, "retrieveScarceResource");
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // due to exception, assignment will NOT have occurred.
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point.
    delete object;

    // test that if an Item which has JS ownership but has a scarce resource property is garbage collected,
    // that the scarce resource is removed from the engine's list of scarce resources to clean up.
    QQmlComponent varComponentThirteen(&engine, testFileUrl("scarceResourceObjectGc.var.qml"));
    object = varComponentThirteen.create();
    QVERIFY(object != nullptr);
    QVERIFY(!object->property("varProperty").isValid()); // not assigned yet
    QMetaObject::invokeMethod(object, "assignVarProperty");
    QVERIFY(v4->scarceResources.isEmpty());             // the scarce resource is a VME property.
    QMetaObject::invokeMethod(object, "deassignVarProperty");
    gc(engine);
    QVERIFY(v4->scarceResources.isEmpty());             // should still be empty; the resource should have been released on gc.
    delete object;

    /* property variant semantics */

    // test that scarce resources are handled properly in signal invocation
    QQmlComponent variantComponentTen(&engine, testFileUrl("scarceResourceSignal.variant.qml"));
    object = variantComponentTen.create();
    QVERIFY(object != nullptr);
    srsc = object->findChild<QObject*>("srsc");
    QVERIFY(srsc);
    QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // hasn't been instantiated yet.
    QCOMPARE(srsc->property("width"), QVariant(5)); // default value is 5.
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QMetaObject::invokeMethod(srsc, "testSignal");
    QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // still hasn't been instantiated
    QCOMPARE(srsc->property("width"), QVariant(10)); // but width was assigned to 10.
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should still be no other copies of it at this stage.
    QMetaObject::invokeMethod(srsc, "testSignal2"); // assigns scarceResourceCopy to the scarce pixmap.
    QVERIFY(srsc->property("scarceResourceCopy").isValid());
    QCOMPARE(srsc->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(!(eo->scarceResourceIsDetached())); // should be another copy of the resource now.
    QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point.
    delete object;

    // test that scarce resources are handled properly from js functions in qml files
    QQmlComponent variantComponentEleven(&engine, testFileUrl("scarceResourceFunction.variant.qml"));
    object = variantComponentEleven.create();
    QVERIFY(object != nullptr);
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QMetaObject::invokeMethod(object, "retrieveScarceResource");
    QVERIFY(object->property("scarceResourceCopy").isValid()); // assigned, so should be valid.
    QCOMPARE(object->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(!eo->scarceResourceIsDetached()); // should be a copy of the resource at this stage.
    QMetaObject::invokeMethod(object, "releaseScarceResource");
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // just released, so should not be valid
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point.
    delete object;

    // test that if an exception occurs while invoking js function from cpp, that the resources are released.
    QQmlComponent variantComponentTwelve(&engine, testFileUrl("scarceResourceFunctionFail.variant.qml"));
    object = variantComponentTwelve.create();
    QVERIFY(object != nullptr);
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    expectedWarning = variantComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function");
    expectedWarning = expectedWarning.arg(QString::number(quintptr(eo), 16));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.
    QMetaObject::invokeMethod(object, "retrieveScarceResource");
    QVERIFY(!object->property("scarceResourceCopy").isValid()); // due to exception, assignment will NOT have occurred.
    eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
    QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
    QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point.
    delete object;
}

void tst_qqmlecmascript::scarceResources_data()
{
    QTest::addColumn<QUrl>("qmlFile");
    QTest::addColumn<bool>("readDetachStatus");
    QTest::addColumn<bool>("expectedDetachStatus");
    QTest::addColumn<QStringList>("propertyNames");
    QTest::addColumn<QVariantList>("expectedValidity");
    QTest::addColumn<QVariantList>("expectedValues");
    QTest::addColumn<QStringList>("expectedErrors");

    QPixmap origPixmap(100, 100);
    origPixmap.fill(Qt::blue);

    /* property var semantics */

    // in the following three cases, the instance created from the component
    // has a property which is a copy of the scarce resource; hence, the
    // resource should NOT be detached prior to deletion of the object instance,
    // unless the resource is destroyed explicitly.
    QTest::newRow("var: import scarce resource copy directly")
        << testFileUrl("scarceResourceCopy.var.qml")
        << true
        << false // won't be detached, because assigned to property and not explicitly released
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << origPixmap)
        << QStringList();

    QTest::newRow("var: import scarce resource copy from JS")
        << testFileUrl("scarceResourceCopyFromJs.var.qml")
        << true
        << false // won't be detached, because assigned to property and not explicitly released
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << origPixmap)
        << QStringList();

    QTest::newRow("var: import released scarce resource copy from JS")
        << testFileUrl("scarceResourceDestroyedCopy.var.qml")
        << true
        << true // explicitly released, so it will be detached
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << false)
        << (QList<QVariant>() << QVariant())
        << QStringList();

    // in the following three cases, no other copy should exist in memory,
    // and so it should be detached (unless explicitly preserved).
    QTest::newRow("var: import auto-release SR from JS in binding side-effect")
        << testFileUrl("scarceResourceTest.var.qml")
        << true
        << true // auto released, so it will be detached
        << (QStringList() << QLatin1String("scarceResourceTest"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << QVariant(100))
        << QStringList();
    QTest::newRow("var: import explicit-preserve SR from JS in binding side-effect")
        << testFileUrl("scarceResourceTestPreserve.var.qml")
        << true
        << false // won't be detached because we explicitly preserve it
        << (QStringList() << QLatin1String("scarceResourceTest"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << QVariant(100))
        << QStringList();
    QTest::newRow("var: import explicit-preserve SR from JS in binding side-effect")
        << testFileUrl("scarceResourceTestMultiple.var.qml")
        << true
        << true // will be detached because all resources were released manually or automatically.
        << (QStringList() << QLatin1String("scarceResourceTest"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << QVariant(100))
        << QStringList();

    // In the following three cases, test that scarce resources are handled
    // correctly for imports.
    QTest::newRow("var: import with no binding")
        << testFileUrl("scarceResourceCopyImportNoBinding.var.qml")
        << false // cannot check detach status.
        << false
        << QStringList()
        << QList<QVariant>()
        << QList<QVariant>()
        << QStringList();
    QTest::newRow("var: import with binding without explicit preserve")
        << testFileUrl("scarceResourceCopyImportNoBinding.var.qml")
        << false
        << false
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << false) // will have been released prior to evaluation of binding.
        << (QList<QVariant>() << QVariant())
        << QStringList();
    QTest::newRow("var: import with explicit release after binding evaluation")
        << testFileUrl("scarceResourceCopyImport.var.qml")
        << false
        << false
        << (QStringList() << QLatin1String("scarceResourceImportedCopy") << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo") << QLatin1String("arePropertiesEqual"))
        << (QList<QVariant>() << false << false << false << true) // since property var = JS object reference, by releasing the provider's resource, all handles are invalidated.
        << (QList<QVariant>() << QVariant() << QVariant() << QVariant() << QVariant(true))
        << QStringList();
    QTest::newRow("var: import with different js objects")
        << testFileUrl("scarceResourceCopyImportDifferent.var.qml")
        << false
        << false
        << (QStringList() << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo") << QLatin1String("arePropertiesEqual"))
        << (QList<QVariant>() << false << true << true) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object.
        << (QList<QVariant>() << QVariant() << QVariant(origPixmap) << QVariant(false))
        << QStringList();
    QTest::newRow("var: import with different js objects and explicit release")
        << testFileUrl("scarceResourceMultipleDifferentNoBinding.var.qml")
        << false
        << false
        << (QStringList() << QLatin1String("resourceOne") << QLatin1String("resourceTwo"))
        << (QList<QVariant>() << true << false) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object.
        << (QList<QVariant>() << QVariant(origPixmap) << QVariant())
        << QStringList();
    QTest::newRow("var: import with same js objects and explicit release")
        << testFileUrl("scarceResourceMultipleSameNoBinding.var.qml")
        << false
        << false
        << (QStringList() << QLatin1String("resourceOne") << QLatin1String("resourceTwo"))
        << (QList<QVariant>() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object.
        << (QList<QVariant>() << QVariant() << QVariant())
        << QStringList();
    QTest::newRow("var: binding with same js objects and explicit release")
        << testFileUrl("scarceResourceMultipleSameWithBinding.var.qml")
        << false
        << false
        << (QStringList() << QLatin1String("resourceOne") << QLatin1String("resourceTwo"))
        << (QList<QVariant>() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object.
        << (QList<QVariant>() << QVariant() << QVariant())
        << QStringList();


    /* property variant semantics */

    // in the following three cases, the instance created from the component
    // has a property which is a copy of the scarce resource; hence, the
    // resource should NOT be detached prior to deletion of the object instance,
    // unless the resource is destroyed explicitly.
    QTest::newRow("variant: import scarce resource copy directly")
        << testFileUrl("scarceResourceCopy.variant.qml")
        << true
        << false // won't be detached, because assigned to property and not explicitly released
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << origPixmap)
        << QStringList();

    QTest::newRow("variant: import scarce resource copy from JS")
        << testFileUrl("scarceResourceCopyFromJs.variant.qml")
        << true
        << false // won't be detached, because assigned to property and not explicitly released
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << origPixmap)
        << QStringList();

    QTest::newRow("variant: import released scarce resource copy from JS")
        << testFileUrl("scarceResourceDestroyedCopy.variant.qml")
        << true
        << true // explicitly released, so it will be detached
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << false)
        << (QList<QVariant>() << QVariant())
        << QStringList();

    // in the following three cases, no other copy should exist in memory,
    // and so it should be detached (unless explicitly preserved).
    QTest::newRow("variant: import auto-release SR from JS in binding side-effect")
        << testFileUrl("scarceResourceTest.variant.qml")
        << true
        << true // auto released, so it will be detached
        << (QStringList() << QLatin1String("scarceResourceTest"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << QVariant(100))
        << QStringList();
    QTest::newRow("variant: import explicit-preserve SR from JS in binding side-effect")
        << testFileUrl("scarceResourceTestPreserve.variant.qml")
        << true
        << false // won't be detached because we explicitly preserve it
        << (QStringList() << QLatin1String("scarceResourceTest"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << QVariant(100))
        << QStringList();
    QTest::newRow("variant: import multiple scarce resources")
        << testFileUrl("scarceResourceTestMultiple.variant.qml")
        << true
        << true // will be detached because all resources were released manually or automatically.
        << (QStringList() << QLatin1String("scarceResourceTest"))
        << (QList<QVariant>() << true)
        << (QList<QVariant>() << QVariant(100))
        << QStringList();

    // In the following three cases, test that scarce resources are handled
    // correctly for imports.
    QTest::newRow("variant: import with no binding")
        << testFileUrl("scarceResourceCopyImportNoBinding.variant.qml")
        << false // cannot check detach status.
        << false
        << QStringList()
        << QList<QVariant>()
        << QList<QVariant>()
        << QStringList();
    QTest::newRow("variant: import with binding without explicit preserve")
        << testFileUrl("scarceResourceCopyImportNoBinding.variant.qml")
        << false
        << false
        << (QStringList() << QLatin1String("scarceResourceCopy"))
        << (QList<QVariant>() << false) // will have been released prior to evaluation of binding.
        << (QList<QVariant>() << QVariant())
        << QStringList();
    QTest::newRow("variant: import with explicit release after binding evaluation")
        << testFileUrl("scarceResourceCopyImport.variant.qml")
        << false
        << false
        << (QStringList() << QLatin1String("scarceResourceImportedCopy") << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo"))
        << (QList<QVariant>() << true << true << false) // since property variant = variant copy, releasing the provider's resource does not invalidate previously assigned copies.
        << (QList<QVariant>() << origPixmap << origPixmap << QVariant())
        << QStringList();
}

void tst_qqmlecmascript::scarceResources()
{
    QFETCH(QUrl, qmlFile);
    QFETCH(bool, readDetachStatus);
    QFETCH(bool, expectedDetachStatus);
    QFETCH(QStringList, propertyNames);
    QFETCH(QVariantList, expectedValidity);
    QFETCH(QVariantList, expectedValues);
    QFETCH(QStringList, expectedErrors);

    QQmlEngine engine;
    QV4::ExecutionEngine *v4 = engine.handle();
    ScarceResourceObject *eo = nullptr;
    QObject *object = nullptr;

    QQmlComponent c(&engine, qmlFile);
    object = c.create();
    QVERIFY(object != nullptr);
    for (int i = 0; i < propertyNames.size(); ++i) {
        QString prop = propertyNames.at(i);
        bool validity = expectedValidity.at(i).toBool();
        QVariant value = expectedValues.at(i);

        QCOMPARE(object->property(prop.toLatin1().constData()).isValid(), validity);
        if (value.type() == QVariant::Int) {
            QCOMPARE(object->property(prop.toLatin1().constData()).toInt(), value.toInt());
        } else if (value.type() == QVariant::Pixmap) {
            QCOMPARE(object->property(prop.toLatin1().constData()).value<QPixmap>(), value.value<QPixmap>());
        }
    }

    if (readDetachStatus) {
        eo = qobject_cast<ScarceResourceObject*>(QQmlProperty::read(object, "a").value<QObject*>());
        QCOMPARE(eo->scarceResourceIsDetached(), expectedDetachStatus);
    }

    QVERIFY(v4->scarceResources.isEmpty());
    delete object;
}

void tst_qqmlecmascript::propertyChangeSlots()
{
    // ensure that allowable property names are allowed and onPropertyNameChanged slots are generated correctly.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("changeslots/propertyChangeSlots.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    delete object;

    // ensure that invalid property names fail properly.
    QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
    QQmlComponent e1(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.1.qml"));
    QString expectedErrorString = e1.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_nameWithUnderscoreChanged\"");
    QCOMPARE(e1.errors().at(0).toString(), expectedErrorString);
    object = e1.create();
    QVERIFY(!object);
    delete object;

    QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
    QQmlComponent e2(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.2.qml"));
    expectedErrorString = e2.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on____nameWithUnderscoresChanged\"");
    QCOMPARE(e2.errors().at(0).toString(), expectedErrorString);
    object = e2.create();
    QVERIFY(!object);
    delete object;

    QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
    QQmlComponent e3(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.3.qml"));
    expectedErrorString = e3.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on$NameWithDollarsignChanged\"");
    QCOMPARE(e3.errors().at(0).toString(), expectedErrorString);
    object = e3.create();
    QVERIFY(!object);
    delete object;

    QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
    QQmlComponent e4(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.4.qml"));
    expectedErrorString = e4.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_6NameWithUnderscoreNumberChanged\"");
    QCOMPARE(e4.errors().at(0).toString(), expectedErrorString);
    object = e4.create();
    QVERIFY(!object);
    delete object;
}

void tst_qqmlecmascript::propertyVar_data()
{
    QTest::addColumn<QUrl>("qmlFile");

    // valid
    QTest::newRow("non-bindable object subproperty changed") << testFileUrl("propertyVar.1.qml");
    QTest::newRow("non-bindable object changed") << testFileUrl("propertyVar.2.qml");
    QTest::newRow("primitive changed") << testFileUrl("propertyVar.3.qml");
    QTest::newRow("javascript array modification") << testFileUrl("propertyVar.4.qml");
    QTest::newRow("javascript map modification") << testFileUrl("propertyVar.5.qml");
    QTest::newRow("javascript array assignment") << testFileUrl("propertyVar.6.qml");
    QTest::newRow("javascript map assignment") << testFileUrl("propertyVar.7.qml");
    QTest::newRow("literal property assignment") << testFileUrl("propertyVar.8.qml");
    QTest::newRow("qobject property assignment") << testFileUrl("propertyVar.9.qml");
    QTest::newRow("base class var property assignment") << testFileUrl("propertyVar.10.qml");
    QTest::newRow("javascript function assignment") << testFileUrl("propertyVar.11.qml");
    QTest::newRow("javascript special assignment") << testFileUrl("propertyVar.12.qml");
    QTest::newRow("declarative binding assignment") << testFileUrl("propertyVar.13.qml");
    QTest::newRow("imperative binding assignment") << testFileUrl("propertyVar.14.qml");
    QTest::newRow("stored binding assignment") << testFileUrl("propertyVar.15.qml");
    QTest::newRow("function expression binding assignment") << testFileUrl("propertyVar.16.qml");
}

void tst_qqmlecmascript::propertyVar()
{
    QFETCH(QUrl, qmlFile);

    QQmlEngine engine;
    QQmlComponent component(&engine, qmlFile);
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

void tst_qqmlecmascript::propertyQJSValue_data()
{
    QTest::addColumn<QUrl>("qmlFile");

    // valid
    QTest::newRow("non-bindable object subproperty changed") << testFileUrl("propertyQJSValue.1.qml");
    QTest::newRow("non-bindable object changed") << testFileUrl("propertyQJSValue.2.qml");
    QTest::newRow("primitive changed") << testFileUrl("propertyQJSValue.3.qml");
    QTest::newRow("javascript array modification") << testFileUrl("propertyQJSValue.4.qml");
    QTest::newRow("javascript map modification") << testFileUrl("propertyQJSValue.5.qml");
    QTest::newRow("javascript array assignment") << testFileUrl("propertyQJSValue.6.qml");
    QTest::newRow("javascript map assignment") << testFileUrl("propertyQJSValue.7.qml");
    QTest::newRow("literal property assignment") << testFileUrl("propertyQJSValue.8.qml");
    QTest::newRow("qobject property assignment") << testFileUrl("propertyQJSValue.9.qml");
    QTest::newRow("base class var property assignment") << testFileUrl("propertyQJSValue.10.qml");
    QTest::newRow("javascript function assignment") << testFileUrl("propertyQJSValue.11.qml");
    QTest::newRow("javascript special assignment") << testFileUrl("propertyQJSValue.12.qml");
    QTest::newRow("declarative binding assignment") << testFileUrl("propertyQJSValue.13.qml");
    QTest::newRow("imperative binding assignment") << testFileUrl("propertyQJSValue.14.qml");
    QTest::newRow("stored binding assignment") << testFileUrl("propertyQJSValue.15.qml");
    QTest::newRow("javascript function binding") << testFileUrl("propertyQJSValue.16.qml");

    QTest::newRow("reset property") << testFileUrl("propertyQJSValue.reset.qml");
    QTest::newRow("reset property in binding") << testFileUrl("propertyQJSValue.bindingreset.qml");
}

void tst_qqmlecmascript::propertyQJSValue()
{
    QFETCH(QUrl, qmlFile);

    QQmlEngine engine;
    QQmlComponent component(&engine, qmlFile);
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

// Tests that we can write QVariant values to var properties from C++
void tst_qqmlecmascript::propertyVarCpp()
{
    QObject *object = nullptr;

    // ensure that writing to and reading from a var property from cpp works as required.
    // Literal values stored in var properties can be read and written as QVariants
    // of a specific type, whereas object values are read as QVariantMaps.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVarCpp.qml"));
    object = component.create();
    QVERIFY(object != nullptr);
    // assign int to property var that currently has int assigned
    QVERIFY(object->setProperty("varProperty", QVariant::fromValue(10)));
    QCOMPARE(object->property("varBound"), QVariant(15));
    QCOMPARE(object->property("intBound"), QVariant(15));
    QVERIFY(isJSNumberType(object->property("varProperty").userType()));
    QVERIFY(isJSNumberType(object->property("varBound").userType()));
    // assign string to property var that current has bool assigned
    QCOMPARE(object->property("varProperty2").userType(), (int)QVariant::Bool);
    QVERIFY(object->setProperty("varProperty2", QVariant(QLatin1String("randomString"))));
    QCOMPARE(object->property("varProperty2"), QVariant(QLatin1String("randomString")));
    QCOMPARE(object->property("varProperty2").userType(), (int)QVariant::String);
    // now enforce behaviour when accessing JavaScript objects from cpp.
    QCOMPARE(object->property("jsobject").userType(), qMetaTypeId<QJSValue>());
    delete object;
}

void tst_qqmlecmascript::propertyVarOwnership()
{
    QQmlEngine engine;

    // Referenced JS objects are not collected
    {
    QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("test").toBool(), false);
    QMetaObject::invokeMethod(object, "runTest");
    QCOMPARE(object->property("test").toBool(), true);
    delete object;
    }
    // Referenced JS objects are not collected
    {
    QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.2.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("test").toBool(), false);
    QMetaObject::invokeMethod(object, "runTest");
    QCOMPARE(object->property("test").toBool(), true);
    delete object;
    }
    // Qt objects are not collected until they've been dereferenced
    {
    QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.3.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test2").toBool(), false);
    QCOMPARE(object->property("test2").toBool(), false);

    QMetaObject::invokeMethod(object, "runTest");
    QCOMPARE(object->property("test1").toBool(), true);

    QPointer<QObject> referencedObject = object->property("object").value<QObject*>();
    QVERIFY(!referencedObject.isNull());
    gc(engine);
    QVERIFY(!referencedObject.isNull());

    QMetaObject::invokeMethod(object, "runTest2");
    QCOMPARE(object->property("test2").toBool(), true);
    gc(engine);
    QVERIFY(referencedObject.isNull());

    delete object;
    }
    // Self reference does not prevent Qt object collection
    {
    QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.4.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("test").toBool(), true);

    QPointer<QObject> referencedObject = object->property("object").value<QObject*>();
    QVERIFY(!referencedObject.isNull());
    gc(engine);
    QVERIFY(!referencedObject.isNull());

    QMetaObject::invokeMethod(object, "runTest");
    gc(engine);
    QVERIFY(referencedObject.isNull());

    delete object;
    }
    // Garbage collection cannot result in attempted dereference of empty handle
    {
    QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.5.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "createComponent");
    engine.collectGarbage();
    QMetaObject::invokeMethod(object, "runTest");
    QCOMPARE(object->property("test").toBool(), true);
    delete object;
    }
}

void tst_qqmlecmascript::propertyVarImplicitOwnership()
{
    // The childObject has a reference to a different QObject.  We want to ensure
    // that the different item will not be cleaned up until required.  IE, the childObject
    // has implicit ownership of the constructed QObject.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVarImplicitOwnership.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignCircular");
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QObject *rootObject = object->property("vp").value<QObject*>();
    QVERIFY(rootObject != nullptr);
    QObject *childObject = rootObject->findChild<QObject*>("text");
    QVERIFY(childObject != nullptr);
    QCOMPARE(rootObject->property("rectCanary").toInt(), 5);
    QCOMPARE(childObject->property("textCanary").toInt(), 10);
    QMetaObject::invokeMethod(childObject, "constructQObject");    // creates a reference to a constructed QObject.
    QPointer<QObject> qobjectGuard(childObject->property("vp").value<QObject*>()); // get the pointer prior to processing deleteLater events.
    QVERIFY(!qobjectGuard.isNull());
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QVERIFY(!qobjectGuard.isNull());
    QMetaObject::invokeMethod(object, "deassignCircular");
    gc(engine);
    QVERIFY(qobjectGuard.isNull());                                // should have been collected now.
    delete object;
}

void tst_qqmlecmascript::propertyVarReparent()
{
    // ensure that nothing breaks if we re-parent objects
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVar.reparent.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignVarProp");
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QObject *rect = object->property("vp").value<QObject*>();
    QObject *text = rect->findChild<QObject*>("textOne");
    QObject *text2 = rect->findChild<QObject*>("textTwo");
    QPointer<QObject> rectGuard(rect);
    QPointer<QObject> textGuard(text);
    QPointer<QObject> text2Guard(text2);
    QVERIFY(!rectGuard.isNull());
    QVERIFY(!textGuard.isNull());
    QVERIFY(!text2Guard.isNull());
    QCOMPARE(text->property("textCanary").toInt(), 11);
    QCOMPARE(text2->property("textCanary").toInt(), 12);
    // now construct an image which we will reparent.
    QMetaObject::invokeMethod(text2, "constructQObject");
    QObject *image = text2->property("vp").value<QObject*>();
    QPointer<QObject> imageGuard(image);
    QVERIFY(!imageGuard.isNull());
    QCOMPARE(image->property("imageCanary").toInt(), 13);
    // now reparent the "Image" object (currently, it has JS ownership)
    image->setParent(text);                                        // shouldn't be collected after deassignVp now, since has a parent.
    QMetaObject::invokeMethod(text2, "deassignVp");
    gc(engine);
    QCOMPARE(text->property("textCanary").toInt(), 11);
    QCOMPARE(text2->property("textCanary").toInt(), 22);
    QVERIFY(!imageGuard.isNull());                                 // should still be alive.
    QCOMPARE(image->property("imageCanary").toInt(), 13);          // still able to access var properties
    QMetaObject::invokeMethod(object, "deassignVarProp");          // now deassign the root-object's vp, causing gc of rect+text+text2
    gc(engine);
    QVERIFY(imageGuard.isNull());                                  // should now have been deleted, due to parent being deleted.
    delete object;
}

void tst_qqmlecmascript::propertyVarReparentNullContext()
{
    // sometimes reparenting can cause problems
    // (eg, if the ctxt is collected, varproperties are no longer available)
    // this test ensures that no crash occurs in that situation.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVar.reparent.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignVarProp");
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QObject *rect = object->property("vp").value<QObject*>();
    QObject *text = rect->findChild<QObject*>("textOne");
    QObject *text2 = rect->findChild<QObject*>("textTwo");
    QPointer<QObject> rectGuard(rect);
    QPointer<QObject> textGuard(text);
    QPointer<QObject> text2Guard(text2);
    QVERIFY(!rectGuard.isNull());
    QVERIFY(!textGuard.isNull());
    QVERIFY(!text2Guard.isNull());
    QCOMPARE(text->property("textCanary").toInt(), 11);
    QCOMPARE(text2->property("textCanary").toInt(), 12);
    // now construct an image which we will reparent.
    QMetaObject::invokeMethod(text2, "constructQObject");
    QObject *image = text2->property("vp").value<QObject*>();
    QPointer<QObject> imageGuard(image);
    QVERIFY(!imageGuard.isNull());
    QCOMPARE(image->property("imageCanary").toInt(), 13);
    // now reparent the "Image" object (currently, it has JS ownership)
    image->setParent(object);                                      // reparented to base object.  after deassignVarProp, the ctxt will be invalid.
    QMetaObject::invokeMethod(object, "deassignVarProp");          // now deassign the root-object's vp, causing gc of rect+text+text2
    gc(engine);
    QVERIFY(!imageGuard.isNull());                                 // should still be alive.
    QVERIFY(!image->property("imageCanary").isValid());            // but varProperties won't be available (null context).
    delete object;
    QVERIFY(imageGuard.isNull());                                  // should now be dead.
}

void tst_qqmlecmascript::propertyVarCircular()
{
    // enforce behaviour regarding circular references - ensure qdvmemo deletion.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVar.circular.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignCircular");           // cause assignment and gc
    {
        QCOMPARE(object->property("canaryInt"), QVariant(5));
        QVariant canaryResourceVariant = object->property("canaryResource");
        QVERIFY(canaryResourceVariant.isValid());
    }

    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QCOMPARE(object->property("canaryInt"), QVariant(5));
    QVariant canaryResourceVariant = object->property("canaryResource");
    QVERIFY(canaryResourceVariant.isValid());
    QPixmap canaryResourcePixmap = canaryResourceVariant.value<QPixmap>();
    canaryResourceVariant = QVariant();                            // invalidate it to remove one copy of the pixmap from memory.
    QMetaObject::invokeMethod(object, "deassignCanaryResource");   // remove one copy of the pixmap from memory
    gc(engine);
    QVERIFY(!canaryResourcePixmap.isDetached());                   // two copies extant - this and the propertyVar.vp.vp.vp.vp.memoryHog.
    QMetaObject::invokeMethod(object, "deassignCircular");         // cause deassignment and gc
    gc(engine);
    QCOMPARE(object->property("canaryInt"), QVariant(2));
    QCOMPARE(object->property("canaryResource"), QVariant(1));
    QVERIFY(canaryResourcePixmap.isDetached());                    // now detached, since orig copy was member of qdvmemo which was deleted.
    delete object;
}

void tst_qqmlecmascript::propertyVarCircular2()
{
    // track deletion of JS-owned parent item with Cpp-owned child
    // where the child has a var property referencing its parent.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVar.circular.2.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignCircular");
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QObject *rootObject = object->property("vp").value<QObject*>();
    QVERIFY(rootObject != nullptr);
    QObject *childObject = rootObject->findChild<QObject*>("text");
    QVERIFY(childObject != nullptr);
    QPointer<QObject> rootObjectTracker(rootObject);
    QVERIFY(!rootObjectTracker.isNull());
    QPointer<QObject> childObjectTracker(childObject);
    QVERIFY(!childObjectTracker.isNull());
    gc(engine);
    QCOMPARE(rootObject->property("rectCanary").toInt(), 5);
    QCOMPARE(childObject->property("textCanary").toInt(), 10);
    QMetaObject::invokeMethod(object, "deassignCircular");
    gc(engine);
    QVERIFY(rootObjectTracker.isNull());                           // should have been collected
    QVERIFY(childObjectTracker.isNull());                          // should have been collected
    delete object;
}

void tst_qqmlecmascript::propertyVarInheritance()
{
    // enforce behaviour regarding element inheritance - ensure handle disposal.
    // The particular component under test here has a chain of references.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVar.inherit.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignCircular");           // cause assignment and gc
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    // we want to be able to track when the varProperties array of the last metaobject is disposed
    QObject *cco5 = object->property("varProperty").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>();
    QObject *ico5 = object->property("varProperty").value<QObject*>()->property("inheritanceVarProperty").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>();
    QVERIFY(cco5);
    QVERIFY(ico5);
    QQmlVMEMetaObject *icovmemo = QQmlVMEMetaObject::get(ico5);
    QQmlVMEMetaObject *ccovmemo = QQmlVMEMetaObject::get(cco5);
    QV4::WeakValue icoCanaryHandle;
    QV4::WeakValue ccoCanaryHandle;
    {
        // XXX NOTE: this is very implementation dependent.  QDVMEMO->vmeProperty() is the only
        // public function which can return us a handle to something in the varProperties array.
        QV4::ReturnedValue tmp = icovmemo->vmeProperty(ico5->metaObject()->indexOfProperty("circ"));
        icoCanaryHandle.set(engine.handle(), tmp);
        tmp = ccovmemo->vmeProperty(cco5->metaObject()->indexOfProperty("circ"));
        ccoCanaryHandle.set(engine.handle(), tmp);
        tmp = QV4::Encode::null();
        QVERIFY(!icoCanaryHandle.isUndefined());
        QVERIFY(!ccoCanaryHandle.isUndefined());
        gc(engine);
        QVERIFY(!icoCanaryHandle.isUndefined());
        QVERIFY(!ccoCanaryHandle.isUndefined());
    }
    // now we deassign the var prop, which should trigger collection of item subtrees.
    QMetaObject::invokeMethod(object, "deassignCircular");         // cause deassignment and gc
    // ensure that there are only weak handles to the underlying varProperties array remaining.
    gc(engine);
    // an equivalent for pragma GCC optimize is still work-in-progress for CLang, so this test will fail.
    QVERIFY(icoCanaryHandle.isUndefined());
    QVERIFY(ccoCanaryHandle.isUndefined());
    delete object;
    // since there are no parent vmemo's to keep implicit references alive, and the only handles
    // to what remains are weak, all varProperties arrays must have been collected.
}

void tst_qqmlecmascript::propertyVarInheritance2()
{
    // The particular component under test here does NOT have a chain of references; the
    // only link between rootObject and childObject is that rootObject is the parent of childObject.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyVar.circular.2.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "assignCircular");
    QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper.
    QCoreApplication::processEvents();
    QObject *rootObject = object->property("vp").value<QObject*>();
    QVERIFY(rootObject != nullptr);
    QObject *childObject = rootObject->findChild<QObject*>("text");
    QVERIFY(childObject != nullptr);
    QCOMPARE(rootObject->property("rectCanary").toInt(), 5);
    QCOMPARE(childObject->property("textCanary").toInt(), 10);
    QV4::WeakValue childObjectVarArrayValueHandle;
    {
        childObjectVarArrayValueHandle.set(engine.handle(),
                                           QQmlVMEMetaObject::get(childObject)->vmeProperty(childObject->metaObject()->indexOfProperty("vp")));
        QVERIFY(!childObjectVarArrayValueHandle.isUndefined());
        gc(engine);
        QVERIFY(!childObjectVarArrayValueHandle.isUndefined()); // should not have been collected yet.
        QCOMPARE(childObject->property("vp").value<QObject*>(), rootObject);
        QCOMPARE(childObject->property("textCanary").toInt(), 10);
    }
    QMetaObject::invokeMethod(object, "deassignCircular");
    gc(engine);
    // an equivalent for pragma GCC optimize is still work-in-progress for CLang, so this test will fail.
    QVERIFY(childObjectVarArrayValueHandle.isUndefined()); // should have been collected now.
    delete object;
}


// Ensure that QObject type conversion works on binding assignment
void tst_qqmlecmascript::elementAssign()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("elementAssign.qml"));

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

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

// QTBUG-12457
void tst_qqmlecmascript::objectPassThroughSignals()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("objectsPassThroughSignals.qml"));

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

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

// QTBUG-21626
void tst_qqmlecmascript::objectConversion()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("objectConversion.qml"));

    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QVariant retn;
    QMetaObject::invokeMethod(object, "circularObject", Q_RETURN_ARG(QVariant, retn));
    QCOMPARE(retn.value<QJSValue>().property("test").toInt(), int(100));

    delete object;
}


// QTBUG-20242
void tst_qqmlecmascript::booleanConversion()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("booleanConversion.qml"));

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

    QCOMPARE(object->property("test_true1").toBool(), true);
    QCOMPARE(object->property("test_true2").toBool(), true);
    QCOMPARE(object->property("test_true3").toBool(), true);
    QCOMPARE(object->property("test_true4").toBool(), true);
    QCOMPARE(object->property("test_true5").toBool(), true);

    QCOMPARE(object->property("test_false1").toBool(), false);
    QCOMPARE(object->property("test_false2").toBool(), false);
    QCOMPARE(object->property("test_false3").toBool(), false);

    delete object;
}

void tst_qqmlecmascript::handleReferenceManagement()
{
    int dtorCount = 0;
    {
        // Linear QObject reference
        QQmlEngine hrmEngine;
        QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.object.1.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
        cro->setDtorCount(&dtorCount);
        QMetaObject::invokeMethod(object, "createReference");
        gc(hrmEngine);
        QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference
        delete object;
        hrmEngine.collectGarbage();
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();
        QCOMPARE(dtorCount, 3);
    }

    dtorCount = 0;
    {
        // Circular QObject reference
        QQmlEngine hrmEngine;
        QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.object.2.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
        cro->setDtorCount(&dtorCount);
        QMetaObject::invokeMethod(object, "circularReference");
        gc(hrmEngine);
        QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive.
        delete object;
        hrmEngine.collectGarbage();
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();
        QCOMPARE(dtorCount, 3);
    }

    {
        // Dynamic variant property reference keeps target alive
        QQmlEngine hrmEngine;
        QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.dynprop.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QMetaObject::invokeMethod(object, "createReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "ensureReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "removeReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "ensureDeletion");
        QCOMPARE(object->property("success").toBool(), true);
        delete object;
    }

    {
        // Dynamic Item property reference keeps target alive
        QQmlEngine hrmEngine;
        QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.dynprop.2.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QMetaObject::invokeMethod(object, "createReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "ensureReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "removeReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "ensureDeletion");
        QCOMPARE(object->property("success").toBool(), true);
        delete object;
    }

    {
        // Item property reference to deleted item doesn't crash
        QQmlEngine hrmEngine;
        QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.dynprop.3.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QMetaObject::invokeMethod(object, "createReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "ensureReference");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "manuallyDelete");
        gc(hrmEngine);
        QMetaObject::invokeMethod(object, "ensureDeleted");
        QCOMPARE(object->property("success").toBool(), true);
        delete object;
    }
}

void tst_qqmlecmascript::stringArg()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("stringArg.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "success");
    QVERIFY(object->property("returnValue").toBool());

    QString w1 = testFileUrl("stringArg.qml").toString() + QLatin1String(":45: Error: String.arg(): Invalid arguments");
    QTest::ignoreMessage(QtWarningMsg, w1.toLatin1().constData());
    QMetaObject::invokeMethod(object, "failure");
    QVERIFY(object->property("returnValue").toBool());

    delete object;
}

void tst_qqmlecmascript::readonlyDeclaration()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("readonlyDeclaration.qml"));

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

    QCOMPARE(object->property("test").toBool(), true);

    delete object;
}

Q_DECLARE_METATYPE(QList<int>)
Q_DECLARE_METATYPE(QList<qreal>)
Q_DECLARE_METATYPE(QList<bool>)
Q_DECLARE_METATYPE(QList<QString>)
Q_DECLARE_METATYPE(QList<QUrl>)
void tst_qqmlecmascript::sequenceConversionRead()
{
    QQmlEngine engine;

    {
        QUrl qmlFile = testFileUrl("sequenceConversion.read.qml");
        QQmlComponent component(&engine, qmlFile);
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
        QVERIFY(seq != nullptr);

        QMetaObject::invokeMethod(object, "readSequences");
        QList<int> intList; intList << 1 << 2 << 3 << 4;
        QCOMPARE(object->property("intListLength").toInt(), intList.length());
        QCOMPARE(object->property("intList").value<QList<int> >(), intList);
        QList<qreal> qrealList; qrealList << 1.1 << 2.2 << 3.3 << 4.4;
        QCOMPARE(object->property("qrealListLength").toInt(), qrealList.length());
        QCOMPARE(object->property("qrealList").value<QList<qreal> >(), qrealList);
        QList<bool> boolList; boolList << true << false << true << false;
        QCOMPARE(object->property("boolListLength").toInt(), boolList.length());
        QCOMPARE(object->property("boolList").value<QList<bool> >(), boolList);
        QList<QString> stringList; stringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth");
        QCOMPARE(object->property("stringListLength").toInt(), stringList.length());
        QCOMPARE(object->property("stringList").value<QList<QString> >(), stringList);
        QList<QUrl> urlList; urlList << QUrl("http://www.example1.com") << QUrl("http://www.example2.com") << QUrl("http://www.example3.com");
        QCOMPARE(object->property("urlListLength").toInt(), urlList.length());
        QCOMPARE(object->property("urlList").value<QList<QUrl> >(), urlList);
        QStringList qstringList; qstringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth");
        QCOMPARE(object->property("qstringListLength").toInt(), qstringList.length());
        QCOMPARE(object->property("qstringList").value<QStringList>(), qstringList);

        QMetaObject::invokeMethod(object, "readSequenceElements");
        QCOMPARE(object->property("intVal").toInt(), 2);
        QCOMPARE(object->property("qrealVal").toReal(), 2.2);
        QCOMPARE(object->property("boolVal").toBool(), false);
        QCOMPARE(object->property("stringVal").toString(), QString(QLatin1String("second")));
        QCOMPARE(object->property("urlVal").toUrl(), QUrl("http://www.example2.com"));
        QCOMPARE(object->property("qstringVal").toString(), QString(QLatin1String("second")));

        QMetaObject::invokeMethod(object, "enumerateSequenceElements");
        QCOMPARE(object->property("enumerationMatches").toBool(), true);

        intList.clear(); intList << 1 << 2 << 3 << 4 << 5; // set by the enumerateSequenceElements test.
        QQmlProperty seqProp(seq, "intListProperty");
        QCOMPARE(seqProp.read().value<QList<int> >(), intList);
        QQmlProperty seqProp2(seq, "intListProperty", &engine);
        QCOMPARE(seqProp2.read().value<QList<int> >(), intList);

        QMetaObject::invokeMethod(object, "testReferenceDeletion");
        QCOMPARE(object->property("referenceDeletion").toBool(), true);

        delete object;
    }

    {
        QUrl qmlFile = testFileUrl("sequenceConversion.read.error.qml");
        QQmlComponent component(&engine, qmlFile);
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
        QVERIFY(seq != nullptr);

        // we haven't registered QList<NonRegisteredType> as a sequence type.
        QString warningOne = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'QList<NonRegisteredType>' for property 'MySequenceConversionObject::typeListProperty'");
        QString warningTwo = qmlFile.toString() + QLatin1String(":18: TypeError: Cannot read property 'length' of undefined");
        QTest::ignoreMessage(QtWarningMsg, warningOne.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warningTwo.toLatin1().constData());

        QMetaObject::invokeMethod(object, "performTest");

        // QList<NonRegisteredType> has not been registered as a sequence type.
        QCOMPARE(object->property("pointListLength").toInt(), 0);
        QVERIFY(!object->property("pointList").isValid());
        QTest::ignoreMessage(QtWarningMsg, "QMetaProperty::read: Unable to handle unregistered datatype 'QList<NonRegisteredType>' for property 'MySequenceConversionObject::typeListProperty'");
        QQmlProperty seqProp(seq, "typeListProperty", &engine);
        QVERIFY(!seqProp.read().isValid()); // not a valid/known sequence type

        delete object;
    }
}

void tst_qqmlecmascript::sequenceConversionWrite()
{
    QQmlEngine engine;
    {
        QUrl qmlFile = testFileUrl("sequenceConversion.write.qml");
        QQmlComponent component(&engine, qmlFile);
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
        QVERIFY(seq != nullptr);

        QMetaObject::invokeMethod(object, "writeSequences");
        QCOMPARE(object->property("success").toBool(), true);

        QMetaObject::invokeMethod(object, "writeSequenceElements");
        QCOMPARE(object->property("success").toBool(), true);

        QMetaObject::invokeMethod(object, "writeOtherElements");
        QCOMPARE(object->property("success").toBool(), true);

        QMetaObject::invokeMethod(object, "testReferenceDeletion");
        QCOMPARE(object->property("referenceDeletion").toBool(), true);

        delete object;
    }

    {
        QUrl qmlFile = testFileUrl("sequenceConversion.write.error.qml");
        QQmlComponent component(&engine, qmlFile);
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
        QVERIFY(seq != nullptr);

        // Behavior change in 5.14: due to added auto-magical conversions, it is possible to assign to
        // QList<QPoint>, even though it is not a registered sequence type
        QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression("Could not convert array value at position 1 from QString to QPoint"));
        QMetaObject::invokeMethod(object, "performTest");

        QList<QPoint> pointList; pointList << QPoint(7, 7) << QPoint(0,0) << QPoint(8, 8) << QPoint(9, 9); // original values, shouldn't have changed
        QCOMPARE(seq->pointListProperty(), pointList);

        delete object;
    }
}

void tst_qqmlecmascript::sequenceConversionArray()
{
    // ensure that in JS the returned sequences act just like normal JS Arrays.
    QUrl qmlFile = testFileUrl("sequenceConversion.array.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, qmlFile);
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object.data(), "indexedAccess");
    QVERIFY(object->property("success").toBool());
    QMetaObject::invokeMethod(object.data(), "arrayOperations");
    QVERIFY(object->property("success").toBool());
    QMetaObject::invokeMethod(object.data(), "testEqualitySemantics");
    QVERIFY(object->property("success").toBool());
    QMetaObject::invokeMethod(object.data(), "testReferenceDeletion");
    QCOMPARE(object->property("referenceDeletion").toBool(), true);
    QMetaObject::invokeMethod(object.data(), "jsonConversion");
    QVERIFY(object->property("success").toBool());
}


void tst_qqmlecmascript::sequenceConversionIndexes()
{
    // ensure that we gracefully fail if unsupported index values are specified.
    // Qt container classes only support non-negative, signed integer index values.
    QUrl qmlFile = testFileUrl("sequenceConversion.indexes.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, qmlFile);
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QString w1 = qmlFile.toString() + QLatin1String(":34: Index out of range during length set");
    QString w2 = qmlFile.toString() + QLatin1String(":41: Index out of range during indexed set");
    QString w3 = qmlFile.toString() + QLatin1String(":48: Index out of range during indexed get");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w1));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w2));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(w3));
    QMetaObject::invokeMethod(object, "indexedAccess");
    QVERIFY(object->property("success").toBool());
    QMetaObject::invokeMethod(object, "indexOf");
    QVERIFY(object->property("success").toBool());
    delete object;
}

void tst_qqmlecmascript::sequenceConversionThreads()
{
    // ensure that sequence conversion operations work correctly in a worker thread
    // and that serialisation between the main and worker thread succeeds.
    QUrl qmlFile = testFileUrl("sequenceConversion.threads.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, qmlFile);
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QMetaObject::invokeMethod(object, "testIntSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    QMetaObject::invokeMethod(object, "testQrealSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    QMetaObject::invokeMethod(object, "testBoolSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    QMetaObject::invokeMethod(object, "testStringSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    QMetaObject::invokeMethod(object, "testQStringSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    QMetaObject::invokeMethod(object, "testUrlSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    QMetaObject::invokeMethod(object, "testVariantSequence");
    QTRY_VERIFY(object->property("finished").toBool());
    QVERIFY(object->property("success").toBool());

    delete object;
}

void tst_qqmlecmascript::sequenceConversionBindings()
{
    QQmlEngine engine;
    {
        QUrl qmlFile = testFileUrl("sequenceConversion.bindings.qml");
        QQmlComponent component(&engine, qmlFile);
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QList<int> intList; intList << 1 << 2 << 3 << 12 << 7;
        QCOMPARE(object->property("boundSequence").value<QList<int> >(), intList);
        QCOMPARE(object->property("boundElement").toInt(), intList.at(3));
        QList<int> intListTwo; intListTwo << 1 << 2 << 3 << 12 << 14;
        QCOMPARE(object->property("boundSequenceTwo").value<QList<int> >(), intListTwo);
        delete object;
    }

    {
        QUrl qmlFile = testFileUrl("sequenceConversion.bindings.error.qml");
        QString warning = QString(QLatin1String("%1:17:9: Unable to assign QList<int> to QList<bool>")).arg(qmlFile.toString());
        QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
        QQmlComponent component(&engine, qmlFile);
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        delete object;
    }
}

void tst_qqmlecmascript::sequenceConversionCopy()
{
    QUrl qmlFile = testFileUrl("sequenceConversion.copy.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, qmlFile);
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    QMetaObject::invokeMethod(object, "testCopySequences");
    QCOMPARE(object->property("success").toBool(), true);
    QMetaObject::invokeMethod(object, "readSequenceCopyElements");
    QCOMPARE(object->property("success").toBool(), true);
    QMetaObject::invokeMethod(object, "testEqualitySemantics");
    QCOMPARE(object->property("success").toBool(), true);
    QMetaObject::invokeMethod(object, "testCopyParameters");
    QCOMPARE(object->property("success").toBool(), true);
    QMetaObject::invokeMethod(object, "testReferenceParameters");
    QCOMPARE(object->property("success").toBool(), true);
    delete object;
}

void tst_qqmlecmascript::assignSequenceTypes()
{
    QQmlEngine engine;

    // test binding array to sequence type property
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.1.qml"));
    MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2));
    QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2 << 3));
    QCOMPARE(object->boolListProperty(), (QList<bool>() << false << true));
    QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com") << QUrl("http://www.example2.com")));
    QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one") << QLatin1String("two")));
    QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("one") << QLatin1String("two")));
    delete object;
    }

    // test binding literal to sequence type property
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.2.qml"));
    MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->intListProperty(), (QList<int>() << 1));
    QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1));
    QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
    QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com")));
    QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one")));
    QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("two")));
    delete object;
    }

    // test binding single value to sequence type property
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.3.qml"));
    MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->intListProperty(), (QList<int>() << 1));
    QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1));
    QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
    QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html"))));
    delete object;
    }

    // test assigning array to sequence type property in js function
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.4.qml"));
    MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2));
    QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2 << 3));
    QCOMPARE(object->boolListProperty(), (QList<bool>() << false << true));
    QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com") << QUrl("http://www.example2.com")));
    QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one") << QLatin1String("two")));
    QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("one") << QLatin1String("two")));
    delete object;
    }

    // test assigning literal to sequence type property in js function
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.5.qml"));
    MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->intListProperty(), (QList<int>() << 1));
    QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1));
    QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
    QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com")));
    QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one")));
    QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("two")));
    delete object;
    }

    // test assigning single value to sequence type property in js function
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.6.qml"));
    MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->intListProperty(), (QList<int>() << 1));
    QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1));
    QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
    QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html"))));
    delete object;
    }

    // test QList<QUrl> literal assignment and binding assignment causes url resolution when required
    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.7.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    MySequenceConversionObject *msco1 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco1"));
    MySequenceConversionObject *msco2 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco2"));
    MySequenceConversionObject *msco3 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco3"));
    MySequenceConversionObject *msco4 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco4"));
    MySequenceConversionObject *msco5 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco5"));
    QVERIFY(msco1 != nullptr && msco2 != nullptr && msco3 != nullptr && msco4 != nullptr && msco5 != nullptr);
    QCOMPARE(msco1->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html"))));
    QCOMPARE(msco2->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html"))));
    QCOMPARE(msco3->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html")) << QUrl(testFileUrl("example2.html"))));
    QCOMPARE(msco4->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html")) << QUrl(testFileUrl("example2.html"))));
    QCOMPARE(msco5->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html")) << QUrl(testFileUrl("example2.html"))));
    delete object;
    }

    {
    QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.8.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
    QVariant result;
    QMetaObject::invokeMethod(object.data(), "tryWritingReadOnlySequence", Q_RETURN_ARG(QVariant, result));
    QVERIFY(result.type() == QVariant::Bool);
    QVERIFY(result.toBool());
    }
}

// Test that assigning a null object works
// Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4
void tst_qqmlecmascript::nullObjectBinding()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("nullObjectBinding.qml"));

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

    QVERIFY(object->property("test") == QVariant::fromValue((QObject *)nullptr));

    delete object;
}

void tst_qqmlecmascript::nullObjectInitializer()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("nullObjectInitializer.qml"));
        QScopedPointer<QObject> obj(component.create());
        QVERIFY(!obj.isNull());

        QQmlData *ddata = QQmlData::get(obj.data(), /*create*/false);
        QVERIFY(ddata);

        {
            const int propertyIndex = obj->metaObject()->indexOfProperty("testProperty");
            QVERIFY(propertyIndex > 0);
            QVERIFY(!ddata->hasBindingBit(propertyIndex));
        }

        QVariant value = obj->property("testProperty");
        QVERIFY(value.userType() == qMetaTypeId<QObject*>());
        QVERIFY(!value.value<QObject*>());
    }

    {
        QQmlComponent component(&engine, testFileUrl("nullObjectInitializer.2.qml"));
        QScopedPointer<QObject> obj(component.create());
        QVERIFY(!obj.isNull());

        QQmlData *ddata = QQmlData::get(obj.data(), /*create*/false);
        QVERIFY(ddata);

        {
            const int propertyIndex = obj->metaObject()->indexOfProperty("testProperty");
            QVERIFY(propertyIndex > 0);
            QVERIFY(!ddata->hasBindingBit(propertyIndex));
        }

        QVERIFY(obj->property("success").toBool());

        QVariant value = obj->property("testProperty");
        QVERIFY(value.userType() == qMetaTypeId<QObject*>());
        QVERIFY(!value.value<QObject*>());
    }

    {
        QQmlComponent component(&engine, testFileUrl("qmlVarNullBinding.qml"));
        QScopedPointer<QObject> obj(component.create());
        QVERIFY(!obj.isNull());
        QVERIFY(!obj->property("signalSeen").toBool());
    }
}

// Test that bindings don't evaluate once the engine has been destroyed
void tst_qqmlecmascript::deletedEngine()
{
    QQmlEngine *engine = new QQmlEngine;
    QQmlComponent component(engine, testFileUrl("deletedEngine.qml"));

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

    QCOMPARE(object->property("a").toInt(), 39);
    object->setProperty("b", QVariant(9));
    QCOMPARE(object->property("a").toInt(), 117);

    delete engine;

    QCOMPARE(object->property("a").toInt(), 0);
    object->setProperty("b", QVariant(10));
    object->setProperty("b", QVariant());
    QCOMPARE(object->property("a").toInt(), 0);

    delete object;
}

// Test the crashing part of QTBUG-9705
void tst_qqmlecmascript::libraryScriptAssert()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("libraryScriptAssert.qml"));

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

    delete object;
}

void tst_qqmlecmascript::variantsAssignedUndefined()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("variantsAssignedUndefined.qml"));

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

    QCOMPARE(object->property("test1").toInt(), 10);
    QCOMPARE(object->property("test2").toInt(), 11);

    object->setProperty("runTest", true);

    QCOMPARE(object->property("test1"), QVariant());
    QCOMPARE(object->property("test2"), QVariant());


    delete object;
}

void tst_qqmlecmascript::variants()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("variants.qml"));

    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("undefinedVariant").type(), QVariant::Invalid);
    QCOMPARE(int(object->property("nullVariant").type()), int(QMetaType::Nullptr));
    QCOMPARE(object->property("intVariant").type(), QVariant::Int);
    QCOMPARE(object->property("doubleVariant").type(), QVariant::Double);

    QVariant result;
    QMetaObject::invokeMethod(object.get(), "checkNull", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);

    QMetaObject::invokeMethod(object.get(), "checkUndefined", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);

    QMetaObject::invokeMethod(object.get(), "checkNumber", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);
}

void tst_qqmlecmascript::qtbug_9792()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_9792.qml"));

    QQmlContext *context = new QQmlContext(engine.rootContext());

    MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create(context));
    QVERIFY(object != nullptr);

    QTest::ignoreMessage(QtDebugMsg, "Hello world!");
    object->basicSignal();

    delete context;

    QQmlTestMessageHandler messageHandler;

    object->basicSignal();

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));

    delete object;
}

// Verifies that QPointer<>s used in the vmemetaobject are cleaned correctly
void tst_qqmlecmascript::qtcreatorbug_1289()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtcreatorbug_1289.qml"));

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

    QObject *nested = qvariant_cast<QObject *>(o->property("object"));
    QVERIFY(nested != nullptr);

    QVERIFY(qvariant_cast<QObject *>(nested->property("nestedObject")) == o);

    delete nested;
    nested = qvariant_cast<QObject *>(o->property("object"));
    QVERIFY(!nested);

    // If the bug is present, the next line will crash
    delete o;
}

// Test that we shut down without stupid warnings
void tst_qqmlecmascript::noSpuriousWarningsAtShutdown()
{
    QQmlEngine engine;
    {
    QQmlComponent component(&engine, testFileUrl("noSpuriousWarningsAtShutdown.qml"));

    QObject *o = component.create();

    QQmlTestMessageHandler messageHandler;

    delete o;

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
    }


    {
    QQmlComponent component(&engine, testFileUrl("noSpuriousWarningsAtShutdown.2.qml"));

    QObject *o = component.create();

    QQmlTestMessageHandler messageHandler;

    delete o;

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
    }
}

void tst_qqmlecmascript::canAssignNullToQObject()
{
    QQmlEngine engine;
    {
    QQmlComponent component(&engine, testFileUrl("canAssignNullToQObject.1.qml"));

    MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(o != nullptr);

    QVERIFY(o->objectProperty() != nullptr);

    o->setProperty("runTest", true);

    QVERIFY(!o->objectProperty());

    delete o;
    }

    {
    QQmlComponent component(&engine, testFileUrl("canAssignNullToQObject.2.qml"));

    MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(o != nullptr);

    QVERIFY(!o->objectProperty());

    delete o;
    }
}

void tst_qqmlecmascript::functionAssignment_fromBinding()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("functionAssignment.1.qml"));

    QString url = component.url().toString();
    QString w1 = url + ":4:5: Unable to assign a function to a property of any type other than var.";
    QString w2 = url + ":5:5: Invalid use of Qt.binding() in a binding declaration.";
    QString w3 = url + ":6:5: Invalid use of Qt.binding() in a binding declaration.";
    QString w4 = url + ":7:5: Invalid use of Qt.binding() in a binding declaration.";
    QTest::ignoreMessage(QtWarningMsg, w1.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, w2.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, w3.toLatin1().constData());
    QTest::ignoreMessage(QtWarningMsg, w4.toLatin1().constData());

    MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(o != nullptr);

    QVERIFY(!o->property("a").isValid());

    delete o;
}

void tst_qqmlecmascript::functionAssignment_fromJS()
{
    QFETCH(QString, triggerProperty);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("functionAssignment.2.qml"));
    QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString()));

    MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(o != nullptr);
    QVERIFY(!o->property("a").isValid());

    o->setProperty("aNumber", QVariant(5));
    o->setProperty(triggerProperty.toUtf8().constData(), true);
    QCOMPARE(o->property("a"), QVariant(50));

    o->setProperty("aNumber", QVariant(10));
    QCOMPARE(o->property("a"), QVariant(100));

    delete o;
}

void tst_qqmlecmascript::functionAssignment_fromJS_data()
{
    QTest::addColumn<QString>("triggerProperty");

    QTest::newRow("assign to property") << "assignToProperty";
    QTest::newRow("assign to property, from JS file") << "assignToPropertyFromJsFile";

    QTest::newRow("assign to value type") << "assignToValueType";

    QTest::newRow("use 'this'") << "assignWithThis";
    QTest::newRow("use 'this' from JS file") << "assignWithThisFromJsFile";
}

void tst_qqmlecmascript::functionAssignmentfromJS_invalid()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("functionAssignment.2.qml"));
    QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString()));

    MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(o != nullptr);
    QVERIFY(!o->property("a").isValid());

    o->setProperty("assignFuncWithoutReturn", true);
    QVERIFY(!o->property("a").isValid());

    QString url = component.url().toString();
    QString warning = url + ":67: Unable to assign QString to int";
    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    o->setProperty("assignWrongType", true);

    warning = url + ":71: Unable to assign QString to int";
    QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData());
    o->setProperty("assignWrongTypeToValueType", true);

    delete o;
}

void tst_qqmlecmascript::functionAssignment_afterBinding()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("functionAssignment.3.qml"));

    QString url = component.url().toString();
    QString w1 = url + ":16: Error: Cannot assign JavaScript function to int";
    QTest::ignoreMessage(QtWarningMsg, w1.toLatin1().constData());

    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("t1"), QVariant::fromValue<int>(4)); // should have bound
    QCOMPARE(o->property("t2"), QVariant::fromValue<int>(2)); // should not have changed

    delete o;
}

void tst_qqmlecmascript::eval()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("eval.qml"));

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

    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test4").toBool(), true);
    QCOMPARE(o->property("test5").toBool(), true);

    delete o;
}

void tst_qqmlecmascript::function()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("function.qml"));

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

    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);

    delete o;
}

// QTBUG-77096
void tst_qqmlecmascript::topLevelGeneratorFunction()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("generatorFunction.qml"));

    QScopedPointer<QObject> o {component.create()};
    if (!o)
        qDebug() << component.errorString();
    QVERIFY(o != nullptr);

    // check that generator works correctly in QML
    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("done").toBool(), true);

    // check that generator is accessible from C++
    QVariant returnedValue;
    QMetaObject::invokeMethod(o.get(), "gen", Q_RETURN_ARG(QVariant, returnedValue));
    auto it = returnedValue.value<QJSValue>();
    QCOMPARE(it.property("next").callWithInstance(it).property("value").toInt(), 1);
}

// QTBUG-91491
void tst_qqmlecmascript::generatorCrashNewProperty()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("generatorCrashNewProperty.qml"));

    QScopedPointer<QObject> o(component.create());

    QVERIFY2(o != nullptr, qPrintable(component.errorString()));

    QCOMPARE(o->property("a").toInt(), 42);
    QCOMPARE(o->property("b").toInt(), 12);
    QCOMPARE(o->property("c").toInt(), 42);
}

void tst_qqmlecmascript::generatorCallsGC()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("generatorCallsGC.qml"));

    QScopedPointer<QObject> o(component.create()); // should not crash
    QVERIFY2(o != nullptr, qPrintable(component.errorString()));
}

// Test the "Qt.include" method
void tst_qqmlecmascript::include()
{
    QQmlEngine engine;
    // Non-library relative include
    {
    QQmlComponent component(&engine, testFileUrl("include.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test0").toInt(), 99);
    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test2_1").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test3_1").toBool(), true);

    delete o;
    }

    // Library relative include
    {
    QQmlComponent component(&engine, testFileUrl("include_shared.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test0").toInt(), 99);
    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test2_1").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test3_1").toBool(), true);

    delete o;
    }

    // Callback
    {
    QQmlComponent component(&engine, testFileUrl("include_callback.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test4").toBool(), true);
    QCOMPARE(o->property("test5").toBool(), true);
    QCOMPARE(o->property("test6").toBool(), true);

    delete o;
    }

    // Including file with ".pragma library"
    {
    QQmlComponent component(&engine, testFileUrl("include_pragma.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test1").toInt(), 100);

    delete o;
    }

    // Including file with ".pragma library", shadowing a global var
    {
    QQmlComponent component(&engine, testFileUrl("include_pragma_shadow.qml"));
    QScopedPointer<QObject> o(component.create());
    QVERIFY(!o.isNull());
    QCOMPARE(o->property("result").toBool(), true);
    }

    // Remote - error
    {
    TestHTTPServer server;
    QVERIFY2(server.listen(), qPrintable(server.errorString()));
    server.serveDirectory(dataDirectory());

    QQmlComponent component(&engine, testFileUrl("include_remote_missing.qml"));
    QObject *o = component.beginCreate(engine.rootContext());
    QVERIFY(o != nullptr);
    o->setProperty("serverBaseUrl", server.baseUrl().toString());
    component.completeCreate();

    QTRY_VERIFY(o->property("done").toBool());

    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);

    delete o;
    }

    // include from resources
    {
    QQmlComponent component(&engine, QUrl("qrc:///data/include.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test0").toInt(), 99);
    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test2_1").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test3_1").toBool(), true);

    delete o;
    }

}

void tst_qqmlecmascript::includeRemoteSuccess()
{
    // Remote - success
    TestHTTPServer server;
    QVERIFY2(server.listen(), qPrintable(server.errorString()));
    server.serveDirectory(dataDirectory());

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("include_remote.qml"));
    QObject *o = component.beginCreate(engine.rootContext());
    QVERIFY(o != nullptr);
    o->setProperty("serverBaseUrl", server.baseUrl().toString());
    component.completeCreate();

    QTRY_VERIFY(o->property("done").toBool());
    QTRY_VERIFY(o->property("done2").toBool());

    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test4").toBool(), true);
    QCOMPARE(o->property("test5").toBool(), true);

    QCOMPARE(o->property("test6").toBool(), true);
    QCOMPARE(o->property("test7").toBool(), true);
    QCOMPARE(o->property("test8").toBool(), true);
    QCOMPARE(o->property("test9").toBool(), true);
    QCOMPARE(o->property("test10").toBool(), true);

    delete o;
}

void tst_qqmlecmascript::signalHandlers()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("signalHandlers.qml"));
    QScopedPointer<QObject> o(component.create());
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("count").toInt(), 0);
    QMetaObject::invokeMethod(o.data(), "testSignalCall");
    QCOMPARE(o->property("count").toInt(), 1);

    QMetaObject::invokeMethod(o.data(), "testSignalHandlerCall");
    QCOMPARE(o->property("count").toInt(), 1);
    QString scopeObjectAsString = o->property("scopeObjectAsString").toString();
    QCOMPARE(o->property("errorString").toString(), QString("TypeError: Property 'onTestSignal' of object %1 is not a function").arg(scopeObjectAsString));

    QCOMPARE(o->property("funcCount").toInt(), 0);
    QMetaObject::invokeMethod(o.data(), "testSignalConnection");
    QCOMPARE(o->property("funcCount").toInt(), 1);

    QMetaObject::invokeMethod(o.data(), "testSignalHandlerConnection");
    QCOMPARE(o->property("funcCount").toInt(), 2);

    QMetaObject::invokeMethod(o.data(), "testSignalDefined");
    QCOMPARE(o->property("definedResult").toBool(), true);

    QMetaObject::invokeMethod(o.data(), "testSignalHandlerDefined");
    QCOMPARE(o->property("definedHandlerResult").toBool(), true);

    QVariant result;
    QMetaObject::invokeMethod(o.data(), "testConnectionOnAlias", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);

    QMetaObject::invokeMethod(o.data(), "testAliasSignalHandler", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);

    QMetaObject::invokeMethod(o.data(), "testSignalWithClosureArgument", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);
    QMetaObject::invokeMethod(o.data(), "testThisInSignalHandler", Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result.toBool(), true);
}

void tst_qqmlecmascript::qtbug_37351()
{
    MyTypeObject signalEmitter;
    {
        QQmlEngine engine;
        QQmlComponent component(&engine);
        component.setData("import Qt.test 1.0; import QtQml 2.0;\nQtObject {\n"
            "    Component.onCompleted: {\n"
            "        testObject.action.connect(function() { print('dont crash'); });"
            "    }\n"
            "}", QUrl());

        engine.rootContext()->setContextProperty("testObject", &signalEmitter);

        QScopedPointer<QObject> object(component.create());
        QVERIFY(!object.isNull());
    }
    signalEmitter.doAction();
    // Don't crash
}

void tst_qqmlecmascript::qtbug_10696()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_10696.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    delete o;
}

void tst_qqmlecmascript::qtbug_11606()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_11606.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

void tst_qqmlecmascript::qtbug_11600()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_11600.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

void tst_qqmlecmascript::qtbug_21864()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_21864.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

void tst_qqmlecmascript::rewriteMultiLineStrings()
{
    QQmlEngine engine;
    {
        // QTBUG-23387
        QQmlComponent component(&engine, testFileUrl("rewriteMultiLineStrings.qml"));
        QObject *o = component.create();
        QVERIFY(o != nullptr);
        QTRY_COMPARE(o->property("test").toBool(), true);
        delete o;
    }

    {
        QQmlComponent component(&engine, testFileUrl("rewriteMultiLineStrings_crlf.1.qml"));
        QObject *o = component.create();
        QVERIFY(o != nullptr);
        delete o;
    }
}

void tst_qqmlecmascript::qobjectConnectionListExceptionHandling()
{
    // QTBUG-23375
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qobjectConnectionListExceptionHandling.qml"));
    QString warning = component.url().toString() + QLatin1String(":13: TypeError: Cannot read property 'undefined' of undefined");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

// Reading and writing non-scriptable properties should fail
void tst_qqmlecmascript::nonscriptable()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("nonscriptable.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("readOk").toBool(), true);
    QCOMPARE(o->property("writeOk").toBool(), true);
    delete o;
}

// deleteLater() should not be callable from QML
void tst_qqmlecmascript::deleteLater()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deleteLater.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

// objectNameChanged() should be usable from QML
void tst_qqmlecmascript::objectNameChangedSignal()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("objectNameChangedSignal.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test").toBool(), false);
    o->setObjectName("obj");
    QCOMPARE(o->property("test").toBool(), true);
    delete o;
}

// destroyed() should not be usable from QML
void tst_qqmlecmascript::destroyedSignal()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("destroyedSignal.qml"));
    QVERIFY(component.isError());

    QString expectedErrorString = component.url().toString() + QLatin1String(":5:5: Cannot assign to non-existent property \"onDestroyed\"");
    QCOMPARE(component.errors().at(0).toString(), expectedErrorString);
}

void tst_qqmlecmascript::in()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("in.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    delete o;
}

void tst_qqmlecmascript::typeOf()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("typeOf.qml"));

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

    QCOMPARE(o->property("test1").toString(), QLatin1String("undefined"));
    QCOMPARE(o->property("test2").toString(), QLatin1String("object"));
    QCOMPARE(o->property("test3").toString(), QLatin1String("number"));
    QCOMPARE(o->property("test4").toString(), QLatin1String("string"));
    QCOMPARE(o->property("test5").toString(), QLatin1String("function"));
    QCOMPARE(o->property("test6").toString(), QLatin1String("object"));
    QCOMPARE(o->property("test7").toString(), QLatin1String("undefined"));
    QCOMPARE(o->property("test8").toString(), QLatin1String("boolean"));
    QCOMPARE(o->property("test9").toString(), QLatin1String("object"));

    delete o;
}

void tst_qqmlecmascript::qtbug_24448()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_24448.qml"));
    QScopedPointer<QObject> o(component.create());
    QVERIFY(o != nullptr);
    QVERIFY(o->property("test").toBool());
}

void tst_qqmlecmascript::sharedAttachedObject()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("sharedAttachedObject.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    delete o;
}

// QTBUG-13999
void tst_qqmlecmascript::objectName()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("objectName.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test1").toString(), QString("hello"));
    QCOMPARE(o->property("test2").toString(), QString("ell"));

    o->setObjectName("world");

    QCOMPARE(o->property("test1").toString(), QString("world"));
    QCOMPARE(o->property("test2").toString(), QString("orl"));

    delete o;
}

void tst_qqmlecmascript::writeRemovesBinding()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("writeRemovesBinding.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
}

// Test bindings assigned to alias properties actually assign to the alias' target
void tst_qqmlecmascript::aliasBindingsAssignCorrectly()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("aliasBindingsAssignCorrectly.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
}

// Test bindings assigned to alias properties override a binding on the target (QTBUG-13719)
void tst_qqmlecmascript::aliasBindingsOverrideTarget()
{
    QQmlEngine engine;
    {
    QQmlComponent component(&engine, testFileUrl("aliasBindingsOverrideTarget.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
    }

    {
    QQmlComponent component(&engine, testFileUrl("aliasBindingsOverrideTarget.2.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
    }

    {
    QQmlComponent component(&engine, testFileUrl("aliasBindingsOverrideTarget.3.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
    }
}

// Test that writes to alias properties override bindings on the alias target (QTBUG-13719)
void tst_qqmlecmascript::aliasWritesOverrideBindings()
{
    QQmlEngine engine;
    {
    QQmlComponent component(&engine, testFileUrl("aliasWritesOverrideBindings.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
    }

    {
    QQmlComponent component(&engine, testFileUrl("aliasWritesOverrideBindings.2.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
    }

    {
    QQmlComponent component(&engine, testFileUrl("aliasWritesOverrideBindings.3.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test").toBool(), true);

    delete o;
    }
}

// Allow an alais to a composite element
// QTBUG-20200
void tst_qqmlecmascript::aliasToCompositeElement()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("aliasToCompositeElement.qml"));

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

    delete object;
}

void tst_qqmlecmascript::qtbug_20344()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_20344.qml"));

    QString warning = component.url().toString() + ":5: Error: Exception thrown from within QObject slot";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));

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

    delete object;
}

void tst_qqmlecmascript::revisionErrors()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevisionErrors.qml"));
        QString url = component.url().toString();

        QString warning1 = url + ":8: ReferenceError: prop2 is not defined";
        QString warning2 = url + ":11: ReferenceError: prop2 is not defined";
        QString warning3 = url + ":13: ReferenceError: method2 is not defined";

        QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData());
        MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create());
        QVERIFY(object != nullptr);
        delete object;
    }
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevisionErrors2.qml"));
        QString url = component.url().toString();

        // MyRevisionedSubclass 1.0 uses MyRevisionedClass revision 0
        // method2, prop2 from MyRevisionedClass not available
        // method4, prop4 from MyRevisionedSubclass not available
        QString warning1 = url + ":8: ReferenceError: prop2 is not defined";
        QString warning2 = url + ":14: ReferenceError: prop2 is not defined";
        QString warning3 = url + ":10: ReferenceError: prop4 is not defined";
        QString warning4 = url + ":16: ReferenceError: prop4 is not defined";
        QString warning5 = url + ":20: ReferenceError: method2 is not defined";

        QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning4.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning5.toLatin1().constData());
        MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create());
        QVERIFY(object != nullptr);
        delete object;
    }
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevisionErrors3.qml"));
        QString url = component.url().toString();

        // MyRevisionedSubclass 1.1 uses MyRevisionedClass revision 1
        // All properties/methods available, except MyRevisionedBaseClassUnregistered rev 1
        QString warning1 = url + ":30: ReferenceError: methodD is not defined";
        QString warning2 = url + ":10: ReferenceError: propD is not defined";
        QString warning3 = url + ":20: ReferenceError: propD is not defined";
        QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData());
        QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData());
        MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create());
        QVERIFY(object != nullptr);
        delete object;
    }
}

void tst_qqmlecmascript::revision()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevision.qml"));
        QString url = component.url().toString();

        MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create());
        QVERIFY(object != nullptr);
        delete object;
    }
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevision2.qml"));
        QString url = component.url().toString();

        MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create());
        QVERIFY(object != nullptr);
        delete object;
    }
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevision3.qml"));
        QString url = component.url().toString();

        MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create());
        QVERIFY(object != nullptr);
        delete object;
    }
    // Test that non-root classes can resolve revisioned methods
    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevision4.qml"));

        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("test").toReal(), 11.);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("metaobjectRevision5.qml"));

        QObject *object = component.create();
        QVERIFY(object != nullptr);
        QCOMPARE(object->property("test").toReal(), 11.);
        delete object;
    }
}

void tst_qqmlecmascript::realToInt()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("realToInt.qml"));
    QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
    QVERIFY(object != nullptr);

    QMetaObject::invokeMethod(object.get(), "test1");
    QCOMPARE(object->value(), int(4));
    QMetaObject::invokeMethod(object.get(), "test2");
    QCOMPARE(object->value(), int(7));
}

void tst_qqmlecmascript::urlProperty()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("urlProperty.1.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);
        object->setStringProperty("http://qt-project.org");
        QCOMPARE(object->urlProperty(), QUrl("http://qt-project.org/index.html"));
        QCOMPARE(object->intProperty(), 123);
        QCOMPARE(object->value(), 1);
        QCOMPARE(object->property("result").toBool(), true);
    }
}

void tst_qqmlecmascript::urlPropertyWithEncoding()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("urlProperty.2.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);
        object->setStringProperty("http://qt-project.org");
        const QUrl encoded = QUrl::fromEncoded("http://qt-project.org/?get%3cDATA%3e", QUrl::TolerantMode);
        QCOMPARE(object->urlProperty(), encoded);
        QCOMPARE(object->value(), 0);   // Interpreting URL as string yields canonicalised version
        QCOMPARE(object->property("result").toBool(), true);
    }
}

void tst_qqmlecmascript::urlListPropertyWithEncoding()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("urlListProperty.qml"));
        QObject *object = component.create();
        QVERIFY(object != nullptr);
        MySequenceConversionObject *msco1 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco1"));
        MySequenceConversionObject *msco2 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco2"));
        MySequenceConversionObject *msco3 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco3"));
        MySequenceConversionObject *msco4 = object->findChild<MySequenceConversionObject *>(QLatin1String("msco4"));
        QVERIFY(msco1 != nullptr && msco2 != nullptr && msco3 != nullptr && msco4 != nullptr);
        const QUrl encoded = QUrl::fromEncoded("http://qt-project.org/?get%3cDATA%3e", QUrl::TolerantMode);
        QCOMPARE(msco1->urlListProperty(), (QList<QUrl>() << encoded));
        QCOMPARE(msco2->urlListProperty(), (QList<QUrl>() << encoded));
        QCOMPARE(msco3->urlListProperty(), (QList<QUrl>() << encoded << encoded));
        QCOMPARE(msco4->urlListProperty(), (QList<QUrl>() << encoded << encoded));
        delete object;
    }
}

void tst_qqmlecmascript::dynamicString()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("dynamicString.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("stringProperty").toString(),
             QString::fromLatin1("string:Hello World false:0 true:1 uint32:100 int32:-100 double:3.14159 date:2011-02-11 05::30:50!"));
}

void tst_qqmlecmascript::deleteLaterObjectMethodCall()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deleteLaterObjectMethodCall.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

void tst_qqmlecmascript::automaticSemicolon()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("automaticSemicolon.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

void tst_qqmlecmascript::compatibilitySemicolon()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("compatibilitySemicolon.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

void tst_qqmlecmascript::incrDecrSemicolon1()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("incrDecrSemicolon1.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

void tst_qqmlecmascript::incrDecrSemicolon2()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("incrDecrSemicolon2.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

void tst_qqmlecmascript::incrDecrSemicolon_error1()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("incrDecrSemicolon_error1.qml"));
    QObject *object = component.create();
    QVERIFY(!object);
}

void tst_qqmlecmascript::unaryExpression()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("unaryExpression.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

// Makes sure that a binding isn't double re-evaluated when it depends on the same variable twice
void tst_qqmlecmascript::doubleEvaluate()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("doubleEvaluate.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    WriteCounter *wc = qobject_cast<WriteCounter *>(object);
    QVERIFY(wc != nullptr);
    QCOMPARE(wc->count(), 1);

    wc->setProperty("x", 9);

    QCOMPARE(wc->count(), 2);

    delete object;
}

void tst_qqmlecmascript::nonNotifyable()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("nonNotifyable.qml"));

    QQmlTestMessageHandler messageHandler;

    QObject *object = component.create();

    QVERIFY(object != nullptr);

    QString expected1 = QLatin1String("QQmlExpression: Expression ") +
                        component.url().toString() +
                        QLatin1String(":5:5 depends on non-NOTIFYable properties:");
    QString expected2 = QLatin1String("    ") +
                        QLatin1String(object->metaObject()->className()) +
                        QLatin1String("::value");

    QCOMPARE(messageHandler.messages().length(), 2);
    QCOMPARE(messageHandler.messages().at(0), expected1);
    QCOMPARE(messageHandler.messages().at(1), expected2);

    delete object;
}

void tst_qqmlecmascript::nonNotifyableConstant()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("nonNotifyableConstant.qml"));
    QQmlTestMessageHandler messageHandler;

    // Shouldn't produce an error message about non-NOTIFYable properties,
    // as the property has the CONSTANT attribute.
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object);
    QCOMPARE(messageHandler.messages().size(), 0);
}

void tst_qqmlecmascript::forInLoop()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("forInLoop.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QMetaObject::invokeMethod(object, "listProperty");

    QStringList r = object->property("listResult").toString().split("|", Qt::SkipEmptyParts);
    QCOMPARE(r.size(), 3);
    QCOMPARE(r[0],QLatin1String("0=obj1"));
    QCOMPARE(r[1],QLatin1String("1=obj2"));
    QCOMPARE(r[2],QLatin1String("2=obj3"));

    //TODO: should test for in loop for other objects (such as QObjects) as well.

    delete object;
}

// An object the binding depends on is deleted while the binding is still running
void tst_qqmlecmascript::deleteWhileBindingRunning()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("deleteWhileBindingRunning.qml"));
    QObject *object = component.create();
    QVERIFY(object != nullptr);
    delete object;
}

void tst_qqmlecmascript::qtbug_22679()
{
    MyQmlObject object;
    object.setStringProperty(QLatin1String("Please work correctly"));
    QQmlEngine engine;
    engine.rootContext()->setContextProperty("contextProp", &object);

    QQmlComponent component(&engine, testFileUrl("qtbug_22679.qml"));
    qRegisterMetaType<QList<QQmlError> >("QList<QQmlError>");
    QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>)));

    QObject *o = component.create();
    QVERIFY(o != nullptr);
    QCOMPARE(warningsSpy.count(), 0);
    delete o;
}

void tst_qqmlecmascript::qtbug_22843_data()
{
    QTest::addColumn<bool>("library");

    QTest::newRow("without .pragma library") << false;
    QTest::newRow("with .pragma library") << true;
}

void tst_qqmlecmascript::qtbug_22843()
{
    QFETCH(bool, library);

    QString fileName("qtbug_22843");
    if (library)
        fileName += QLatin1String(".library");
    fileName += QLatin1String(".qml");

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl(fileName));

    QString url = component.url().toString();
    QString expectedError = url.left(url.length()-3) + QLatin1String("js:4:16: Expected token `;'");

    QVERIFY(component.isError());
    QCOMPARE(component.errors().value(1).toString(), expectedError);
}


void tst_qqmlecmascript::switchStatement()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("switchStatement.1.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        // `object->value()' is the number of executed statements

        object->setStringProperty("A");
        QCOMPARE(object->value(), 5);

        object->setStringProperty("S");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("D");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("F");
        QCOMPARE(object->value(), 4);

        object->setStringProperty("something else");
        QCOMPARE(object->value(), 1);
    }

    {
        QQmlComponent component(&engine, testFileUrl("switchStatement.2.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        // `object->value()' is the number of executed statements

        object->setStringProperty("A");
        QCOMPARE(object->value(), 5);

        object->setStringProperty("S");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("D");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("F");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("something else");
        QCOMPARE(object->value(), 4);
    }

    {
        QQmlComponent component(&engine, testFileUrl("switchStatement.3.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        // `object->value()' is the number of executed statements

        object->setStringProperty("A");
        QCOMPARE(object->value(), 5);

        object->setStringProperty("S");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("D");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("F");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("something else");
        QCOMPARE(object->value(), 6);
    }

    {
        QQmlComponent component(&engine, testFileUrl("switchStatement.4.qml"));

        QString warning = component.url().toString() + ":4:5: Unable to assign [undefined] to int";
        QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));

        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        // `object->value()' is the number of executed statements

        object->setStringProperty("A");
        QCOMPARE(object->value(), 5);

        object->setStringProperty("S");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("D");
        QCOMPARE(object->value(), 3);

        object->setStringProperty("F");
        QCOMPARE(object->value(), 3);

        QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));

        object->setStringProperty("something else");
    }

    {
        QQmlComponent component(&engine, testFileUrl("switchStatement.5.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        // `object->value()' is the number of executed statements

        object->setStringProperty("A");
        QCOMPARE(object->value(), 1);

        object->setStringProperty("S");
        QCOMPARE(object->value(), 1);

        object->setStringProperty("D");
        QCOMPARE(object->value(), 1);

        object->setStringProperty("F");
        QCOMPARE(object->value(), 1);

        object->setStringProperty("something else");
        QCOMPARE(object->value(), 1);
    }

    {
        QQmlComponent component(&engine, testFileUrl("switchStatement.6.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        // `object->value()' is the number of executed statements

        object->setStringProperty("A");
        QCOMPARE(object->value(), 123);

        object->setStringProperty("S");
        QCOMPARE(object->value(), 123);

        object->setStringProperty("D");
        QCOMPARE(object->value(), 321);

        object->setStringProperty("F");
        QCOMPARE(object->value(), 321);

        object->setStringProperty("something else");
        QCOMPARE(object->value(), 0);
    }
}

void tst_qqmlecmascript::withStatement()
{
    QQmlEngine engine;
    {
        QUrl url = testFileUrl("withStatement.1.qml");
        QQmlComponent component(&engine, url);
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        QCOMPARE(object->value(), 123);
    }
}

void tst_qqmlecmascript::tryStatement()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("tryStatement.1.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        QCOMPARE(object->value(), 123);
    }

    {
        QQmlComponent component(&engine, testFileUrl("tryStatement.2.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        QCOMPARE(object->value(), 321);
    }

    {
        QQmlComponent component(&engine, testFileUrl("tryStatement.3.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        QVERIFY(object->qjsvalue().isUndefined());
    }

    {
        QQmlComponent component(&engine, testFileUrl("tryStatement.4.qml"));
        QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
        QVERIFY(object != nullptr);

        QVERIFY(object->qjsvalue().isUndefined());
    }
}

class CppInvokableWithQObjectDerived : public QObject
{
    Q_OBJECT
public:
    CppInvokableWithQObjectDerived() {}
    ~CppInvokableWithQObjectDerived() {}

    Q_INVOKABLE MyQmlObject *createMyQmlObject(QString data)
    {
        MyQmlObject *obj = new MyQmlObject();
        obj->setStringProperty(data);
        return obj;
    }

    Q_INVOKABLE QString getStringProperty(MyQmlObject *obj)
    {
        return obj->stringProperty();
    }
};

void tst_qqmlecmascript::invokableWithQObjectDerived()
{
    CppInvokableWithQObjectDerived invokable;

    {
    QQmlEngine engine;
    engine.rootContext()->setContextProperty("invokable", &invokable);

    QQmlComponent component(&engine, testFileUrl("qobjectDerivedArgument.qml"));

    QObject *object = component.create();

    QVERIFY(object != nullptr);
    QVERIFY(object->property("result").value<bool>());

    delete object;
    }
}

void tst_qqmlecmascript::realTypePrecision()
{
    // Properties and signal parameters of type real should have double precision.
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("realTypePrecision.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("test").toDouble(), 1234567890.);
    QCOMPARE(object->property("test2").toDouble(), 1234567890.);
    QCOMPARE(object->property("test3").toDouble(), 1234567890.);
    QCOMPARE(object->property("test4").toDouble(), 1234567890.);
    QCOMPARE(object->property("test5").toDouble(), 1234567890.);
    QCOMPARE(object->property("test6").toDouble(), 1234567890.*2);
}

void tst_qqmlecmascript::registeredFlagMethod()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("registeredFlagMethod.qml"));
    MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->buttons(), 0);
    emit object->basicSignal();
    QCOMPARE(object->buttons(), Qt::RightButton);

    delete object;
}

// QTBUG-23138
void tst_qqmlecmascript::replaceBinding()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("replaceBinding.qml"));
    QObject *obj = c.create();
    QVERIFY(obj != nullptr);

    QVERIFY(obj->property("success").toBool());
    delete obj;
}

void tst_qqmlecmascript::bindingBoundFunctions()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("bindingBoundFunctions.qml"));
    QObject *obj = c.create();
    QVERIFY(obj != nullptr);

    QVERIFY(obj->property("success").toBool());
    delete obj;
}

void tst_qqmlecmascript::deleteRootObjectInCreation()
{
    QQmlEngine engine;
    {
    QQmlComponent c(&engine, testFileUrl("deleteRootObjectInCreation.qml"));
    QObject *obj = c.create();
    QVERIFY(obj != nullptr);
    QVERIFY(obj->property("rootIndestructible").toBool());
    QVERIFY(!obj->property("childDestructible").toBool());
    QTest::qWait(1);
    QVERIFY(obj->property("childDestructible").toBool());
    delete obj;
    }

    {
    QQmlComponent c(&engine, testFileUrl("deleteRootObjectInCreation.2.qml"));
    QObject *object = c.create();
    QVERIFY(object != nullptr);
    QVERIFY(object->property("testConditionsMet").toBool());
    delete object;
    }
}

void tst_qqmlecmascript::onDestruction()
{
    {
        // Delete object manually to invoke the associated handlers,
        // prior to engine destruction.
        QQmlEngine engine;
        QQmlComponent c(&engine, testFileUrl("onDestruction.qml"));
        QObject *obj = c.create();
        QVERIFY(obj != nullptr);
        delete obj;
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    }

    {
        // In this case, the teardown of the engine causes deletion
        // of contexts and child items.  This triggers the
        // onDestruction handler of a (previously .destroy()ed)
        // component instance.  This shouldn't crash.
        QQmlEngine engine;
        QQmlComponent c(&engine, testFileUrl("onDestruction.qml"));
        QScopedPointer<QObject> obj(c.create());
        QVERIFY(obj != nullptr);
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
    }
}

class WeakReferenceMutator : public QObject
{
    Q_OBJECT
public:
    WeakReferenceMutator()
        : resultPtr(nullptr)
        , weakRef(nullptr)
    {}

    void init(QV4::ExecutionEngine *v4, QV4::WeakValue *weakRef, bool *resultPtr)
    {
        QV4::QObjectWrapper::wrap(v4, this);
        QQmlEngine::setObjectOwnership(this, QQmlEngine::JavaScriptOwnership);

        this->resultPtr = resultPtr;
        this->weakRef = weakRef;

        QObject::connect(QQmlComponent::qmlAttachedProperties(this), &QQmlComponentAttached::destruction, this, &WeakReferenceMutator::reviveFirstWeakReference);
    }

private slots:
    void reviveFirstWeakReference() {
        // weakRef is not required to be undefined here. The gc can clear it later.
        *resultPtr = weakRef->valueRef();
        if (!*resultPtr)
            return;
        QV4::ExecutionEngine *v4 = qmlEngine(this)->handle();
        weakRef->set(v4, v4->newObject());
        *resultPtr = weakRef->valueRef() && !weakRef->isNullOrUndefined();
    }

public:
    bool *resultPtr;

    QV4::WeakValue *weakRef;
};

QT_BEGIN_NAMESPACE

namespace QV4 {

namespace Heap {
struct WeakReferenceSentinel : public Object {
    void init(WeakValue *weakRef, bool *resultPtr)
    {
        Object::init();
        this->weakRef = weakRef;
        this->resultPtr = resultPtr;
    }

    void destroy() {
        *resultPtr = weakRef->isNullOrUndefined();
        Object::destroy();
    }

    WeakValue *weakRef;
    bool *resultPtr;
};
} // namespace Heap

struct WeakReferenceSentinel : public Object {
    V4_OBJECT2(WeakReferenceSentinel, Object)
    V4_NEEDS_DESTROY
};

} // namespace QV4

QT_END_NAMESPACE

DEFINE_OBJECT_VTABLE(QV4::WeakReferenceSentinel);

void tst_qqmlecmascript::onDestructionViaGC()
{
    qmlRegisterType<WeakReferenceMutator>("Test", 1, 0, "WeakReferenceMutator");

    QQmlEngine engine;
    QV4::ExecutionEngine *v4 =engine.handle();

    QQmlComponent component(&engine, testFileUrl("DestructionHelper.qml"));

    QScopedPointer<QV4::WeakValue> weakRef;

    bool mutatorResult = false;
    bool sentinelResult = false;

    {
        weakRef.reset(new QV4::WeakValue);
        weakRef->set(v4, v4->newObject());
        QVERIFY(!weakRef->isNullOrUndefined());

        QPointer<WeakReferenceMutator> weakReferenceMutator = qobject_cast<WeakReferenceMutator *>(component.create());
        QVERIFY2(!weakReferenceMutator.isNull(), qPrintable(component.errorString()));
        weakReferenceMutator->init(v4, weakRef.data(), &mutatorResult);

        v4->memoryManager->allocate<QV4::WeakReferenceSentinel>(weakRef.data(), &sentinelResult);
    }
    gc(engine);

    QVERIFY2(mutatorResult, "We failed to re-assign the weak reference a new value during GC");
    QVERIFY2(sentinelResult, "The weak reference was not cleared properly");
}

struct EventProcessor : public QObject
{
    Q_OBJECT
public:
    Q_INVOKABLE void process()
    {
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();
    }
};

void tst_qqmlecmascript::bindingSuppression()
{
    QQmlEngine engine;
    EventProcessor processor;
    engine.rootContext()->setContextProperty("pendingEvents", &processor);

    QQmlTestMessageHandler messageHandler;

    QQmlComponent c(&engine, testFileUrl("bindingSuppression.qml"));
    QObject *obj = c.create();
    QVERIFY(obj != nullptr);
    delete obj;

    QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
}

void tst_qqmlecmascript::signalEmitted()
{
    {
        // calling destroy on the sibling.
        QQmlEngine engine;
        QQmlComponent c(&engine, testFileUrl("signalEmitted.2.qml"));
        QObject *obj = c.create();
        QVERIFY(obj != nullptr);
        QTRY_VERIFY(obj->property("success").toBool());
        delete obj;
    }

    {
        // allowing gc to clean up the sibling.
        QQmlEngine engine;
        QQmlComponent c(&engine, testFileUrl("signalEmitted.3.qml"));
        QObject *obj = c.create();
        QVERIFY(obj != nullptr);
        gc(engine); // should collect c1.
        QTRY_VERIFY(obj->property("success").toBool());
        delete obj;
    }

    {
        // allowing gc to clean up the sibling after manually destroying target.
        QQmlEngine engine;
        QQmlComponent c(&engine, testFileUrl("signalEmitted.4.qml"));
        QObject *obj = c.create();
        QVERIFY(obj != nullptr);
        gc(engine); // should collect c1.
        QMetaObject::invokeMethod(obj, "destroyC2");
        QTRY_VERIFY(obj->property("success").toBool()); // handles events (incl. delete later).
        delete obj;
    }
}

// QTBUG-25647
void tst_qqmlecmascript::threadSignal()
{
    QQmlEngine engine;
    {
        QQmlComponent c(&engine, testFileUrl("threadSignal.qml"));
        QScopedPointer<QObject> object(c.create());
        QVERIFY(!object.isNull());
        QTRY_VERIFY(object->property("passed").toBool());
    }
    {
        QQmlComponent c(&engine, testFileUrl("threadSignal.2.qml"));
        QScopedPointer<QObject> object(c.create());
        QVERIFY(!object.isNull());
        QMetaObject::invokeMethod(object.data(), "doIt");
        QTRY_VERIFY(object->property("passed").toBool());
    }
}

// ensure that the qqmldata::destroyed() handler doesn't cause problems
void tst_qqmlecmascript::qqmldataDestroyed()
{
    QQmlEngine engine;
    // gc cleans up a qobject, later the qqmldata destroyed handler will run.
    {
        QQmlComponent c(&engine, testFileUrl("qqmldataDestroyed.qml"));
        QObject *object = c.create();
        QVERIFY(object != nullptr);
        // now gc causing the collection of the dynamically constructed object.
        engine.collectGarbage();
        engine.collectGarbage();
        // now process events to allow deletion (calling qqmldata::destroyed())
        QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
        QCoreApplication::processEvents();
        // shouldn't crash.
        delete object;
    }

    // in this case, the object has CPP ownership, and the gc will
    // be triggered during its beginCreate stage.
    {
        QQmlComponent c(&engine, testFileUrl("qqmldataDestroyed.2.qml"));
        QObject *object = c.create();
        QVERIFY(object != nullptr);
        QVERIFY(object->property("testConditionsMet").toBool());
        // the gc() within the handler will have triggered the weak
        // qobject reference callback.  If that incorrectly disposes
        // the handle, when the qqmldata::destroyed() handler is
        // called due to object deletion we will see a crash.
        delete object;
        // shouldn't have crashed.
    }
}

void tst_qqmlecmascript::secondAlias()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("secondAlias.qml"));
    QObject *object = c.create();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("test").toInt(), 200);
    delete object;
}

// An alias to a var property works
void tst_qqmlecmascript::varAlias()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("varAlias.qml"));
    QObject *object = c.create();
    QVERIFY(object != nullptr);
    QCOMPARE(object->property("test").toInt(), 192);
    delete object;
}

// Used to trigger an assert in the lazy meta object creation stage
void tst_qqmlecmascript::overrideDataAssert()
{
    QQmlEngine engine;
    QQmlComponent c(&engine, testFileUrl("overrideDataAssert.qml"));
    QObject *object = c.create();
    QVERIFY(object != nullptr);
    object->metaObject();
    delete object;
}

void tst_qqmlecmascript::fallbackBindings_data()
{
    QTest::addColumn<QString>("source");

    QTest::newRow("Property without fallback") << "fallbackBindings.1.qml";
    QTest::newRow("Property fallback") << "fallbackBindings.2.qml";
    QTest::newRow("SingletonType without fallback") << "fallbackBindings.3.qml";
    QTest::newRow("SingletonType fallback") << "fallbackBindings.4.qml";
    QTest::newRow("Attached without fallback") << "fallbackBindings.5.qml";
    QTest::newRow("Attached fallback") << "fallbackBindings.6.qml";
    QTest::newRow("Subproperty without fallback") << "fallbackBindings.7.qml";
    QTest::newRow("Subproperty fallback") << "fallbackBindings.8.qml";
}

void tst_qqmlecmascript::fallbackBindings()
{
    QFETCH(QString, source);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl(source));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("success").toBool(), true);
}

void tst_qqmlecmascript::propertyOverride()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("propertyOverride.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);

    QCOMPARE(object->property("success").toBool(), true);
}

void tst_qqmlecmascript::sequenceSort_data()
{
    QTest::addColumn<QString>("function");
    QTest::addColumn<bool>("useComparer");

    QTest::newRow("qtbug_25269") << "test_qtbug_25269" << false;

    const char *types[] = { "alphabet", "numbers", "reals", "number_vector", "real_vector" };
    const char *sort[] = { "insertionSort", "quickSort" };

    for (size_t t=0 ; t < sizeof(types)/sizeof(types[0]) ; ++t) {
        for (size_t s=0 ; s < sizeof(sort)/sizeof(sort[0]) ; ++s) {
            for (int c=0 ; c < 2 ; ++c) {
                QString testName = QLatin1String(types[t]) + QLatin1Char('_') + QLatin1String(sort[s]);
                QString fnName = QLatin1String("test_") + testName;
                bool useComparer = c != 0;
                testName += useComparer ? QLatin1String("[custom]") : QLatin1String("[default]");
                QTest::newRow(testName.toLatin1().constData()) << fnName << useComparer;
            }
        }
    }
}

void tst_qqmlecmascript::sequenceSort()
{
    QFETCH(QString, function);
    QFETCH(bool, useComparer);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("sequenceSort.qml"));

    QObject *object = component.create();
    if (object == nullptr)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);

    QVariant q;
    QMetaObject::invokeMethod(object, function.toLatin1().constData(), Q_RETURN_ARG(QVariant, q), Q_ARG(QVariant, useComparer));
    QVERIFY(q.toBool());

    delete object;
}

void tst_qqmlecmascript::dateParse()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("date.qml"));

    QScopedPointer<QObject> object(component.create());
    if (object == nullptr)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);

    QVariant q;
    QMetaObject::invokeMethod(object.get(), "test_is_invalid_jsDateTime", Q_RETURN_ARG(QVariant, q));
    QVERIFY(q.toBool());

    QMetaObject::invokeMethod(object.get(), "test_is_invalid_qtDateTime", Q_RETURN_ARG(QVariant, q));
    QVERIFY(q.toBool());

    QMetaObject::invokeMethod(object.get(), "test_rfc2822_date", Q_RETURN_ARG(QVariant, q));
    QCOMPARE(q.toLongLong(), 1379512851000LL);
}

void tst_qqmlecmascript::utcDate()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("utcdate.qml"));

    QScopedPointer<QObject> object(component.create());
    if (object == nullptr)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);

    QVariant q;
    QVariant val = QString::fromLatin1("2014-07-16T23:30:31");
    QMetaObject::invokeMethod(object.get(), "check_utc", Q_RETURN_ARG(QVariant, q), Q_ARG(QVariant, val));
    QVERIFY(q.toBool());
}

void tst_qqmlecmascript::negativeYear()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("negativeyear.qml"));

    QScopedPointer<QObject> object(component.create());
    if (object == nullptr)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);

    QVariant q;
    QMetaObject::invokeMethod(object.get(), "check_negative_tostring", Q_RETURN_ARG(QVariant, q));

    // Only check for the year. We hope that every language writes the year in arabic numerals and
    // in relation to a specific dude's date of birth. We also hope that no language adds a "-2001"
    // junk string somewhere in the middle.
    QVERIFY(q.toString().indexOf(QStringLiteral("-2001")) != -1);

    QMetaObject::invokeMethod(object.get(), "check_negative_toisostring", Q_RETURN_ARG(QVariant, q));
    QCOMPARE(q.toString().left(16), QStringLiteral("result: -002000-"));
}

void tst_qqmlecmascript::concatenatedStringPropertyAccess()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("concatenatedStringPropertyAccess.qml"));
    QObject *object = component.create();
    QVERIFY(object);
    QVERIFY(object->property("success").toBool());
    delete object;
}

void tst_qqmlecmascript::jsOwnedObjectsDeletedOnEngineDestroy()
{
    QQmlEngine *myEngine = new QQmlEngine;

    MyDeleteObject deleteObject;
    deleteObject.setObjectName("deleteObject");
    QObject * const object1 = new QObject;
    QObject * const object2 = new QObject;
    object1->setObjectName("object1");
    object2->setObjectName("object2");
    deleteObject.setObject1(object1);
    deleteObject.setObject2(object2);

    // Objects returned by function calls get marked as destructible, but objects returned by
    // property getters do not - therefore we explicitly set the object as destructible.
    QQmlEngine::setObjectOwnership(object2, QQmlEngine::JavaScriptOwnership);

    myEngine->rootContext()->setContextProperty("deleteObject", &deleteObject);
    QQmlComponent component(myEngine, testFileUrl("jsOwnedObjectsDeletedOnEngineDestroy.qml"));
    QObject *object = component.create();
    QVERIFY(object);

    // Destroying the engine should delete all JS owned QObjects
    QSignalSpy spy1(object1, SIGNAL(destroyed()));
    QSignalSpy spy2(object2, SIGNAL(destroyed()));
    delete myEngine;
    QCOMPARE(spy1.count(), 1);
    QCOMPARE(spy2.count(), 1);

    deleteObject.deleteNestedObject();
    delete object;
}

void tst_qqmlecmascript::updateCall()
{
    // update is a slot on QQuickItem. Even though it's not
    // documented it can be called from within QML. Make sure
    // we don't crash when calling it.
    QString file("updateCall.qml");
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl(file));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
}

void tst_qqmlecmascript::numberParsing()
{
    QQmlEngine engine;
    for (int i = 1; i < 8; ++i) {
        QString file("numberParsing.%1.qml");
        file = file.arg(i);
        QQmlComponent component(&engine, testFileUrl(file));
        QScopedPointer<QObject> object(component.create());
        QVERIFY(object != nullptr);
    }
    for (int i = 1; i < 3; ++i) {
        QString file("numberParsing_error.%1.qml");
        file = file.arg(i);
        QQmlComponent component(&engine, testFileUrl(file));
        QVERIFY(!component.errors().isEmpty());
    }
}

void tst_qqmlecmascript::stringParsing()
{
    QQmlEngine engine;
    for (int i = 1; i < 7; ++i) {
        QString file("stringParsing_error.%1.qml");
        file = file.arg(i);
        QQmlComponent component(&engine, testFileUrl(file));
        QObject *object = component.create();
        QVERIFY(!object);
    }
}

void tst_qqmlecmascript::push_and_shift()
{
    QJSEngine e;
    const QString program =
            "var array = []; "
            "for (var i = 0; i < 10000; i++) {"
            "    array.push(5); array.unshift(5); array.push(5);"
            "}"
            "array.length;";
    QCOMPARE(e.evaluate(program).toNumber(), double(30000));
}

void tst_qqmlecmascript::qtbug_32801()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_32801.qml"));

    QScopedPointer<QObject> obj(component.create());
    QVERIFY(obj != nullptr);

    // do not crash when a QML signal is connected to a non-void slot
    connect(obj.data(), SIGNAL(testSignal(QString)), obj.data(), SLOT(slotWithReturnValue(QString)));
    QVERIFY(QMetaObject::invokeMethod(obj.data(), "emitTestSignal"));
}

void tst_qqmlecmascript::thisObject()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("thisObject.qml"));
    QObject *object = component.create();
    QVERIFY(object);
    QCOMPARE(qvariant_cast<QObject*>(object->property("subObject"))->property("test").toInt(), 2);
    delete object;
}

void tst_qqmlecmascript::qtbug_33754()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_33754.qml"));

    QScopedPointer<QObject> obj(component.create());
    QVERIFY(obj != nullptr);
}

void tst_qqmlecmascript::qtbug_34493()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_34493.qml"));

    QScopedPointer<QObject> obj(component.create());
    if (component.errors().size())
        qDebug() << component.errors();
    QVERIFY(component.errors().isEmpty());
    QVERIFY(obj != nullptr);
    QVERIFY(QMetaObject::invokeMethod(obj.data(), "doIt"));
    QTRY_VERIFY(obj->property("prop").toString() == QLatin1String("Hello World!"));
}

// Check that a Singleton can be passed from QML to C++
// as its type*, it's parent type* and as QObject*
void tst_qqmlecmascript::singletonFromQMLToCpp()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFile("singletonTest.qml"));
    QScopedPointer<QObject> obj(component.create());
    if (component.errors().size())
        qDebug() << component.errors();
    QVERIFY(component.errors().isEmpty());
    QVERIFY(obj != nullptr);

    QCOMPARE(obj->property("qobjectTest"), QVariant(true));
    QCOMPARE(obj->property("myQmlObjectTest"), QVariant(true));
    QCOMPARE(obj->property("myInheritedQmlObjectTest"), QVariant(true));
}

// Check that a Singleton can be passed from QML to C++
// as its type*, it's parent type* and as QObject*
// and correctly compares to itself
void tst_qqmlecmascript::singletonFromQMLAndBackAndCompare()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFile("singletonTest2.qml"));
    QScopedPointer<QObject> o(component.create());
    if (component.errors().size())
        qDebug() << component.errors();
    QVERIFY(component.errors().isEmpty());
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("myInheritedQmlObjectTest1"), QVariant(true));
    QCOMPARE(o->property("myInheritedQmlObjectTest2"), QVariant(true));
    QCOMPARE(o->property("myInheritedQmlObjectTest3"), QVariant(true));

    QCOMPARE(o->property("myQmlObjectTest1"), QVariant(true));
    QCOMPARE(o->property("myQmlObjectTest2"), QVariant(true));
    QCOMPARE(o->property("myQmlObjectTest3"), QVariant(true));

    QCOMPARE(o->property("qobjectTest1"), QVariant(true));
    QCOMPARE(o->property("qobjectTest2"), QVariant(true));
    QCOMPARE(o->property("qobjectTest3"), QVariant(true));

    QCOMPARE(o->property("singletonEqualToItself"), QVariant(true));
}

void tst_qqmlecmascript::setPropertyOnInvalid()
{
    QQmlEngine engine;
    {
        QQmlComponent component(&engine, testFileUrl("setPropertyOnNull.qml"));
        QString warning = component.url().toString() + ":4: TypeError: Value is null and could not be converted to an object";
        QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
        QObject *object = component.create();
        QVERIFY(object);
        delete object;
    }

    {
        QQmlComponent component(&engine, testFileUrl("setPropertyOnUndefined.qml"));
        QString warning = component.url().toString() + ":4: TypeError: Value is undefined and could not be converted to an object";
        QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
        QObject *object = component.create();
        QVERIFY(object);
        delete object;
    }
}

void tst_qqmlecmascript::miscTypeTest()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("misctypetest.qml"));

    QObject *object = component.create();
    if (object == nullptr)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);

    QVariant q;
    QMetaObject::invokeMethod(object, "test_invalid_url_equal", Q_RETURN_ARG(QVariant, q));
    QVERIFY(q.toBool());
    QMetaObject::invokeMethod(object, "test_invalid_url_strictequal", Q_RETURN_ARG(QVariant, q));
    QVERIFY(q.toBool());
    QMetaObject::invokeMethod(object, "test_valid_url_equal", Q_RETURN_ARG(QVariant, q));
    QVERIFY(q.toBool());
    QMetaObject::invokeMethod(object, "test_valid_url_strictequal", Q_RETURN_ARG(QVariant, q));
    QVERIFY(q.toBool());

    delete object;
}

void tst_qqmlecmascript::stackLimits()
{
    QJSEngine engine;
    engine.evaluate(QStringLiteral("function foo() {foo();} try {foo()} catch(e) { }"));
}

void tst_qqmlecmascript::idsAsLValues()
{
    QQmlEngine engine;
    QString err = QString(QLatin1String("%1:5: Error: left-hand side of assignment operator is not an lvalue")).arg(testFileUrl("idAsLValue.qml").toString());
    QQmlComponent component(&engine, testFileUrl("idAsLValue.qml"));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(err));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!qobject_cast<MyQmlObject*>(object.get()));
}

void tst_qqmlecmascript::qtbug_34792()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug34792.qml"));

    QObject *object = component.create();
    if (object == nullptr)
        qDebug() << component.errorString();
    QVERIFY(object != nullptr);
    delete object;
}

void tst_qqmlecmascript::noCaptureWhenWritingProperty()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("noCaptureWhenWritingProperty.qml"));
    QScopedPointer<QObject> obj(component.create());
    QVERIFY(!obj.isNull());
    QCOMPARE(obj->property("somePropertyEvaluated").toBool(), false);
}

void tst_qqmlecmascript::singletonWithEnum()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("singletontype/singletonWithEnum.qml"));
    QScopedPointer<QObject> obj(component.create());
    if (obj.isNull())
        qDebug() << component.errors().first().toString();
    QVERIFY(!obj.isNull());
    QVariant prop = obj->property("testValue");
    QCOMPARE(prop.type(), QVariant::Int);
    QCOMPARE(prop.toInt(), int(SingletonWithEnum::TestValue));

    {
        QQmlExpression expr(qmlContext(obj.data()), obj.data(), "SingletonWithEnum.TestValue_MinusOne");
        bool valueUndefined = false;
        QVariant result = expr.evaluate(&valueUndefined);
        QVERIFY2(!expr.hasError(), qPrintable(expr.error().toString()));
        QVERIFY(!valueUndefined);
        QCOMPARE(result.toInt(), -1);
    }
}

void tst_qqmlecmascript::lazyBindingEvaluation()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("lazyBindingEvaluation.qml"));
    QScopedPointer<QObject> obj(component.create());
    if (obj.isNull())
       qDebug() << component.errors().first().toString();
    QVERIFY(!obj.isNull());
    QVariant prop = obj->property("arrayLength");
    QCOMPARE(prop.type(), QVariant::Int);
    QCOMPARE(prop.toInt(), 2);
}

void tst_qqmlecmascript::varPropertyAccessOnObjectWithInvalidContext()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("varPropertyAccessOnObjectWithInvalidContext.qml"));
    QScopedPointer<QObject> obj(component.create());
    if (obj.isNull())
       qDebug() << component.errors().first().toString();
    QVERIFY(!obj.isNull());
    QVERIFY(obj->property("success").toBool());
}

void tst_qqmlecmascript::importedScriptsAccessOnObjectWithInvalidContext()
{
    QQmlEngine engine;
    const QUrl url = testFileUrl("importedScriptsAccessOnObjectWithInvalidContext.qml");
    QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + ":29: TypeError: Cannot read property 'Foo' of null"));
    QQmlComponent component(&engine, url);
    QScopedPointer<QObject> obj(component.create());
    if (obj.isNull())
       qDebug() << component.errors().first().toString();
    QVERIFY(!obj.isNull());
    QTRY_VERIFY(obj->property("success").toBool());
}

void tst_qqmlecmascript::importedScriptsWithoutQmlMode()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("importScriptsWithoutQmlMode.qml"));
    QScopedPointer<QObject> obj(component.create());
    if (obj.isNull())
       qDebug() << component.errors().first().toString();
    QVERIFY(!obj.isNull());
    QTRY_VERIFY(obj->property("success").toBool());
}

void tst_qqmlecmascript::contextObjectOnLazyBindings()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("contextObjectOnLazyBindings.qml"));
    QScopedPointer<QObject> obj(component.create());
    if (obj.isNull())
        qDebug() << component.errors().first().toString();
    QVERIFY(!obj.isNull());
    QObject *subObject = qvariant_cast<QObject*>(obj->property("subObject"));
    QVERIFY(subObject);
    QCOMPARE(subObject->property("testValue").toInt(), int(42));
}

void tst_qqmlecmascript::garbageCollectionDuringCreation()
{
    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.setData("import Qt.test 1.0\n"
                      "QObjectContainerWithGCOnAppend {\n"
                      "    objectName: \"root\"\n"
                      "    FloatingQObject {\n"
                      "        objectName: \"parentLessChild\"\n"
                      "        property var blah;\n" // Ensure we have JS wrapper
                      "    }\n"
                      "}\n",
                      QUrl());

    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());

    QObjectContainer *container = qobject_cast<QObjectContainer*>(object.data());
    QCOMPARE(container->dataChildren.count(), 1);

    QObject *child = container->dataChildren.first();
    QQmlData *ddata = QQmlData::get(child);
    QVERIFY(!ddata->jsWrapper.isNullOrUndefined());

    gc(engine);
    QCOMPARE(container->dataChildren.count(), 0);
}

void tst_qqmlecmascript::qtbug_39520()
{
    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0\n"
                      "Item {\n"
                      "    property string s\n"
                      "    Component.onCompleted: test()\n"
                      "    function test() {\n"
                      "    var count = 1 * 1000 * 1000\n"
                      "    var t = ''\n"
                      "    for (var i = 0; i < count; ++i)\n"
                      "        t += 'testtest ' + i + '\n'\n"
                      "    s = t\n"
                      "    }\n"
                      "}\n",
                      QUrl());

    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());

    QString s = object->property("s").toString();
    QCOMPARE(s.count('\n'), 1 * 1000 * 1000);
}

class ContainedObject1 : public QObject
{
    Q_OBJECT
};

class ContainedObject2 : public QObject
{
    Q_OBJECT
};

class ObjectContainer : public QObject
{
    Q_OBJECT
    Q_PROPERTY(ContainedObject1 *object1 READ object1 WRITE setObject1)
    Q_PROPERTY(ContainedObject2 *object2 READ object2 WRITE setObject2)
public:
    explicit ObjectContainer(QObject *parent = nullptr) :
        QObject(parent),
        mGetterCalled(false),
        mSetterCalled(false)
    {
    }

    ContainedObject1 *object1()
    {
        mGetterCalled = true;
        return nullptr;
    }

    void setObject1(ContainedObject1 *)
    {
        mSetterCalled = true;
    }

    ContainedObject2 *object2()
    {
        mGetterCalled = true;
        return nullptr;
    }

    void setObject2(ContainedObject2 *)
    {
        mSetterCalled = true;
    }

public:
    bool mGetterCalled;
    bool mSetterCalled;
};

void tst_qqmlecmascript::readUnregisteredQObjectProperty()
{
    qmlRegisterType<ObjectContainer>("Test", 1, 0, "ObjectContainer");
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("accessUnregisteredQObjectProperty.qml"));
    QScopedPointer<QObject> root(component.create());
    QVERIFY(root);

    QMetaObject::invokeMethod(root.get(), "readProperty");
    QCOMPARE(root->property("container").value<ObjectContainer*>()->mGetterCalled, true);
}

void tst_qqmlecmascript::writeUnregisteredQObjectProperty()
{
    qmlRegisterType<ObjectContainer>("Test", 1, 0, "ObjectContainer");
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("accessUnregisteredQObjectProperty.qml"));
    QScopedPointer<QObject> root(component.create());
    QVERIFY(root);

    QMetaObject::invokeMethod(root.get(), "writeProperty");
    QCOMPARE(root->property("container").value<ObjectContainer*>()->mSetterCalled, true);
}

void tst_qqmlecmascript::switchExpression()
{
    // verify that we evaluate the expression inside switch() exactly once
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1(
            "var num = 0\n"
            "var x = 0\n"
            "function f() { ++num; return (Math.random() > 0.5) ? 0 : 1; }\n"
            "for (var i = 0; i < 1000; ++i) {\n"
            "   switch (f()) {\n"
            "   case 0:\n"
            "   case 1:\n"
            "       break;\n"
            "   default:\n"
            "       ++x;\n"
            "   }\n"
            "}\n"
            "(x == 0 && num == 1000) ? true : false\n"
                        ));
    QVERIFY(!v.isError());
    QCOMPARE(v.toBool(), true);
}

void tst_qqmlecmascript::qtbug_46022()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_46022.qml"));

    QScopedPointer<QObject> obj(component.create());
    QVERIFY(obj != nullptr);
    QCOMPARE(obj->property("test1").toBool(), true);
    QCOMPARE(obj->property("test2").toBool(), true);
}

void tst_qqmlecmascript::qtbug_52340()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_52340.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());
    QVariant returnValue;
    QVERIFY(QMetaObject::invokeMethod(object.data(), "testCall", Q_RETURN_ARG(QVariant, returnValue)));
    QVERIFY(returnValue.isValid());
    QVERIFY(returnValue.toBool());
}

void tst_qqmlecmascript::qtbug_54589()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_54589.qml"));

    QScopedPointer<QObject> obj(component.create());
    QVERIFY(obj != nullptr);
    QCOMPARE(obj->property("result").toBool(), true);
}

void tst_qqmlecmascript::qtbug_54687()
{
    QJSEngine e;
    // it's simple: this shouldn't crash.
    e.evaluate("12\n----12");
}

void tst_qqmlecmascript::stringify_qtbug_50592()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("stringify_qtbug_50592.qml"));

    QScopedPointer<QObject> obj(component.create());
    QVERIFY(obj != nullptr);
    QCOMPARE(obj->property("source").toString(),
             QString::fromLatin1("\"http://example.org/some_nonexistant_image.png\""));
}

// Tests for the JS-only instanceof. Tests for the QML extensions for
// instanceof belong in tst_qqmllanguage!
void tst_qqmlecmascript::instanceof_data()
{
    QTest::addColumn<QString>("setupCode");
    QTest::addColumn<QVariant>("expectedValue");

    // so the way this works is that the name of the test tag defines the test
    // to run. the code in setupCode defines code run before the actual test
    // (e.g. to create vars).
    //
    // the expectedValue is either a boolean true or false for whether the two
    // operands are indeed an instanceof each other, or a string for the
    // expected error message.
    QTest::newRow("String instanceof String")
            << ""
            << QVariant(false);
    QTest::newRow("s instanceof String")
            << "var s = \"hello\""
            << QVariant(false);
    QTest::newRow("objectString instanceof String")
            << "var objectString = new String(\"hello\")"
            << QVariant(true);
    QTest::newRow("o instanceof Object")
            << "var o = new Object()"
            << QVariant(true);
    QTest::newRow("o instanceof String")
            << "var o = new Object()"
            << QVariant(false);
    QTest::newRow("true instanceof true")
            << ""
            << QVariant("TypeError: Type error");
    QTest::newRow("1 instanceof Math")
            << ""
            << QVariant("TypeError: Type error");
    QTest::newRow("date instanceof Date")
            << "var date = new Date"
            << QVariant(true);
    QTest::newRow("date instanceof Object")
            << "var date = new Date"
            << QVariant(true);
    QTest::newRow("date instanceof String")
            << "var date = new Date"
            << QVariant(false);
}

void tst_qqmlecmascript::instanceof()
{
    QFETCH(QString, setupCode);
    QFETCH(QVariant, expectedValue);

    QJSEngine engine;
    QJSValue ret = engine.evaluate(setupCode + ";\n" + QTest::currentDataTag());

    if (expectedValue.type() == QVariant::Bool) {
        bool returnValue = ret.toBool();
        QVERIFY2(!ret.isError(), qPrintable(ret.toString()));
        QCOMPARE(returnValue, expectedValue.toBool());
    } else {
        QVERIFY2(ret.isError(), qPrintable(ret.toString()));
        QCOMPARE(ret.toString(), expectedValue.toString());
    }
}

void tst_qqmlecmascript::constkw_data()
{
    QTest::addColumn<QString>("sourceCode");
    QTest::addColumn<bool>("exceptionExpected");
    QTest::addColumn<QVariant>("expectedValue");

    QTest::newRow("simpleconst")
        << "const v = 5\n"
           "v\n"
        << false
        << QVariant(5);
    QTest::newRow("twoconst")
        << "const v = 5, i = 10\n"
           "v + i\n"
        << false
        << QVariant(15);
    QTest::newRow("constandvar")
        << "const v = 5\n"
           "var i = 20\n"
           "v + i\n"
        << false
        << QVariant(25);
    QTest::newRow("const-multiple-scopes-same-var")
        << "const v = 3\n"
           "function f() { const v = 1; return v; }\n"
           "v + f()\n"
        << false
        << QVariant(4);

    // error cases
    QTest::newRow("const-no-initializer")
        << "const v\n"
        << true
        << QVariant("SyntaxError: Missing initializer in const declaration");
    QTest::newRow("const-no-initializer-comma")
        << "const v = 1, i\n"
        << true
        << QVariant("SyntaxError: Missing initializer in const declaration");
    QTest::newRow("const-no-duplicate")
        << "const v = 1, v = 2\n"
        << true
        << QVariant("SyntaxError: Identifier v has already been declared");
    QTest::newRow("const-no-duplicate-2")
        << "const v = 1\n"
           "const v = 2\n"
        << true
        << QVariant("SyntaxError: Identifier v has already been declared");
    QTest::newRow("const-no-duplicate-var")
        << "const v = 1\n"
           "var v = 1\n"
        << true
        << QVariant("SyntaxError: Identifier v has already been declared");
    QTest::newRow("var-no-duplicate-const")
        << "var v = 1\n"
           "const v = 1\n"
        << true
        << QVariant("SyntaxError: Identifier v has already been declared");
    QTest::newRow("const-no-duplicate-let")
        << "const v = 1\n"
           "let v = 1\n"
        << true
        << QVariant("SyntaxError: Identifier v has already been declared");
    QTest::newRow("let-no-duplicate-const")
        << "let v = 1\n"
           "const v = 1\n"
        << true
        << QVariant("SyntaxError: Identifier v has already been declared");
}

void tst_qqmlecmascript::constkw()
{
    QFETCH(QString, sourceCode);
    QFETCH(bool, exceptionExpected);
    QFETCH(QVariant, expectedValue);

    QJSEngine engine;
    QJSValue ret = engine.evaluate(sourceCode);

    if (!exceptionExpected) {
        QVERIFY2(!ret.isError(), qPrintable(ret.toString()));
        QCOMPARE(ret.toVariant(), expectedValue);
    } else {
        QVERIFY2(ret.isError(), qPrintable(ret.toString()));
        QCOMPARE(ret.toString(), expectedValue.toString());
    }
}

// Redefine a property found on the global object. It shouldn't throw.
void tst_qqmlecmascript::redefineGlobalProp()
{
    {
        QJSEngine engine;
        QJSValue ret = engine.evaluate("\"use strict\"\n var toString = 1;");
        QVERIFY2(!ret.isError(), qPrintable(ret.toString()));
    }
    {
        QJSEngine engine;
        QJSValue ret = engine.evaluate("var toString = 1;");
        QVERIFY2(!ret.isError(), qPrintable(ret.toString()));
    }
}

void tst_qqmlecmascript::freeze_empty_object()
{
    // this shouldn't crash
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1(
            "var obj = {};\n"
            "Object.freeze(obj);\n"
    ));
    QVERIFY(!v.isError());
    QCOMPARE(v.toBool(), true);
}

void tst_qqmlecmascript::singleBlockLoops()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug_59012.qml"));

    QScopedPointer<QObject> obj(component.create());
    QVERIFY(obj != nullptr);
    QVERIFY(!component.isError());
}

// 'counter' was incorrectly resolved as a type rather than a variable.
// This fix ensures it looks up the right thing.
void tst_qqmlecmascript::qtbug_60547()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("qtbug60547/main.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY2(!object.isNull(), qPrintable(component.errorString()));
    QCOMPARE(object->property("counter"), QVariant(int(1)));
}

void tst_qqmlecmascript::anotherNaN()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("nans.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY2(!object.isNull(), qPrintable(component.errorString()));
    object->setProperty("prop", std::numeric_limits<double>::quiet_NaN()); // don't crash

    std::uint64_t anotherNaN = 0xFFFFFF01000000F7ul;
    double d;
    std::memcpy(&d, &anotherNaN, sizeof(d));
    QVERIFY(std::isnan(d));
    object->setProperty("prop", d);  // don't crash
}

void tst_qqmlecmascript::delayLoadingArgs()
{
    QJSEngine engine;
    QJSValue ret = engine.evaluate("(function(x){return x + (x+=2)})(20)");
    QCOMPARE(ret.toInt(), 42); // esp. not 44.
}

void tst_qqmlecmascript::manyArguments()
{
    const char *testCase =
            "function x() { var sum; for (var i = 0; i < arguments.length; ++i) sum += arguments[i][0]; }"
            "x([0],[1],[2],[3],[4],[5],[6],[7],[8],[9], [0],[1],[2],[3],[4],[5],[6],[7],[8],[9], [0],[1],[2],[3],[4],[5],[6],[7],[8],[9])";

    QJSEngine engine;
    engine.evaluate(testCase);
}

void tst_qqmlecmascript::forInIterator()
{
    auto testCase =
            "(function(){\n"
            "var x = 'yoyo'\n"
            "var i\n"
            "for (i in x) {\n"
            "}\n"
            "return i\n"
            "})()";
    QJSEngine engine;
    QJSValue ret = engine.evaluate(testCase);
    QVERIFY(ret.isString());
    QCOMPARE(ret.toString(), QStringLiteral("3"));
}

void tst_qqmlecmascript::localForInIterator()
{
    auto testCase =
            "(function(){\n"
            "var x = 'yoyo'\n"
            "for (var i in x) {\n"
            "}\n"
            "return i\n"
            "})()";
    QJSEngine engine;
    QJSValue ret = engine.evaluate(testCase);
    QVERIFY(ret.isString());
    QCOMPARE(ret.toString(), QStringLiteral("3"));
}

void tst_qqmlecmascript::shadowedFunctionName()
{
    // verify that arguments shadow the function name
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1(
            "function f(f) { return f; }\n"
            "f(true)\n"
                        ));
    QVERIFY(!v.isError());
    QVERIFY(v.isBool());
    QCOMPARE(v.toBool(), true);
}

void tst_qqmlecmascript::callPropertyOnUndefined()
{
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1(
            "function f() {\n"
            "    var base;\n"
            "    base.push(1);"
            "}\n"
    ));
    QVERIFY(!v.isError()); // well, more importantly: this shouldn't fail on an assert.
}

void tst_qqmlecmascript::jumpStrictNotEqualUndefined()
{
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1(
        "var ok = 0\n"
        "var foo = 0\n"
        "if (foo !== void 1)\n"
        "    ++ok;\n"
        "else\n"
        "    --ok;\n"
        "if (foo === void 1)\n"
        "    --ok;\n"
        "else\n"
        "    ++ok;\n"
        "ok\n"
    ));
    QVERIFY(!v.isError());
    QCOMPARE(v.toInt(), 2);
}

void tst_qqmlecmascript::removeBindingsWithNoDependencies()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("removeBindingsWithNoDependencies.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());
    QVariant rect = object->property("placement");
    QCOMPARE(rect.toRectF(), QRectF(0, 0, 100, 100));
    const QMetaObject *metaObject = object->metaObject();

    {
        const QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("placement"));
        QVERIFY(prop.isValid());
        QVERIFY(!QQmlPropertyPrivate::binding(object.data(), QQmlPropertyIndex(prop.propertyIndex())));
    }

    {
        const QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("partialPlacement"));
        QVERIFY(prop.isValid());
        QQmlAbstractBinding *vtProxyBinding = QQmlPropertyPrivate::binding(object.data(), QQmlPropertyIndex(prop.propertyIndex()));
        QVERIFY(vtProxyBinding);
        QVERIFY(vtProxyBinding->isValueTypeProxy());

        QQmlValueTypeProxyBinding *proxy = static_cast<QQmlValueTypeProxyBinding*>(vtProxyBinding);
        QVERIFY(!proxy->subBindings());
    }

}

void tst_qqmlecmascript::preserveBindingWithUnresolvedNames()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("preserveBindingWithUnresolvedNames.qml"));
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());
    QCOMPARE(object->property("testTypeOf").toString(), QString("undefined"));
    QObject obj;
    engine.rootContext()->setContextProperty("contextProp", &obj);
    QCOMPARE(object->property("testTypeOf").toString(), QString("object"));
}

void tst_qqmlecmascript::temporaryDeadZone()
{
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1("a; let a;"));
    QVERIFY(v.isError());
    v = engine.evaluate(QString::fromLatin1("a.name; let a;"));
    QVERIFY(v.isError());
    v = engine.evaluate(QString::fromLatin1("var a = {}; a[b]; let b;"));
    QVERIFY(v.isError());
    v = engine.evaluate(QString::fromLatin1("class C { constructor() { super[x]; let x; } }; new C()"));
    QVERIFY(v.isError());
}

void tst_qqmlecmascript::importLexicalVariables_data()
{
    QTest::addColumn<QUrl>("testFile");
    QTest::addColumn<QString>("expected");

    QTest::newRow("script")
        << testFileUrl("importLexicalVariables_script.qml")
        << QStringLiteral("000 100 210");
    QTest::newRow("pragmaLibrary")
        << testFileUrl("importLexicalVariables_pragmaLibrary.qml")
        << QStringLiteral("000 100 210");
    QTest::newRow("module")
        << testFileUrl("importLexicalVariables_module.qml")
        << QStringLiteral("000 000 110");
}

void tst_qqmlecmascript::importLexicalVariables()
{
    QFETCH(QUrl, testFile);
    QFETCH(QString, expected);

    QQmlEngine engine;
    QQmlComponent component(&engine, testFile);
    QScopedPointer<QObject> object(component.create());
    QVERIFY(object != nullptr);
    QVERIFY(!component.isError());

    QVariant result;
    QMetaObject::invokeMethod(object.data(), "runTest", Qt::DirectConnection, Q_RETURN_ARG(QVariant, result));
    QCOMPARE(result, QVariant(expected));
}

void tst_qqmlecmascript::hugeObject()
{
    // mainly check that this doesn't crash
    QJSEngine engine;
    QJSValue v = engine.evaluate(QString::fromLatin1(
        "var known = {}, prefix = 'x'\n"
        "for (var i = 0; i < 150000; i++) known[prefix + i] = true;"
    ));
    QVERIFY(!v.isError());
}

void tst_qqmlecmascript::templateStringTerminator()
{
    QJSEngine engine;
    const QJSValue value = engine.evaluate("let a = 123; let b = `x${a}\ny^`; b;");
    QVERIFY(!value.isError());
    QCOMPARE(value.toString(), QLatin1String("x123\ny^"));
}

void tst_qqmlecmascript::arrayAndException()
{
    QJSEngine engine;
    const QJSValue value = engine.evaluate("[...[],[,,$]]");
    // Should not crash
    QVERIFY(value.isError());
}

void tst_qqmlecmascript::numberToStringWithRadix()
{
    QJSEngine engine;
    {
        const QJSValue value = engine.evaluate(".5.toString(5)");
        QVERIFY(!value.isError());
        QVERIFY(value.toString().startsWith("0.2222222222"));
    }
    {
        const QJSValue value = engine.evaluate(".05.toString(5)");
        QVERIFY(!value.isError());
        QVERIFY(value.toString().startsWith("0.01111111111"));
    }
}

void tst_qqmlecmascript::tailCallWithArguments()
{
    QJSEngine engine;
    const QJSValue value = engine.evaluate(
            "'use strict';\n"
            "[[1, 2]].map(function (a) {\n"
            "    return (function() { return Math.min.apply(this, arguments); })(a[0], a[1]);\n"
            "})[0];");
    QVERIFY(!value.isError());
    QCOMPARE(value.toInt(), 1);
}

void tst_qqmlecmascript::deleteSparseInIteration()
{
    QJSEngine engine;
    const QJSValue value = engine.evaluate(
            "(function() {\n"
            "    var obj = { 1: null, 2: null, 4096: null };\n"
            "    var iterated = [];\n"
            "    for (var t in obj) {\n"
            "        if (t == 2)\n"
            "            delete obj[t];\n"
            "        iterated.push(t);\n"
            "    }\n"
            "    return iterated;"
            "})()");
    QVERIFY(value.isArray());
    QCOMPARE(value.property("length").toInt(), 3);
    QCOMPARE(value.property("0").toInt(), 1);
    QCOMPARE(value.property("1").toInt(), 2);
    QCOMPARE(value.property("2").toInt(), 4096);
}

void tst_qqmlecmascript::saveAccumulatorBeforeToInt32()
{
    QJSEngine engine;

    // Infinite recursion produces a range error, but should not crash.
    // Also, any GC runs in between should not trash the temporary results of "a+a".
    const QJSValue value = engine.evaluate("function a(){a(a&a+a)}a()");
    QVERIFY(value.isError());
    QCOMPARE(value.toString(), QLatin1String("RangeError: Maximum call stack size exceeded."));
}

void tst_qqmlecmascript::intMinDividedByMinusOne()
{
    QQmlEngine engine;
    QQmlComponent component(&engine);
    component.setData(QByteArray("import QtQml 2.2\n"
                                 "QtObject {\n"
                                 "   property int intMin: -2147483648\n"
                                 "   property int minusOne: -1\n"
                                 "   property double doesNotFitInInt: intMin / minusOne\n"
                                 "}"), QUrl());
    QVERIFY(component.isReady());
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());
    QCOMPARE(object->property("doesNotFitInInt").toUInt(), 2147483648u);
}

void tst_qqmlecmascript::undefinedPropertiesInObjectWrapper()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFile("undefinedPropertiesInObjectWrapper.qml"));
    QVERIFY(component.isReady());
    QScopedPointer<QObject> object(component.create());
    QVERIFY(!object.isNull());
}

void tst_qqmlecmascript::hugeRegexpQuantifiers()
{
    QJSEngine engine;
    QJSValue value = engine.evaluate("/({3072140529})?{3072140529}/");

    // It's a regular expression, but it won't match anything.
    // The RegExp compiler also shouldn't crash.
    QVERIFY(value.isRegExp());
}

struct CppSingleton1 : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int testVar MEMBER testVar CONSTANT)
public:
    const int testVar = 0;
};

struct CppSingleton2 : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int testVar MEMBER testVar CONSTANT)
public:
    const int testVar = 1;
};

void tst_qqmlecmascript::singletonTypeWrapperLookup()
{
    QQmlEngine engine;

    auto singletonTypeId1 = qmlRegisterSingletonType<CppSingleton1>("Test.Singletons", 1, 0, "CppSingleton1",
                                                                    [](QQmlEngine *, QJSEngine *) -> QObject * {
            return new CppSingleton1;
    });

    auto singletonTypeId2 = qmlRegisterSingletonType<CppSingleton2>("Test.Singletons", 1, 0, "CppSingleton2",
                                                                    [](QQmlEngine *, QJSEngine *) -> QObject * {
            return new CppSingleton2;
    });

    auto cleanup = qScopeGuard([&]() {
       QQmlMetaType::unregisterType(singletonTypeId1);
       QQmlMetaType::unregisterType(singletonTypeId2);
    });

    QQmlComponent component(&engine, testFileUrl("SingletonLookupTest.qml"));
    QScopedPointer<QObject> test(component.create());
    QVERIFY2(!test.isNull(), qPrintable(component.errorString()));

    auto singleton1 = engine.singletonInstance<CppSingleton1*>(singletonTypeId1);
    QVERIFY(singleton1);

    auto singleton2 = engine.singletonInstance<CppSingleton2*>(singletonTypeId2);
    QVERIFY(singleton2);

    QCOMPARE(test->property("firstLookup").toInt(), singleton1->testVar);
    QCOMPARE(test->property("secondLookup").toInt(), singleton2->testVar);
}

void tst_qqmlecmascript::getThisObject()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("getThis.qml"));
    QVERIFY(component.isReady());
    QScopedPointer<QObject> test(component.create());
    QVERIFY(!test.isNull());

    QTRY_COMPARE(qvariant_cast<QObject *>(test->property("self")), test.data());
}

// QTBUG-77954
void tst_qqmlecmascript::semicolonAfterProperty()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("semicolonAfterProperty.qml"));
    QVERIFY(component.isReady());
    QScopedPointer<QObject> test(component.create());
    QVERIFY(!test.isNull());
}

void tst_qqmlecmascript::hugeStack()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("hugeStack.qml"));
    QVERIFY(component.isReady());
    QScopedPointer<QObject> test(component.create());
    QVERIFY(!test.isNull());

    QVariant huge = test->property("longList");
    QCOMPARE(qvariant_cast<QJSValue>(huge).property(QLatin1String("length")).toInt(), 33059);
}

void tst_qqmlecmascript::gcCrashRegressionTest()
{
    const QString qmljs = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmljs";
    if (!QFile::exists(qmljs)) {
        QSKIP("Tets requires qmljs");
    }
    QProcess process;

    QTemporaryFile infile;
    QVERIFY(infile.open());
    infile.write(R"js(
        function i_want_to_break_free() {
            var n = 400;
            var m = 10;
            var regex = new RegExp("(ab)".repeat(n), "g"); // g flag to trigger the vulnerable path
            var part = "ab".repeat(n); // matches have to be at least size 2 to prevent interning
            var s = (part + "|").repeat(m);
            var cnt = 0;
            var ary = [];
            s.replace(regex, function() {
                for (var i = 1; i < arguments.length-2; ++i) {
                    if (typeof arguments[i] !== 'string') {
                        i_am_free = arguments[i];
                        throw "success";
                    }
                    ary[cnt++] = arguments[i];  // root everything to force GC
                }
                return "x";
            });
        }
        try { i_want_to_break_free(); } catch (e) {console.log("hi") }
        console.log(typeof(i_am_free));  // will print "object"
    )js");
    infile.close();

    QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
    environment.insert("QV4_GC_MAX_STACK_SIZE", "32768");

    process.setProcessEnvironment(environment);
    process.start(qmljs, QStringList({infile.fileName()}));
    QVERIFY(process.waitForStarted());
    const qint64 pid = process.processId();
    QVERIFY(pid != 0);
    QVERIFY(process.waitForFinished());
    QCOMPARE(process.exitCode(), 0);
}

void tst_qqmlecmascript::variantConversionMethod()
{
    QQmlEngine qmlengine;

    VariantConvertObject obj;
    qmlengine.rootContext()->setContextProperty("variantObject", &obj);

    QQmlComponent component(&qmlengine, testFileUrl("variantConvert.qml"));
    QScopedPointer<QObject> o(component.create());
    QVERIFY(o != nullptr);
    QCOMPARE(obj.funcCalled, QLatin1String("QModelIndex"));
}

void tst_qqmlecmascript::proxyIteration()
{
    QQmlEngine engine;
    QQmlComponent component(&engine, testFileUrl("proxyIteration.qml"));
    QScopedPointer<QObject> root(component.create());
    QVERIFY2(root != nullptr, qPrintable(component.errorString()));
    QCOMPARE(root->property("sum").toInt(), 6);
}

void tst_qqmlecmascript::proxyHandlerTraps()
{
    const QString expression = QStringLiteral(R"SNIPPET(
        (function(){
            const target = {
                prop: 47
            };
            const handler = {
                getOwnPropertyDescriptor(target, prop) {
                    return { configurable: true, enumerable: true, value: 47 };
                }
            };
            const proxy = new Proxy(target, handler);

            // QTBUG-88786
            if (!proxy.propertyIsEnumerable("prop"))
                throw Error("FAIL: propertyisEnumerable");
            if (!proxy.hasOwnProperty("prop"))
                throw Error("FAIL: hasOwnProperty");

            return "SUCCESS";
        })()
    )SNIPPET");

    QJSEngine engine;
    QJSValue value = engine.evaluate(expression);
    QVERIFY(value.isString() && value.toString() == QStringLiteral("SUCCESS"));
}

QTEST_MAIN(tst_qqmlecmascript)

#include "tst_qqmlecmascript.moc"
