diff options
Diffstat (limited to 'tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp')
-rw-r--r-- | tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 6062 |
1 files changed, 6062 insertions, 0 deletions
diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp new file mode 100644 index 0000000000..af219c8826 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -0,0 +1,6062 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $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 <QtQml/private/qqmlguard_p.h> +#include <QtCore/qdir.h> +#include <QtCore/qnumeric.h> +#include <private/qqmlengine_p.h> +#include <private/qqmlvmemetaobject_p.h> +#include <private/qv4compiler_p.h> +#include "testtypes.h" +#include "testhttpserver.h" +#include "../../shared/util.h" + +/* +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 assignBasicTypes(); + void idShortcutInvalidates(); + void boolPropertiesEvaluateAsBool(); + void methods(); + void signalAssignment(); + void bindingLoop(); + void basicExpressions(); + void basicExpressions_data(); + void arrayExpressions(); + void contextPropertiesTriggerReeval(); + void objectPropertiesTriggerReeval(); + void deferredProperties(); + void deferredPropertiesErrors(); + 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 dynamicCreation_data(); + void dynamicCreation(); + void dynamicDestruction(); + void objectToString(); + void objectHasOwnProperty(); + void selfDeletingBinding(); + void extendedObjectPropertyLookup(); + void extendedObjectPropertyLookup2(); + 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 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 moduleApi_data(); + void moduleApi(); + void importScripts_data(); + void importScripts(); + void scarceResources(); + void scarceResources_data(); + void scarceResources_other(); + void propertyChangeSlots(); + void propertyVar_data(); + void propertyVar(); + 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 qtbug_22464(); + void qtbug_21580(); + + void bug1(); + void bug2(); + void dynamicCreationCrash(); + void dynamicCreationOwnership(); + void regExpBug(); + void nullObjectBinding(); + void deletedEngine(); + void libraryScriptAssert(); + void variantsAssignedUndefined(); + void qtbug_9792(); + void qtcreatorbug_1289(); + void noSpuriousWarningsAtShutdown(); + void canAssignNullToQObject(); + void functionAssignment_fromBinding(); + void functionAssignment_fromJS(); + void functionAssignment_fromJS_data(); + void functionAssignmentfromJS_invalid(); + void eval(); + void function(); + void functionException(); + void qtbug_10696(); + void qtbug_11606(); + void qtbug_11600(); + void qtbug_21864(); + void qobjectConnectionListExceptionHandling(); + void nonscriptable(); + void deleteLater(); + void in(); + void typeOf(); + 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 signalHandlers(); + void doubleEvaluate(); + void forInLoop(); + void nonNotifyable(); + void deleteWhileBindingRunning(); + void callQtInvokables(); + void invokableObjectArg(); + void invokableObjectRet(); + void qtbug_20344(); + void qtbug_22679(); + void qtbug_22843_data(); + void qtbug_22843(); + void rewriteMultiLineStrings(); + void revisionErrors(); + void revision(); + void invokableWithQObjectDerived(); + + void automaticSemicolon(); + void unaryExpression(); + void switchStatement(); + void withStatement(); + void tryStatement(); + +private: + static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); + QQmlEngine engine; +}; + +void tst_qqmlecmascript::initTestCase() +{ + QQmlDataTest::initTestCase(); + registerTypes(); +} + +void tst_qqmlecmascript::assignBasicTypes() +{ + { + QQmlComponent component(&engine, testFileUrl("assignBasicTypes.qml")); + MyTypeObject *object = qobject_cast<MyTypeObject *>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3); + QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2); + 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))); + 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.2)); + 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 != 0); + QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3); + QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2); + 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))); + 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.2)); + QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml"))); + delete object; + } +} + +void tst_qqmlecmascript::idShortcutInvalidates() +{ + { + QQmlComponent component(&engine, testFileUrl("idShortcutInvalidates.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + QVERIFY(object->objectProperty() != 0); + delete object->objectProperty(); + QVERIFY(object->objectProperty() == 0); + delete object; + } + + { + QQmlComponent component(&engine, testFileUrl("idShortcutInvalidates.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + QVERIFY(object->objectProperty() != 0); + delete object->objectProperty(); + QVERIFY(object->objectProperty() == 0); + delete object; + } +} + +void tst_qqmlecmascript::boolPropertiesEvaluateAsBool() +{ + { + QQmlComponent component(&engine, testFileUrl("boolPropertiesEvaluateAsBool.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->stringProperty(), QLatin1String("pass")); + delete object; + } + { + QQmlComponent component(&engine, testFileUrl("boolPropertiesEvaluateAsBool.2.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->stringProperty(), QLatin1String("pass")); + delete object; + } +} + +void tst_qqmlecmascript::signalAssignment() +{ + { + QQmlComponent component(&engine, testFileUrl("signalAssignment.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + 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 != 0); + 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; + } +} + +void tst_qqmlecmascript::methods() +{ + { + QQmlComponent component(&engine, testFileUrl("methods.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + 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 != 0); + 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 != 0); + QCOMPARE(object->property("test").toInt(), 19); + delete object; + } + + { + QQmlComponent component(&engine, testFileUrl("methods.4.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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 != 0); + QCOMPARE(object->property("test").toInt(), 9); + delete object; + } +} + +void tst_qqmlecmascript::bindingLoop() +{ + QQmlComponent component(&engine, testFileUrl("bindingLoop.qml")); + QString warning = component.url().toString() + ":5:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\""; + QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); + QObject *object = component.create(); + QVERIFY(object != 0); + 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); + + 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; + + 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<QList<QObject *> >()); + QList<QObject *> list = qvariant_cast<QList<QObject *> >(result); + QCOMPARE(list.count(), 4); + QCOMPARE(list.at(0), &obj1); + QCOMPARE(list.at(1), &obj2); + QCOMPARE(list.at(2), &obj3); + QCOMPARE(list.at(3), (QObject *)0); +} + +// Tests that modifying a context property will reevaluate expressions +void tst_qqmlecmascript::contextPropertiesTriggerReeval() +{ + 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() +{ + 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(0); + 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::deferredProperties() +{ + QQmlComponent component(&engine, testFileUrl("deferredProperties.qml")); + MyDeferredObject *object = + qobject_cast<MyDeferredObject *>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->value(), 0); + QVERIFY(object->objectProperty() == 0); + QVERIFY(object->objectProperty2() != 0); + qmlExecuteDeferred(object); + QCOMPARE(object->value(), 10); + QVERIFY(object->objectProperty() != 0); + MyQmlObject *qmlObject = + qobject_cast<MyQmlObject *>(object->objectProperty()); + QVERIFY(qmlObject != 0); + 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() +{ + QQmlComponent component(&engine, testFileUrl("deferredPropertiesErrors.qml")); + MyDeferredObject *object = + qobject_cast<MyDeferredObject *>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->value(), 0); + QVERIFY(object->objectProperty() == 0); + QVERIFY(object->objectProperty2() == 0); + + QString warning = component.url().toString() + ":6: Unable to assign [undefined] to QObject*"; + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + + qmlExecuteDeferred(object); + + delete object; +} + +void tst_qqmlecmascript::extensionObjects() +{ + QQmlComponent component(&engine, testFileUrl("extensionObjects.qml")); + MyExtendedObject *object = + qobject_cast<MyExtendedObject *>(component.create()); + QVERIFY(object != 0); + 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() +{ + QQmlComponent component(&engine, testFileUrl("extensionObjectsPropertyOverride.qml")); + OverrideDefaultPropertyObject *object = + qobject_cast<OverrideDefaultPropertyObject *>(component.create()); + QVERIFY(object != 0); + QVERIFY(object->secondProperty() != 0); + QVERIFY(object->firstProperty() == 0); + + delete object; +} + +void tst_qqmlecmascript::attachedProperties() +{ + { + QQmlComponent component(&engine, testFileUrl("attachedProperty.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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 != 0); + 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 != 0); + + QMetaObject::invokeMethod(object, "writeValue2"); + + MyQmlAttachedObject *attached = + qobject_cast<MyQmlAttachedObject *>(qmlAttachedPropertiesObject<MyQmlObject>(object)); + QVERIFY(attached != 0); + + QCOMPARE(attached->value2(), 9); + delete object; + } +} + +void tst_qqmlecmascript::enums() +{ + // Existent enums + { + QQmlComponent component(&engine, testFileUrl("enums.1.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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); + + delete object; + } + // Non-existent enums + { + QQmlComponent component(&engine, testFileUrl("enums.2.qml")); + + QString warning1 = component.url().toString() + ":5: Unable to assign [undefined] to int"; + QString warning2 = component.url().toString() + ":6: Unable to assign [undefined] to int"; + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1)); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2)); + + QObject *object = component.create(); + QVERIFY(object != 0); + QCOMPARE(object->property("a").toInt(), 0); + QCOMPARE(object->property("b").toInt(), 0); + + delete object; + } +} + +void tst_qqmlecmascript::valueTypeFunctions() +{ + QQmlComponent component(&engine, testFileUrl("valueTypeFunctions.qml")); + MyTypeObject *obj = qobject_cast<MyTypeObject*>(component.create()); + QVERIFY(obj != 0); + 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() +{ + // From ECMAScript + { + QQmlComponent component(&engine, testFileUrl("constantsOverrideBindings.1.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); + + 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 != 0); + + 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 != 0); + + 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() +{ + QQmlComponent component(&engine, + testFileUrl("outerBindingOverridesInnerBinding.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("nonExistentAttachedObject.qml")); + + QString warning = component.url().toString() + ":4: Unable to assign [undefined] to QString"; + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + + QObject *object = component.create(); + QVERIFY(object != 0); + + delete object; +} + +void tst_qqmlecmascript::scope() +{ + { + QQmlComponent component(&engine, testFileUrl("scope.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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 != 0); + + 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 != 0); + + QCOMPARE(object->property("test1").toBool(), true); + 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 != 0); + + 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 != 0); + + 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 != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("importScope.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("signalParameterTypes.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + 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); + + delete object; +} + +/* +Test that two JS objects for the same QObject compare as equal. +*/ +void tst_qqmlecmascript::objectsCompareAsEqual() +{ + QQmlComponent component(&engine, testFileUrl("objectsCompareAsEqual.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("aliasPropertyAndBinding.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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() +{ + QObject *object = 0; + + // 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 != 0); + QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0); + QCOMPARE(object->property("aliasIsUndefined"), QVariant(false)); + QMetaObject::invokeMethod(object, "resetAliased"); + QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() == 0); + 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 != 0); + QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0); + QCOMPARE(object->property("loaderSourceComponentIsUndefined"), QVariant(false)); + QMetaObject::invokeMethod(object, "resetAlias"); + QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() == 0); + 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 != 0); + 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*>() == 0); + 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 != 0); + QVERIFY(object->property("sourceComponentAlias").value<QQmlComponent*>() != 0); + QObject *loader = object->findChild<QObject*>("loader"); + QVERIFY(loader != 0); + 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*>() == 0); + 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*>() == 0); + 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 != 0); + 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 != 0); + 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::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); + + QQmlComponent component(&engine, testFileUrl("dynamicCreation.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + + 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() +{ + { + QQmlComponent component(&engine, testFileUrl("dynamicDeletion.qml")); + QQmlGuard<MyQmlObject> object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + QQmlGuard<QObject> createdQmlObject = 0; + + QMetaObject::invokeMethod(object, "create"); + createdQmlObject = object->objectProperty(); + QVERIFY(createdQmlObject); + QCOMPARE(createdQmlObject->objectName(), QString("emptyObject")); + + QMetaObject::invokeMethod(object, "killOther"); + QVERIFY(createdQmlObject); + + QCoreApplication::sendPostedEvents(0, 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(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } + } + QVERIFY(!createdQmlObject); + + QQmlEngine::setObjectOwnership(object, QQmlEngine::JavaScriptOwnership); + QMetaObject::invokeMethod(object, "killMe"); + QVERIFY(object); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QVERIFY(!object); + } + + { + QQmlComponent component(&engine, testFileUrl("dynamicDeletion.2.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QVERIFY(qvariant_cast<QObject*>(o->property("objectProperty")) == 0); + + QMetaObject::invokeMethod(o, "create"); + + QVERIFY(qvariant_cast<QObject*>(o->property("objectProperty")) != 0); + + QMetaObject::invokeMethod(o, "destroy"); + + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QVERIFY(qvariant_cast<QObject*>(o->property("objectProperty")) == 0); + + delete o; + } +} + +/* + tests that id.toString() works +*/ +void tst_qqmlecmascript::objectToString() +{ + QQmlComponent component(&engine, testFileUrl("qmlToString.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + 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"; + + QQmlComponent component(&engine, url); + QObject *object = component.create(); + QVERIFY(object != 0); + + // test QObjects in QML + QMetaObject::invokeMethod(object, "testHasOwnPropertySuccess"); + QVERIFY(object->property("result").value<bool>() == true); + QMetaObject::invokeMethod(object, "testHasOwnPropertyFailure"); + QVERIFY(object->property("result").value<bool>() == false); + + // now test other types in QML + QObject *child = object->findChild<QObject*>("typeObj"); + QVERIFY(child != 0); + 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("moduleApiTypeHasOwnProperty").toBool(), true); + QCOMPARE(child->property("moduleApiPropertyTypeHasOwnProperty").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("moduleApiNonPropertyHasOwnProperty").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() +{ + { + QQmlComponent component(&engine, testFileUrl("selfDeletingBinding.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + object->setProperty("triggerDelete", true); + delete object; + } + + { + QQmlComponent component(&engine, testFileUrl("selfDeletingBinding.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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() +{ + QQmlComponent component(&engine, testFileUrl("extendedObjectPropertyLookup.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + delete object; +} + +/* +Test that extended object properties can be accessed correctly. +*/ +void tst_qqmlecmascript::extendedObjectPropertyLookup2() +{ + QQmlComponent component(&engine, testFileUrl("extendedObjectPropertyLookup2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QVariant returnValue; + QVERIFY(QMetaObject::invokeMethod(object, "getValue", Q_RETURN_ARG(QVariant, returnValue))); + QCOMPARE(returnValue.toInt(), 42); + + delete object; +} +/* +Test file/lineNumbers for binding/Script errors. +*/ +void tst_qqmlecmascript::scriptErrors() +{ + 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: Can't find variable: a"; + QString warning3 = url.left(url.length() - 3) + "js:4: Error: Invalid write to global property \"a\""; + QString warning4 = url + ":13: ReferenceError: Can't find variable: a"; + QString warning5 = url + ":11: ReferenceError: Can't find variable: a"; + QString warning6 = url + ":10: Unable to assign [undefined] to int"; + QString warning7 = url + ":15: Error: 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 != 0); + + 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() +{ + 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 != 0); + 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 != 0); + + QString srpname = object->property("srp_name").toString(); + + warning = url + QLatin1String(":16: TypeError: Property 'scarceResource' of object ") + srpname + + QLatin1String(" is not a function"); + 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() +{ + QQmlComponent component(&engine, testFileUrl("propertyAssignmentErrors.qml")); + + QString url = component.url().toString(); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("signalTriggeredBindings.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("listProperties.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + + 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() +{ + 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 != 0); + + 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() +{ + 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 != 0); + delete object; +} + +void tst_qqmlecmascript::exceptionBindingProducesWarning() +{ + 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 != 0); + delete object; +} + +void tst_qqmlecmascript::compileInvalidBinding() +{ + // QTBUG-23387: ensure that invalid bindings don't cause a crash. + QQmlComponent component(&engine, testFileUrl("v8bindingException.qml")); + QString warning = component.url().toString() + ":16: SyntaxError: Unexpected token ILLEGAL"; + QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); + QObject *object = component.create(); + QVERIFY(object != 0); + delete object; +} + +static int transientErrorsMsgCount = 0; +static void transientErrorsMsgHandler(QtMsgType, const char *) +{ + ++transientErrorsMsgCount; +} + +// Check that transient binding errors are not displayed +void tst_qqmlecmascript::transientErrors() +{ + { + QQmlComponent component(&engine, testFileUrl("transientErrors.qml")); + + transientErrorsMsgCount = 0; + QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler); + + QObject *object = component.create(); + QVERIFY(object != 0); + + qInstallMsgHandler(old); + + QCOMPARE(transientErrorsMsgCount, 0); + + delete object; + } + + // One binding erroring multiple times, but then resolving + { + QQmlComponent component(&engine, testFileUrl("transientErrors.2.qml")); + + transientErrorsMsgCount = 0; + QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler); + + QObject *object = component.create(); + QVERIFY(object != 0); + + qInstallMsgHandler(old); + + QCOMPARE(transientErrorsMsgCount, 0); + + delete object; + } +} + +// Check that errors during shutdown are minimized +void tst_qqmlecmascript::shutdownErrors() +{ + QQmlComponent component(&engine, testFileUrl("shutdownErrors.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + transientErrorsMsgCount = 0; + QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler); + + delete object; + + qInstallMsgHandler(old); + QCOMPARE(transientErrorsMsgCount, 0); +} + +void tst_qqmlecmascript::compositePropertyType() +{ + 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() +{ + QQmlComponent component(&engine, testFileUrl("jsObject.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toInt(), 92); + + delete object; +} + +void tst_qqmlecmascript::undefinedResetsProperty() +{ + { + QQmlComponent component(&engine, testFileUrl("undefinedResetsProperty.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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 != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_22464.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + delete object; +} + +void tst_qqmlecmascript::qtbug_21580() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_21580.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + delete object; +} + +// QTBUG-6781 +void tst_qqmlecmascript::bug1() +{ + QQmlComponent component(&engine, testFileUrl("bug.1.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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; +} + +void tst_qqmlecmascript::bug2() +{ + QQmlComponent component(&engine); + component.setData("import Qt.test 1.0;\nQPlainTextEdit { width: 100 }", QUrl()); + + QObject *object = component.create(); + QVERIFY(object != 0); + + delete object; +} + +// Don't crash in createObject when the component has errors. +void tst_qqmlecmascript::dynamicCreationCrash() +{ + QQmlComponent component(&engine, testFileUrl("dynamicCreation.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + + QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); + QMetaObject::invokeMethod(object, "dontCrash"); + QObject *created = object->objectProperty(); + QVERIFY(created == 0); + + 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 != 0); + MyDynamicCreationDestructionObject *mdcdo = object->findChild<MyDynamicCreationDestructionObject*>("mdcdo"); + QVERIFY(mdcdo != 0); + 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(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + } + } + + delete object; + } + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, expectedDtorCount); +} + +void tst_qqmlecmascript::regExpBug() +{ + //QTBUG-9367 + { + QQmlComponent component(&engine, testFileUrl("regExp.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->regExp().pattern(), QLatin1String("[a-zA-z]")); + delete object; + } + + //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); + } +} + +static inline bool evaluate_error(QV8Engine *engine, v8::Handle<v8::Object> o, const char *source) +{ + QString functionSource = QLatin1String("(function(object) { return ") + + QLatin1String(source) + QLatin1String(" })"); + v8::TryCatch tc; + v8::Local<v8::Script> program = v8::Script::Compile(engine->toString(functionSource)); + if (tc.HasCaught()) + return false; + v8::Handle<v8::Function> function = v8::Handle<v8::Function>::Cast(program->Run()); + if (function.IsEmpty()) + return false; + v8::Handle<v8::Value> args[] = { o }; + function->Call(engine->global(), 1, args); + return tc.HasCaught(); +} + +static inline bool evaluate_value(QV8Engine *engine, v8::Handle<v8::Object> o, + const char *source, v8::Handle<v8::Value> result) +{ + QString functionSource = QLatin1String("(function(object) { return ") + + QLatin1String(source) + QLatin1String(" })"); + v8::TryCatch tc; + v8::Local<v8::Script> program = v8::Script::Compile(engine->toString(functionSource)); + if (tc.HasCaught()) + return false; + v8::Handle<v8::Function> function = v8::Handle<v8::Function>::Cast(program->Run()); + if (function.IsEmpty()) + return false; + v8::Handle<v8::Value> args[] = { o }; + + v8::Handle<v8::Value> value = function->Call(engine->global(), 1, args); + + if (tc.HasCaught()) + return false; + + return value->StrictEquals(result); +} + +static inline v8::Handle<v8::Value> evaluate(QV8Engine *engine, v8::Handle<v8::Object> o, + const char *source) +{ + QString functionSource = QLatin1String("(function(object) { return ") + + QLatin1String(source) + QLatin1String(" })"); + v8::TryCatch tc; + v8::Local<v8::Script> program = v8::Script::Compile(engine->toString(functionSource)); + if (tc.HasCaught()) + return v8::Handle<v8::Value>(); + v8::Handle<v8::Function> function = v8::Handle<v8::Function>::Cast(program->Run()); + if (function.IsEmpty()) + return v8::Handle<v8::Value>(); + v8::Handle<v8::Value> args[] = { o }; + + v8::Handle<v8::Value> value = function->Call(engine->global(), 1, args); + + if (tc.HasCaught()) + return v8::Handle<v8::Value>(); + return value; +} + +#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() +{ + MyInvokableObject o; + + QQmlEngine qmlengine; + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(&qmlengine); + + QV8Engine *engine = ep->v8engine(); + + v8::HandleScope handle_scope; + v8::Context::Scope scope(engine->context()); + + v8::Local<v8::Object> object = engine->newQObject(&o)->ToObject(); + + // 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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()", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 0); + QCOMPARE(o.actuals().count(), 0); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_NoArgs_int()", v8::Integer::New(6))); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 1); + QCOMPARE(o.actuals().count(), 0); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_NoArgs_real()", v8::Number::New(19.75))); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 2); + QCOMPARE(o.actuals().count(), 0); + + o.reset(); + { + v8::Handle<v8::Value> ret = EVALUATE("object.method_NoArgs_QPointF()"); + QVERIFY(!ret.IsEmpty()); + QCOMPARE(engine->toVariant(ret, -1), QVariant(QPointF(123, 4.5))); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 3); + QCOMPARE(o.actuals().count(), 0); + } + + o.reset(); + { + v8::Handle<v8::Value> ret = EVALUATE("object.method_NoArgs_QObject()"); + QCOMPARE(engine->toQObject(ret), (QObject *)&o); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 4); + QCOMPARE(o.actuals().count(), 0); + } + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_NoArgs_unknown()", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 5); + QCOMPARE(o.actuals().count(), 0); + + o.reset(); + { + v8::Handle<v8::Value> ret = EVALUATE("object.method_NoArgs_QScriptValue()"); + QVERIFY(ret->IsString()); + QCOMPARE(engine->toString(ret), 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()", engine->toString("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)", v8::Undefined())); + 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\")", v8::Undefined())); + 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\")", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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\")", v8::Undefined())); + 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\")", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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\")", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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())", v8::Undefined())); + 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())", v8::Undefined())); + 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)", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 13); + QCOMPARE(o.actuals().count(), 1); + QCOMPARE(o.actuals().at(0), qVariantFromValue((QObject *)0)); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_QObject(\"Hello world\")", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 13); + QCOMPARE(o.actuals().count(), 1); + QCOMPARE(o.actuals().at(0), qVariantFromValue((QObject *)0)); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_QObject(null)", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 13); + QCOMPARE(o.actuals().count(), 1); + QCOMPARE(o.actuals().at(0), qVariantFromValue((QObject *)0)); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 13); + QCOMPARE(o.actuals().count(), 1); + QCOMPARE(o.actuals().at(0), qVariantFromValue((QObject *)0)); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_QObject(object)", v8::Undefined())); + QCOMPARE(o.error(), false); + QCOMPARE(o.invoked(), 13); + QCOMPARE(o.actuals().count(), 1); + QCOMPARE(o.actuals().at(0), qVariantFromValue((QObject *)&o)); + + o.reset(); + QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(null)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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])", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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])", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Undefined())); + 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\")", v8::Undefined())); + 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)", v8::Undefined())); + 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)", v8::Integer::New(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)", v8::Integer::New(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)", v8::Undefined())); + 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)", v8::Undefined())); + 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\")", v8::Undefined())); + 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"))); +} + +// QTBUG-13047 (check that you can pass registered object types as args) +void tst_qqmlecmascript::invokableObjectArg() +{ + 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() +{ + QQmlComponent component(&engine, testFileUrl("invokableObjectRet.qml")); + + QObject *o = component.create(); + QVERIFY(o); + QCOMPARE(o->property("test").toBool(), true); + delete o; +} + +// QTBUG-5675 +void tst_qqmlecmascript::listToVariant() +{ + QQmlComponent component(&engine, testFileUrl("listToVariant.qml")); + + MyQmlContainer container; + + QQmlContext context(engine.rootContext()); + context.setContextObject(&container); + + QObject *object = component.create(&context); + QVERIFY(object != 0); + + QVariant v = object->property("test"); + QCOMPARE(v.userType(), qMetaTypeId<QQmlListReference>()); + QVERIFY(qvariant_cast<QQmlListReference>(v).object() == &container); + + delete object; +} + +// QTBUG-16316 +Q_DECLARE_METATYPE(QQmlListProperty<MyQmlObject>) +void tst_qqmlecmascript::listAssignment() +{ + 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() +{ + 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() +{ + QQmlComponent component(&engine, testFileUrl("attachedPropertyScope.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + MyQmlAttachedObject *attached = + qobject_cast<MyQmlAttachedObject *>(qmlAttachedPropertiesObject<MyQmlObject>(object)); + QVERIFY(attached != 0); + + QCOMPARE(object->property("value2").toInt(), 0); + + attached->emitMySignal(); + + QCOMPARE(object->property("value2").toInt(), 9); + + delete object; +} + +void tst_qqmlecmascript::scriptConnect() +{ + { + QQmlComponent component(&engine, testFileUrl("scriptConnect.1.qml")); + + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + 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 != 0); + + 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 != 0); + + 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 != 0); + + 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 != 0); + + 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 != 0); + + 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() +{ + { + QQmlComponent component(&engine, testFileUrl("scriptDisconnect.1.qml")); + + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + 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 != 0); + + 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 != 0); + + 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 != 0); + + 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() +{ + OwnershipObject own; + QQmlContext *context = new QQmlContext(engine.rootContext()); + context->setContextObject(&own); + + { + QQmlComponent component(&engine, testFileUrl("ownership.qml")); + + QVERIFY(own.object != 0); + + QObject *object = component.create(context); + + engine.collectGarbage(); + + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QVERIFY(own.object == 0); + + delete object; + } + + own.object = new QObject(&own); + + { + QQmlComponent component(&engine, testFileUrl("ownership.qml")); + + QVERIFY(own.object != 0); + + QObject *object = component.create(context); + + engine.collectGarbage(); + + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QVERIFY(own.object != 0); + + delete object; + } + + delete context; +} + +class CppOwnershipReturnValue : public QObject +{ + Q_OBJECT +public: + CppOwnershipReturnValue() : value(0) {} + ~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 == 0); + + 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 != 0); + QVERIFY(source.value != 0); + + delete object; + } + + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QVERIFY(source.value != 0); +} + +// QTBUG-15697 +void tst_qqmlecmascript::ownershipCustomReturnValue() +{ + CppOwnershipReturnValue source; + + { + QQmlEngine engine; + engine.rootContext()->setContextProperty("source", &source); + + QVERIFY(source.value == 0); + + 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 != 0); + QVERIFY(source.value != 0); + + delete object; + } + + engine.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QVERIFY(source.value == 0); +} + +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() +{ + 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() +{ + QQmlComponent component(&engine, testFileUrl("strictlyEquals.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("compiled.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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(), 5); + QCOMPARE(object->property("test18").toReal(), qreal(176)); + QCOMPARE(object->property("test19").toInt(), 7); + QCOMPARE(object->property("test20").toReal(), qreal(6.7)); + QCOMPARE(object->property("test21").toString(), QLatin1String("6.7")); + 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() +{ + QQmlComponent component(&engine, testFileUrl("numberAssignment.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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)7)); + 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() +{ + QQmlComponent component(&engine, testFileUrl("propertySplicing.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + delete object; +} + +// QTBUG-16683 +void tst_qqmlecmascript::signalWithUnknownTypes() +{ + QQmlComponent component(&engine, testFileUrl("signalWithUnknownTypes.qml")); + + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + MyQmlObject::MyType type; + type.value = 0x8971123; + emit object->signalWithUnknownType(type); + + MyQmlObject::MyType result = qvariant_cast<MyQmlObject::MyType>(object->variant()); + + QCOMPARE(result.value, type.value); + + + 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(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 arguments.callee(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); + + QQmlComponent component(&engine, testFileUrl("signalWithJSValueInVariant.qml")); + QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create())); + QVERIFY(object != 0); + + QJSValue value = engine.evaluate(expression); + QVERIFY(!engine.hasUncaughtException()); + 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); + + QQmlComponent component(&engine, testFileUrl("signalWithJSValueInVariant.qml")); + QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create())); + QVERIFY(object != 0); + + QJSEngine engine2; + QJSValue value = engine2.evaluate(expression); + QVERIFY(!engine2.hasUncaughtException()); + object->setProperty("expression", expression); + object->setProperty("compare", compare); + object->setProperty("pass", false); + + QTest::ignoreMessage(QtWarningMsg, "JSValue can't be rassigned to an another engine."); + emit object->signalWithVariant(QVariant::fromValue(value)); + QVERIFY(!object->property("pass").toBool()); +} + +void tst_qqmlecmascript::signalWithQJSValue_data() +{ + signalWithJSValueInVariant_data(); +} + +void tst_qqmlecmascript::signalWithQJSValue() +{ + QFETCH(QString, expression); + QFETCH(QString, compare); + + QQmlComponent component(&engine, testFileUrl("signalWithQJSValue.qml")); + QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create())); + QVERIFY(object != 0); + + QJSValue value = engine.evaluate(expression); + QVERIFY(!engine.hasUncaughtException()); + 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::moduleApi_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") + << testFileUrl("moduleapi/qobjectModuleApi.qml") + << QString() + << QStringList() + << (QStringList() << "existingUriTest" << "qobjectTest" << "qobjectMethodTest" + << "qobjectMinorVersionTest" << "qobjectMajorVersionTest" << "qobjectParentedTest") + << (QVariantList() << 20 << 20 << 1 << 20 << 20 << 26) + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); + + QTest::newRow("script, register + read") + << testFileUrl("moduleapi/scriptModuleApi.qml") + << QString() + << QStringList() + << (QStringList() << "scriptTest") + << (QVariantList() << 13) + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); + + QTest::newRow("qobject, caching + read") + << testFileUrl("moduleapi/qobjectModuleApiCaching.qml") + << QString() + << QStringList() + << (QStringList() << "existingUriTest" << "qobjectParentedTest") + << (QVariantList() << 20 << 26) // 26, shouldn't have incremented to 27. + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); + + QTest::newRow("script, caching + read") + << testFileUrl("moduleapi/scriptModuleApiCaching.qml") + << QString() + << QStringList() + << (QStringList() << "scriptTest") + << (QVariantList() << 13) // 13, shouldn't have incremented to 14. + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); + + QTest::newRow("qobject, writing + readonly constraints") + << testFileUrl("moduleapi/qobjectModuleApiWriting.qml") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("moduleapi/qobjectModuleApiWriting.qml").toLocalFile() + QLatin1String(":14: Error: Cannot assign to read-only property \"qobjectTestProperty\""))) + << (QStringList() << "readOnlyProperty" << "writableProperty") + << (QVariantList() << 20 << 50) + << (QStringList() << "firstProperty" << "writableProperty") + << (QVariantList() << 30 << 30) + << (QStringList() << "readOnlyProperty" << "writableProperty") + << (QVariantList() << 20 << 30); + + QTest::newRow("script, writing + readonly constraints") + << testFileUrl("moduleapi/scriptModuleApiWriting.qml") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("moduleapi/scriptModuleApiWriting.qml").toLocalFile() + QLatin1String(":21: Error: Cannot assign to read-only property \"scriptTestProperty\""))) + << (QStringList() << "readBack" << "unchanged") + << (QVariantList() << 13 << 42) + << (QStringList() << "firstProperty" << "secondProperty") + << (QVariantList() << 30 << 30) + << (QStringList() << "readBack" << "unchanged") + << (QVariantList() << 30 << 42); + + QTest::newRow("qobject module API enum values in JS") + << testFileUrl("moduleapi/qobjectModuleApiEnums.qml") + << QString() + << QStringList() + << (QStringList() << "enumValue" << "enumMethod") + << (QVariantList() << 42 << 30) + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); + + QTest::newRow("qobject, invalid major version fail") + << testFileUrl("moduleapi/moduleApiMajorVersionFail.qml") + << QString("QQmlComponent: Component is not ready") + << QStringList() + << QStringList() + << QVariantList() + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); + + QTest::newRow("qobject, invalid minor version fail") + << testFileUrl("moduleapi/moduleApiMinorVersionFail.qml") + << QString("QQmlComponent: Component is not ready") + << QStringList() + << QStringList() + << QVariantList() + << QStringList() + << QVariantList() + << QStringList() + << QVariantList(); +} + +void tst_qqmlecmascript::moduleApi() +{ + 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); + + QQmlComponent component(&engine, testfile); + + if (!errorMessage.isEmpty()) + QTest::ignoreMessage(QtWarningMsg, errorMessage.toAscii().constData()); + + if (warningMessages.size()) + foreach (const QString &warning, warningMessages) + QTest::ignoreMessage(QtWarningMsg, warning.toAscii().constData()); + + QObject *object = component.create(); + if (!errorMessage.isEmpty()) { + QVERIFY(object == 0); + } else { + QVERIFY(object != 0); + for (int i = 0; i < readProperties.size(); ++i) + QCOMPARE(object->property(readProperties.at(i).toAscii().constData()), readExpectedValues.at(i)); + for (int i = 0; i < writeProperties.size(); ++i) + QVERIFY(object->setProperty(writeProperties.at(i).toAscii().constData(), writeValues.at(i))); + for (int i = 0; i < readBackProperties.size(); ++i) + QCOMPARE(object->property(readBackProperties.at(i).toAscii().constData()), readBackExpectedValues.at(i)); + delete object; + } +} + +void tst_qqmlecmascript::importScripts_data() +{ + QTest::addColumn<QUrl>("testfile"); + QTest::addColumn<QString>("errorMessage"); + QTest::addColumn<QStringList>("warningMessages"); + QTest::addColumn<QStringList>("propertyNames"); + QTest::addColumn<QVariantList>("propertyValues"); + + QTest::newRow("basic functionality") + << testFileUrl("jsimport/testImport.qml") + << 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") + << QString() + << QStringList() + << (QStringList() << QLatin1String("componentError")) + << (QVariantList() << QVariant(5)); + + QTest::newRow("parent scope shouldn't be inherited by import with imports") + << testFileUrl("jsimportfail/failOne.qml") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("jsimportfail/failOne.qml").toLocalFile() + 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") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("jsimportfail/failTwo.qml").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: ImportOneJs"))) + << (QStringList() << QLatin1String("importScriptFunctionValue")) + << (QVariantList() << QVariant(QString())); + + QTest::newRow("module imports in an import should be private to the import scope") + << testFileUrl("jsimportfail/failThree.qml") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("jsimportfail/failThree.qml").toLocalFile() + 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") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("jsimportfail/failFour.qml").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: JsQtTest"))) + << (QStringList() << QLatin1String("importedModuleEnumValue")) + << (QVariantList() << QVariant(0)); + + QTest::newRow("import with imports has it's own activation scope") + << testFileUrl("jsimportfail/failFive.qml") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("jsimportfail/importWithImports.js").toLocalFile() + QLatin1String(":8: ReferenceError: Can't find variable: Component"))) + << (QStringList() << QLatin1String("componentError")) + << (QVariantList() << QVariant(0)); + + QTest::newRow("import pragma library script") + << testFileUrl("jsimport/testImportPragmaLibrary.qml") + << QString() + << QStringList() + << (QStringList() << QLatin1String("testValue")) + << (QVariantList() << QVariant(31)); + + QTest::newRow("pragma library imports shouldn't inherit parent imports or scope") + << testFileUrl("jsimportfail/testImportPragmaLibrary.qml") + << QString() + << (QStringList() << QString(QLatin1String("file://") + testFileUrl("jsimportfail/importPragmaLibrary.js").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: Component"))) + << (QStringList() << QLatin1String("testValue")) + << (QVariantList() << QVariant(0)); + + QTest::newRow("import pragma library script which has an import") + << testFileUrl("jsimport/testImportPragmaLibraryWithImports.qml") + << QString() + << QStringList() + << (QStringList() << QLatin1String("testValue")) + << (QVariantList() << QVariant(55)); + + QTest::newRow("import pragma library script which has a pragma library import") + << testFileUrl("jsimport/testImportPragmaLibraryWithPragmaLibraryImports.qml") + << QString() + << QStringList() + << (QStringList() << QLatin1String("testValue")) + << (QVariantList() << QVariant(18)); +} + +void tst_qqmlecmascript::importScripts() +{ + QFETCH(QUrl, testfile); + QFETCH(QString, errorMessage); + QFETCH(QStringList, warningMessages); + QFETCH(QStringList, propertyNames); + QFETCH(QVariantList, propertyValues); + + QQmlComponent component(&engine, testfile); + + if (!errorMessage.isEmpty()) + QTest::ignoreMessage(QtWarningMsg, errorMessage.toAscii().constData()); + + if (warningMessages.size()) + foreach (const QString &warning, warningMessages) + QTest::ignoreMessage(QtWarningMsg, warning.toAscii().constData()); + + QObject *object = component.create(); + if (!errorMessage.isEmpty()) { + QVERIFY(object == 0); + } else { + QVERIFY(object != 0); + for (int i = 0; i < propertyNames.size(); ++i) + QCOMPARE(object->property(propertyNames.at(i).toAscii().constData()), propertyValues.at(i)); + delete object; + } +} + +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; + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(&engine); + ScarceResourceObject *eo = 0; + QObject *srsc = 0; + QObject *object = 0; + + /* 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(ep->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 != 0); + 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(ep->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 != 0); + 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. + srp_name = object->property("srp_name").toString(); + expectedWarning = varComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ") + srp_name + QLatin1String(" is not a function"); + 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(ep->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 != 0); + QVERIFY(!object->property("varProperty").isValid()); // not assigned yet + QMetaObject::invokeMethod(object, "assignVarProperty"); + QVERIFY(ep->scarceResources.isEmpty()); // the scarce resource is a VME property. + QMetaObject::invokeMethod(object, "deassignVarProperty"); + QVERIFY(ep->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 != 0); + 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(ep->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 != 0); + 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(ep->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 != 0); + 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. + srp_name = object->property("srp_name").toString(); + expectedWarning = variantComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ") + srp_name + QLatin1String(" is not a function"); + 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(ep->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); + + QQmlEnginePrivate *ep = QQmlEnginePrivate::get(&engine); + ScarceResourceObject *eo = 0; + QObject *object = 0; + + QQmlComponent c(&engine, qmlFile); + object = c.create(); + QVERIFY(object != 0); + 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(ep->scarceResources.isEmpty()); + delete object; +} + +void tst_qqmlecmascript::propertyChangeSlots() +{ + // ensure that allowable property names are allowed and onPropertyNameChanged slots are generated correctly. + QQmlComponent component(&engine, testFileUrl("changeslots/propertyChangeSlots.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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 == 0); + 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 == 0); + 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 == 0); + 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 == 0); + 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"); +} + +void tst_qqmlecmascript::propertyVar() +{ + QFETCH(QUrl, qmlFile); + + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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 = 0; + + // 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. + QQmlComponent component(&engine, testFileUrl("propertyVarCpp.qml")); + object = component.create(); + QVERIFY(object != 0); + // 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)); + QCOMPARE(object->property("varProperty").userType(), (int)QVariant::Int); + QCOMPARE(object->property("varBound").userType(), (int)QVariant::Int); + // 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(), (int)QVariant::Map); + delete object; +} + +static void gc(QQmlEngine &engine) +{ + engine.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); +} + +void tst_qqmlecmascript::propertyVarOwnership() +{ + // Referenced JS objects are not collected + { + QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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 != 0); + 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 != 0); + + 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 != 0); + + 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; + } +} + +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. + QQmlComponent component(&engine, testFileUrl("propertyVarImplicitOwnership.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QObject *rootObject = object->property("vp").value<QObject*>(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild<QObject*>("text"); + QVERIFY(childObject != 0); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + QMetaObject::invokeMethod(childObject, "constructQObject"); // creates a reference to a constructed QObject. + QWeakPointer<QObject> qobjectGuard(childObject->property("vp").value<QObject*>()); // get the pointer prior to processing deleteLater events. + QVERIFY(!qobjectGuard.isNull()); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QVERIFY(!qobjectGuard.isNull()); + QMetaObject::invokeMethod(object, "deassignCircular"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QVERIFY(qobjectGuard.isNull()); // should have been collected now. + delete object; +} + +void tst_qqmlecmascript::propertyVarReparent() +{ + // ensure that nothing breaks if we re-parent objects + QQmlComponent component(&engine, testFileUrl("propertyVar.reparent.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignVarProp"); + QCoreApplication::sendPostedEvents(0, 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"); + QWeakPointer<QObject> rectGuard(rect); + QWeakPointer<QObject> textGuard(text); + QWeakPointer<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*>(); + QWeakPointer<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"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + 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 + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + 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. + QQmlComponent component(&engine, testFileUrl("propertyVar.reparent.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignVarProp"); + QCoreApplication::sendPostedEvents(0, 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"); + QWeakPointer<QObject> rectGuard(rect); + QWeakPointer<QObject> textGuard(text); + QWeakPointer<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*>(); + QWeakPointer<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 + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + 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. + QQmlComponent component(&engine, testFileUrl("propertyVar.circular.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); // cause assignment and gc + QCoreApplication::sendPostedEvents(0, 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 + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QVERIFY(!canaryResourcePixmap.isDetached()); // two copies extant - this and the propertyVar.vp.vp.vp.vp.memoryHog. + QMetaObject::invokeMethod(object, "deassignCircular"); // cause deassignment and gc + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + 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. + QQmlComponent component(&engine, testFileUrl("propertyVar.circular.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QObject *rootObject = object->property("vp").value<QObject*>(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild<QObject*>("text"); + QVERIFY(childObject != 0); + QWeakPointer<QObject> rootObjectTracker(rootObject); + QVERIFY(!rootObjectTracker.isNull()); + QWeakPointer<QObject> childObjectTracker(childObject); + QVERIFY(!childObjectTracker.isNull()); + gc(engine); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + QMetaObject::invokeMethod(object, "deassignCircular"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QVERIFY(rootObjectTracker.isNull()); // should have been collected + QVERIFY(childObjectTracker.isNull()); // should have been collected + delete object; +} + +void tst_qqmlecmascript::propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter) +{ + *(int*)(parameter) += 1; + qPersistentDispose(object); +} + +void tst_qqmlecmascript::propertyVarInheritance() +{ + int propertyVarWeakRefCallbackCount = 0; + + // enforce behaviour regarding element inheritance - ensure handle disposal. + // The particular component under test here has a chain of references. + QQmlComponent component(&engine, testFileUrl("propertyVar.inherit.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); // cause assignment and gc + QCoreApplication::sendPostedEvents(0, 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*>(); + QQmlVMEMetaObject *icovmemo = ((QQmlVMEMetaObject *)(ico5->metaObject())); + QQmlVMEMetaObject *ccovmemo = ((QQmlVMEMetaObject *)(cco5->metaObject())); + v8::Persistent<v8::Value> icoCanaryHandle; + v8::Persistent<v8::Value> ccoCanaryHandle; + { + v8::HandleScope hs; + // 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. + icoCanaryHandle = qPersistentNew(icovmemo->vmeProperty(ico5->metaObject()->indexOfProperty("circ"))); + ccoCanaryHandle = qPersistentNew(ccovmemo->vmeProperty(cco5->metaObject()->indexOfProperty("circ"))); + // we make them weak and invoke the gc, but we should not hit the weak-callback yet + // as the varproperties array of each vmemo still references the resource. + icoCanaryHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback); + ccoCanaryHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback); + gc(engine); + QVERIFY(propertyVarWeakRefCallbackCount == 0); + } + // now we deassign the var prop, which should trigger collection of item subtrees. + QMetaObject::invokeMethod(object, "deassignCircular"); // cause deassignment and gc + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + // ensure that there are only weak handles to the underlying varProperties array remaining. + gc(engine); + QCOMPARE(propertyVarWeakRefCallbackCount, 2); // should have been called for both, since all refs should be weak. + 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() +{ + int propertyVarWeakRefCallbackCount = 0; + + // 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. + QQmlComponent component(&engine, testFileUrl("propertyVar.circular.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "assignCircular"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QObject *rootObject = object->property("vp").value<QObject*>(); + QVERIFY(rootObject != 0); + QObject *childObject = rootObject->findChild<QObject*>("text"); + QVERIFY(childObject != 0); + QCOMPARE(rootObject->property("rectCanary").toInt(), 5); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + v8::Persistent<v8::Value> childObjectVarArrayValueHandle; + { + v8::HandleScope hs; + propertyVarWeakRefCallbackCount = 0; // reset callback count. + childObjectVarArrayValueHandle = qPersistentNew(((QQmlVMEMetaObject *)(childObject->metaObject()))->vmeProperty(childObject->metaObject()->indexOfProperty("vp"))); + childObjectVarArrayValueHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback); + gc(engine); + QVERIFY(propertyVarWeakRefCallbackCount == 0); // should not have been collected yet. + QCOMPARE(childObject->property("vp").value<QObject*>(), rootObject); + QCOMPARE(childObject->property("textCanary").toInt(), 10); + } + QMetaObject::invokeMethod(object, "deassignCircular"); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. + QCoreApplication::processEvents(); + QVERIFY(propertyVarWeakRefCallbackCount == 1); // should have been collected now. + delete object; +} + +// Ensure that QObject type conversion works on binding assignment +void tst_qqmlecmascript::elementAssign() +{ + QQmlComponent component(&engine, testFileUrl("elementAssign.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + delete object; +} + +// QTBUG-12457 +void tst_qqmlecmascript::objectPassThroughSignals() +{ + QQmlComponent component(&engine, testFileUrl("objectsPassThroughSignals.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + QCOMPARE(object->property("test").toBool(), true); + + delete object; +} + +// QTBUG-21626 +void tst_qqmlecmascript::objectConversion() +{ + QQmlComponent component(&engine, testFileUrl("objectConversion.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + QVariant retn; + QMetaObject::invokeMethod(object, "circularObject", Q_RETURN_ARG(QVariant, retn)); + QCOMPARE(retn.value<QVariantMap>().value("test"), QVariant(100)); + + delete object; +} + + +// QTBUG-20242 +void tst_qqmlecmascript::booleanConversion() +{ + QQmlComponent component(&engine, testFileUrl("booleanConversion.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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 != 0); + CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro"); + cro->setEngine(&hrmEngine); + cro->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object, "createReference"); + gc(engine); + QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference + delete object; + hrmEngine.collectGarbage(); + QCoreApplication::sendPostedEvents(0, 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 != 0); + CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro"); + cro->setEngine(&hrmEngine); + cro->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object, "circularReference"); + gc(engine); + QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive. + delete object; + hrmEngine.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 3); + } + + dtorCount = 0; + { + // Linear handle reference + QQmlEngine hrmEngine; + QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.handle.1.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + CircularReferenceHandle *crh = object->findChild<CircularReferenceHandle*>("crh"); + QVERIFY(crh != 0); + crh->setEngine(&hrmEngine); + crh->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object, "createReference"); + CircularReferenceHandle *first = object->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second = object->property("second").value<CircularReferenceHandle*>(); + QVERIFY(first != 0); + QVERIFY(second != 0); + first->addReference(QQmlData::get(second)->v8object); // create reference + // now we have to reparent second and make second owned by JS. + second->setParent(0); + QQmlEngine::setObjectOwnership(second, QQmlEngine::JavaScriptOwnership); + gc(engine); + QCOMPARE(dtorCount, 0); // due to reference from first to second, second shouldn't be collected. + delete object; + hrmEngine.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 3); + } + + dtorCount = 0; + { + // Circular handle reference + QQmlEngine hrmEngine; + QQmlComponent component(&hrmEngine, testFileUrl("handleReferenceManagement.handle.2.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + CircularReferenceHandle *crh = object->findChild<CircularReferenceHandle*>("crh"); + QVERIFY(crh != 0); + crh->setEngine(&hrmEngine); + crh->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object, "circularReference"); + CircularReferenceHandle *first = object->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second = object->property("second").value<CircularReferenceHandle*>(); + QVERIFY(first != 0); + QVERIFY(second != 0); + first->addReference(QQmlData::get(second)->v8object); // create circular reference + second->addReference(QQmlData::get(first)->v8object); // note: must be weak. + // now we have to reparent and change ownership. + first->setParent(0); + second->setParent(0); + QQmlEngine::setObjectOwnership(first, QQmlEngine::JavaScriptOwnership); + QQmlEngine::setObjectOwnership(second, QQmlEngine::JavaScriptOwnership); + gc(engine); + QCOMPARE(dtorCount, 2); // despite circular references, both will be collected. + delete object; + hrmEngine.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 3); + } + + dtorCount = 0; + { + // multiple engine interaction - linear reference + QQmlEngine hrmEngine1; + QQmlEngine hrmEngine2; + QQmlComponent component1(&hrmEngine1, testFileUrl("handleReferenceManagement.handle.1.qml")); + QQmlComponent component2(&hrmEngine2, testFileUrl("handleReferenceManagement.handle.1.qml")); + QObject *object1 = component1.create(); + QObject *object2 = component2.create(); + QVERIFY(object1 != 0); + QVERIFY(object2 != 0); + CircularReferenceHandle *crh1 = object1->findChild<CircularReferenceHandle*>("crh"); + CircularReferenceHandle *crh2 = object2->findChild<CircularReferenceHandle*>("crh"); + QVERIFY(crh1 != 0); + QVERIFY(crh2 != 0); + crh1->setEngine(&hrmEngine1); + crh2->setEngine(&hrmEngine2); + crh1->setDtorCount(&dtorCount); + crh2->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object1, "createReference"); + QMetaObject::invokeMethod(object2, "createReference"); + CircularReferenceHandle *first1 = object1->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second1 = object1->property("second").value<CircularReferenceHandle*>(); + CircularReferenceHandle *first2 = object2->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second2 = object2->property("second").value<CircularReferenceHandle*>(); + QVERIFY(first1 != 0); + QVERIFY(second1 != 0); + QVERIFY(first2 != 0); + QVERIFY(second2 != 0); + first1->addReference(QQmlData::get(second2)->v8object); // create reference across engines + // now we have to reparent second2 and make second2 owned by JS. + second2->setParent(0); + QQmlEngine::setObjectOwnership(second2, QQmlEngine::JavaScriptOwnership); + gc(engine); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 0); // due to reference from first1 to second2, second2 shouldn't be collected. + delete object1; + delete object2; + hrmEngine1.collectGarbage(); + hrmEngine2.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 6); + } + + dtorCount = 0; + { + // multiple engine interaction - circular reference + QQmlEngine hrmEngine1; + QQmlEngine hrmEngine2; + QQmlComponent component1(&hrmEngine1, testFileUrl("handleReferenceManagement.handle.1.qml")); + QQmlComponent component2(&hrmEngine2, testFileUrl("handleReferenceManagement.handle.1.qml")); + QObject *object1 = component1.create(); + QObject *object2 = component2.create(); + QVERIFY(object1 != 0); + QVERIFY(object2 != 0); + CircularReferenceHandle *crh1 = object1->findChild<CircularReferenceHandle*>("crh"); + CircularReferenceHandle *crh2 = object2->findChild<CircularReferenceHandle*>("crh"); + QVERIFY(crh1 != 0); + QVERIFY(crh2 != 0); + crh1->setEngine(&hrmEngine1); + crh2->setEngine(&hrmEngine2); + crh1->setDtorCount(&dtorCount); + crh2->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object1, "createReference"); + QMetaObject::invokeMethod(object2, "createReference"); + CircularReferenceHandle *first1 = object1->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second1 = object1->property("second").value<CircularReferenceHandle*>(); + CircularReferenceHandle *first2 = object2->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second2 = object2->property("second").value<CircularReferenceHandle*>(); + QVERIFY(first1 != 0); + QVERIFY(second1 != 0); + QVERIFY(first2 != 0); + QVERIFY(second2 != 0); + first1->addReference(QQmlData::get(second1)->v8object); // create linear reference within engine1 + second1->addReference(QQmlData::get(second2)->v8object); // create linear reference across engines + second2->addReference(QQmlData::get(first2)->v8object); // create linear reference within engine2 + first2->addReference(QQmlData::get(first1)->v8object); // close the loop - circular ref across engines + // now we have to reparent and change ownership to JS. + first1->setParent(0); + second1->setParent(0); + first2->setParent(0); + second2->setParent(0); + QQmlEngine::setObjectOwnership(first1, QQmlEngine::JavaScriptOwnership); + QQmlEngine::setObjectOwnership(second1, QQmlEngine::JavaScriptOwnership); + QQmlEngine::setObjectOwnership(first2, QQmlEngine::JavaScriptOwnership); + QQmlEngine::setObjectOwnership(second2, QQmlEngine::JavaScriptOwnership); + gc(engine); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 4); // circular references shouldn't keep them alive. + delete object1; + delete object2; + hrmEngine1.collectGarbage(); + hrmEngine2.collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 6); + } + + dtorCount = 0; + { + // multiple engine interaction - linear reference with engine deletion + QQmlEngine *hrmEngine1 = new QQmlEngine; + QQmlEngine *hrmEngine2 = new QQmlEngine; + QQmlComponent component1(hrmEngine1, testFileUrl("handleReferenceManagement.handle.1.qml")); + QQmlComponent component2(hrmEngine2, testFileUrl("handleReferenceManagement.handle.1.qml")); + QObject *object1 = component1.create(); + QObject *object2 = component2.create(); + QVERIFY(object1 != 0); + QVERIFY(object2 != 0); + CircularReferenceHandle *crh1 = object1->findChild<CircularReferenceHandle*>("crh"); + CircularReferenceHandle *crh2 = object2->findChild<CircularReferenceHandle*>("crh"); + QVERIFY(crh1 != 0); + QVERIFY(crh2 != 0); + crh1->setEngine(hrmEngine1); + crh2->setEngine(hrmEngine2); + crh1->setDtorCount(&dtorCount); + crh2->setDtorCount(&dtorCount); + QMetaObject::invokeMethod(object1, "createReference"); + QMetaObject::invokeMethod(object2, "createReference"); + CircularReferenceHandle *first1 = object1->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second1 = object1->property("second").value<CircularReferenceHandle*>(); + CircularReferenceHandle *first2 = object2->property("first").value<CircularReferenceHandle*>(); + CircularReferenceHandle *second2 = object2->property("second").value<CircularReferenceHandle*>(); + QVERIFY(first1 != 0); + QVERIFY(second1 != 0); + QVERIFY(first2 != 0); + QVERIFY(second2 != 0); + first1->addReference(QQmlData::get(second1)->v8object); // create linear reference within engine1 + second1->addReference(QQmlData::get(second2)->v8object); // create linear reference across engines + second2->addReference(QQmlData::get(first2)->v8object); // create linear reference within engine2 + // now we have to reparent and change ownership to JS. + first1->setParent(crh1); + second1->setParent(0); + first2->setParent(0); + second2->setParent(0); + QQmlEngine::setObjectOwnership(second1, QQmlEngine::JavaScriptOwnership); + QQmlEngine::setObjectOwnership(first2, QQmlEngine::JavaScriptOwnership); + QQmlEngine::setObjectOwnership(second2, QQmlEngine::JavaScriptOwnership); + gc(engine); + QCOMPARE(dtorCount, 0); + delete hrmEngine2; + gc(engine); + QCOMPARE(dtorCount, 0); + delete object1; + delete object2; + hrmEngine1->collectGarbage(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + QCOMPARE(dtorCount, 6); + delete hrmEngine1; + } +} + +void tst_qqmlecmascript::stringArg() +{ + QQmlComponent component(&engine, testFileUrl("stringArg.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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.toAscii().constData()); + QMetaObject::invokeMethod(object, "failure"); + QVERIFY(object->property("returnValue").toBool()); + + delete object; +} + +void tst_qqmlecmascript::readonlyDeclaration() +{ + QQmlComponent component(&engine, testFileUrl("readonlyDeclaration.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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() +{ + { + QUrl qmlFile = testFileUrl("sequenceConversion.read.qml"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + 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 != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + // we haven't registered QList<QPoint> as a sequence type. + QString warningOne = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'QList<QPoint>' for property 'MySequenceConversionObject::pointListProperty'"); + QString warningTwo = qmlFile.toString() + QLatin1String(":18: TypeError: Cannot read property 'length' of undefined"); + QTest::ignoreMessage(QtWarningMsg, warningOne.toAscii().constData()); + QTest::ignoreMessage(QtWarningMsg, warningTwo.toAscii().constData()); + + QMetaObject::invokeMethod(object, "performTest"); + + // QList<QPoint> 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<QPoint>' for property 'MySequenceConversionObject::pointListProperty'"); + QQmlProperty seqProp(seq, "pointListProperty", &engine); + QVERIFY(!seqProp.read().isValid()); // not a valid/known sequence type + + delete object; + } +} + +void tst_qqmlecmascript::sequenceConversionWrite() +{ + { + QUrl qmlFile = testFileUrl("sequenceConversion.write.qml"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + 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 != 0); + MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco"); + QVERIFY(seq != 0); + + // we haven't registered QList<QPoint> as a sequence type, so writing shouldn't work. + QString warningOne = qmlFile.toString() + QLatin1String(":16: Error: Cannot assign QVariantList to void"); + QTest::ignoreMessage(QtWarningMsg, warningOne.toAscii().constData()); + + QMetaObject::invokeMethod(object, "performTest"); + + QList<QPoint> pointList; pointList << QPoint(1, 2) << QPoint(3, 4) << QPoint(5, 6); // 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"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + QMetaObject::invokeMethod(object, "indexedAccess"); + QVERIFY(object->property("success").toBool()); + QMetaObject::invokeMethod(object, "arrayOperations"); + QVERIFY(object->property("success").toBool()); + QMetaObject::invokeMethod(object, "testEqualitySemantics"); + QVERIFY(object->property("success").toBool()); + QMetaObject::invokeMethod(object, "testReferenceDeletion"); + QCOMPARE(object->property("referenceDeletion").toBool(), true); + delete object; +} + + +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"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + 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"); + QString w4 = qmlFile.toString() + QLatin1String(":78: std::bad_alloc during length set"); + QString w5 = qmlFile.toString() + QLatin1String(":83: std::bad_alloc during indexed set"); + 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)); + QMetaObject::invokeMethod(object, "indexedAccess"); + 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"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + + 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() +{ + { + QUrl qmlFile = testFileUrl("sequenceConversion.bindings.qml"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + 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: Unable to assign QList<int> to QList<bool>")).arg(qmlFile.toString()); + QTest::ignoreMessage(QtWarningMsg, warning.toAscii().constData()); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + delete object; + } +} + +void tst_qqmlecmascript::sequenceConversionCopy() +{ + QUrl qmlFile = testFileUrl("sequenceConversion.copy.qml"); + QQmlComponent component(&engine, qmlFile); + QObject *object = component.create(); + QVERIFY(object != 0); + 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); + delete object; +} + +void tst_qqmlecmascript::assignSequenceTypes() +{ + // test binding array to sequence type property + { + QQmlComponent component(&engine, testFileUrl("assignSequenceTypes.1.qml")); + MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create()); + QVERIFY(object != 0); + QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2)); + QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2)); + 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 != 0); + 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 != 0); + 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(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 != 0); + QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2)); + QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2)); + 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 != 0); + 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 != 0); + 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(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 != 0); + 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 != 0 && msco2 != 0 && msco3 != 0 && msco4 != 0 && msco5 != 0); + 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; + } +} + +// Test that assigning a null object works +// Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4 +void tst_qqmlecmascript::nullObjectBinding() +{ + QQmlComponent component(&engine, testFileUrl("nullObjectBinding.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + QVERIFY(object->property("test") == QVariant::fromValue((QObject *)0)); + + delete object; +} + +// 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 != 0); + + QCOMPARE(object->property("a").toInt(), 39); + object->setProperty("b", QVariant(9)); + QCOMPARE(object->property("a").toInt(), 117); + + delete engine; + + QCOMPARE(object->property("a").toInt(), 117); + object->setProperty("b", QVariant(10)); + QCOMPARE(object->property("a").toInt(), 117); + + delete object; +} + +// Test the crashing part of QTBUG-9705 +void tst_qqmlecmascript::libraryScriptAssert() +{ + QQmlComponent component(&engine, testFileUrl("libraryScriptAssert.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + delete object; +} + +void tst_qqmlecmascript::variantsAssignedUndefined() +{ + QQmlComponent component(&engine, testFileUrl("variantsAssignedUndefined.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + 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::qtbug_9792() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_9792.qml")); + + QQmlContext *context = new QQmlContext(engine.rootContext()); + + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create(context)); + QVERIFY(object != 0); + + QTest::ignoreMessage(QtDebugMsg, "Hello world!"); + object->basicSignal(); + + delete context; + + transientErrorsMsgCount = 0; + QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler); + + object->basicSignal(); + + qInstallMsgHandler(old); + + QCOMPARE(transientErrorsMsgCount, 0); + + delete object; +} + +// Verifies that QQmlGuard<>s used in the vmemetaobject are cleaned correctly +void tst_qqmlecmascript::qtcreatorbug_1289() +{ + QQmlComponent component(&engine, testFileUrl("qtcreatorbug_1289.qml")); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QObject *nested = qvariant_cast<QObject *>(o->property("object")); + QVERIFY(nested != 0); + + QVERIFY(qvariant_cast<QObject *>(nested->property("nestedObject")) == o); + + delete nested; + nested = qvariant_cast<QObject *>(o->property("object")); + QVERIFY(nested == 0); + + // If the bug is present, the next line will crash + delete o; +} + +// Test that we shut down without stupid warnings +void tst_qqmlecmascript::noSpuriousWarningsAtShutdown() +{ + { + QQmlComponent component(&engine, testFileUrl("noSpuriousWarningsAtShutdown.qml")); + + QObject *o = component.create(); + + transientErrorsMsgCount = 0; + QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler); + + delete o; + + qInstallMsgHandler(old); + + QCOMPARE(transientErrorsMsgCount, 0); + } + + + { + QQmlComponent component(&engine, testFileUrl("noSpuriousWarningsAtShutdown.2.qml")); + + QObject *o = component.create(); + + transientErrorsMsgCount = 0; + QtMsgHandler old = qInstallMsgHandler(transientErrorsMsgHandler); + + delete o; + + qInstallMsgHandler(old); + + QCOMPARE(transientErrorsMsgCount, 0); + } +} + +void tst_qqmlecmascript::canAssignNullToQObject() +{ + { + QQmlComponent component(&engine, testFileUrl("canAssignNullToQObject.1.qml")); + + MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(o != 0); + + QVERIFY(o->objectProperty() != 0); + + o->setProperty("runTest", true); + + QVERIFY(o->objectProperty() == 0); + + delete o; + } + + { + QQmlComponent component(&engine, testFileUrl("canAssignNullToQObject.2.qml")); + + MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(o != 0); + + QVERIFY(o->objectProperty() == 0); + + delete o; + } +} + +void tst_qqmlecmascript::functionAssignment_fromBinding() +{ + QQmlComponent component(&engine, testFileUrl("functionAssignment.1.qml")); + + QString url = component.url().toString(); + QString warning = url + ":4: Unable to assign a function to a property."; + QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); + + MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(o != 0); + + QVERIFY(!o->property("a").isValid()); + + delete o; +} + +void tst_qqmlecmascript::functionAssignment_fromJS() +{ + QFETCH(QString, triggerProperty); + + QQmlComponent component(&engine, testFileUrl("functionAssignment.2.qml")); + QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString())); + + MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(o != 0); + 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() +{ + QQmlComponent component(&engine, testFileUrl("functionAssignment.2.qml")); + QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString())); + + MyQmlObject *o = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(o != 0); + 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::eval() +{ + QQmlComponent component(&engine, testFileUrl("eval.qml")); + + QObject *o = component.create(); + QVERIFY(o != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("function.qml")); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test1").toBool(), true); + QCOMPARE(o->property("test2").toBool(), true); + QCOMPARE(o->property("test3").toBool(), true); + + delete o; +} + +void tst_qqmlecmascript::functionException() +{ + // QTBUG-24037 - shouldn't crash. + QString errstr = testFileUrl("v8functionException.qml").toString() + QLatin1String(":13: SyntaxError: Unexpected token ILLEGAL"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(errstr)); + QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Exception occurred during compilation of function: dynamicSlot()"); + QQmlComponent component(&engine, testFileUrl("v8functionException.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QMetaObject::invokeMethod(o, "dynamicSlot"); + delete o; +} + +// Test the "Qt.include" method +void tst_qqmlecmascript::include() +{ + // Non-library relative include + { + QQmlComponent component(&engine, testFileUrl("include.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + 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 != 0); + + 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 != 0); + + 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 != 0); + QCOMPARE(o->property("test1").toInt(), 100); + + delete o; + } + + // Remote - success + { + TestHTTPServer server(8111); + QVERIFY(server.isValid()); + server.serveDirectory(dataDirectory()); + + QQmlComponent component(&engine, testFileUrl("include_remote.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QTRY_VERIFY(o->property("done").toBool() == true); + QTRY_VERIFY(o->property("done2").toBool() == true); + + 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; + } + + // Remote - error + { + TestHTTPServer server(8111); + QVERIFY(server.isValid()); + server.serveDirectory(dataDirectory()); + + QQmlComponent component(&engine, testFileUrl("include_remote_missing.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QTRY_VERIFY(o->property("done").toBool() == true); + + QCOMPARE(o->property("test1").toBool(), true); + QCOMPARE(o->property("test2").toBool(), true); + QCOMPARE(o->property("test3").toBool(), true); + + delete o; + } +} + +void tst_qqmlecmascript::signalHandlers() +{ + QQmlComponent component(&engine, testFileUrl("signalHandlers.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QVERIFY(o->property("count").toInt() == 0); + QMetaObject::invokeMethod(o, "testSignalCall"); + QCOMPARE(o->property("count").toInt(), 1); + + QMetaObject::invokeMethod(o, "testSignalHandlerCall"); + QCOMPARE(o->property("count").toInt(), 1); + QCOMPARE(o->property("errorString").toString(), QLatin1String("TypeError: Property 'onTestSignal' of object [object Object] is not a function")); + + QVERIFY(o->property("funcCount").toInt() == 0); + QMetaObject::invokeMethod(o, "testSignalConnection"); + QCOMPARE(o->property("funcCount").toInt(), 1); + + QMetaObject::invokeMethod(o, "testSignalHandlerConnection"); + QCOMPARE(o->property("funcCount").toInt(), 2); + + QMetaObject::invokeMethod(o, "testSignalDefined"); + QCOMPARE(o->property("definedResult").toBool(), true); + + QMetaObject::invokeMethod(o, "testSignalHandlerDefined"); + QCOMPARE(o->property("definedHandlerResult").toBool(), true); + + delete o; +} + +void tst_qqmlecmascript::qtbug_10696() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_10696.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + delete o; +} + +void tst_qqmlecmascript::qtbug_11606() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_11606.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QCOMPARE(o->property("test").toBool(), true); + delete o; +} + +void tst_qqmlecmascript::qtbug_11600() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_11600.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QCOMPARE(o->property("test").toBool(), true); + delete o; +} + +void tst_qqmlecmascript::qtbug_21864() +{ + QQmlComponent component(&engine, testFileUrl("qtbug_21864.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QCOMPARE(o->property("test").toBool(), true); + delete o; +} + +void tst_qqmlecmascript::rewriteMultiLineStrings() +{ + // QTBUG-23387 + QQmlComponent component(&engine, testFileUrl("rewriteMultiLineStrings.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QTRY_COMPARE(o->property("test").toBool(), true); + delete o; +} + +void tst_qqmlecmascript::qobjectConnectionListExceptionHandling() +{ + // QTBUG-23375 + 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 != 0); + QCOMPARE(o->property("test").toBool(), true); + delete o; +} + +// Reading and writing non-scriptable properties should fail +void tst_qqmlecmascript::nonscriptable() +{ + QQmlComponent component(&engine, testFileUrl("nonscriptable.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + 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() +{ + QQmlComponent component(&engine, testFileUrl("deleteLater.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QCOMPARE(o->property("test").toBool(), true); + delete o; +} + +void tst_qqmlecmascript::in() +{ + QQmlComponent component(&engine, testFileUrl("in.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QCOMPARE(o->property("test1").toBool(), true); + QCOMPARE(o->property("test2").toBool(), true); + delete o; +} + +void tst_qqmlecmascript::typeOf() +{ + QQmlComponent component(&engine, testFileUrl("typeOf.qml")); + + // These warnings should not happen once QTBUG-21864 is fixed + QString warning1 = component.url().toString() + QLatin1String(":16: Error: Cannot assign [undefined] to QString"); + QString warning2 = component.url().resolved(QUrl("typeOf.js")).toString() + QLatin1String(":1: ReferenceError: Can't find variable: a"); + + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1)); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2)); + + QObject *o = component.create(); + QVERIFY(o != 0); + + QEXPECT_FAIL("", "QTBUG-21864", Abort); + 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::sharedAttachedObject() +{ + QQmlComponent component(&engine, testFileUrl("sharedAttachedObject.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + QCOMPARE(o->property("test1").toBool(), true); + QCOMPARE(o->property("test2").toBool(), true); + delete o; +} + +// QTBUG-13999 +void tst_qqmlecmascript::objectName() +{ + QQmlComponent component(&engine, testFileUrl("objectName.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + 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() +{ + QQmlComponent component(&engine, testFileUrl("writeRemovesBinding.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test").toBool(), true); + + delete o; +} + +// Test bindings assigned to alias properties actually assign to the alias' target +void tst_qqmlecmascript::aliasBindingsAssignCorrectly() +{ + QQmlComponent component(&engine, testFileUrl("aliasBindingsAssignCorrectly.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + 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() +{ + { + QQmlComponent component(&engine, testFileUrl("aliasBindingsOverrideTarget.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test").toBool(), true); + + delete o; + } + + { + QQmlComponent component(&engine, testFileUrl("aliasBindingsOverrideTarget.2.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test").toBool(), true); + + delete o; + } + + { + QQmlComponent component(&engine, testFileUrl("aliasBindingsOverrideTarget.3.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + 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() +{ + { + QQmlComponent component(&engine, testFileUrl("aliasWritesOverrideBindings.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test").toBool(), true); + + delete o; + } + + { + QQmlComponent component(&engine, testFileUrl("aliasWritesOverrideBindings.2.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test").toBool(), true); + + delete o; + } + + { + QQmlComponent component(&engine, testFileUrl("aliasWritesOverrideBindings.3.qml")); + QObject *o = component.create(); + QVERIFY(o != 0); + + QCOMPARE(o->property("test").toBool(), true); + + delete o; + } +} + +// Allow an alais to a composite element +// QTBUG-20200 +void tst_qqmlecmascript::aliasToCompositeElement() +{ + QQmlComponent component(&engine, testFileUrl("aliasToCompositeElement.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + + delete object; +} + +void tst_qqmlecmascript::qtbug_20344() +{ + 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 != 0); + + delete object; +} + +void tst_qqmlecmascript::revisionErrors() +{ + { + QQmlComponent component(&engine, testFileUrl("metaobjectRevisionErrors.qml")); + QString url = component.url().toString(); + + QString warning1 = url + ":8: ReferenceError: Can't find variable: prop2"; + QString warning2 = url + ":11: ReferenceError: Can't find variable: prop2"; + QString warning3 = url + ":13: ReferenceError: Can't find variable: method2"; + + 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 != 0); + 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: Can't find variable: prop2"; + QString warning2 = url + ":14: ReferenceError: Can't find variable: prop2"; + QString warning3 = url + ":10: ReferenceError: Can't find variable: prop4"; + QString warning4 = url + ":16: ReferenceError: Can't find variable: prop4"; + QString warning5 = url + ":20: ReferenceError: Can't find variable: method2"; + + 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 != 0); + 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: Can't find variable: methodD"; + QString warning2 = url + ":10: ReferenceError: Can't find variable: propD"; + QString warning3 = url + ":20: ReferenceError: Can't find variable: propD"; + 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 != 0); + delete object; + } +} + +void tst_qqmlecmascript::revision() +{ + { + QQmlComponent component(&engine, testFileUrl("metaobjectRevision.qml")); + QString url = component.url().toString(); + + MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create()); + QVERIFY(object != 0); + delete object; + } + { + QQmlComponent component(&engine, testFileUrl("metaobjectRevision2.qml")); + QString url = component.url().toString(); + + MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create()); + QVERIFY(object != 0); + delete object; + } + { + QQmlComponent component(&engine, testFileUrl("metaobjectRevision3.qml")); + QString url = component.url().toString(); + + MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(component.create()); + QVERIFY(object != 0); + delete object; + } + // Test that non-root classes can resolve revisioned methods + { + QQmlComponent component(&engine, testFileUrl("metaobjectRevision4.qml")); + + QObject *object = component.create(); + QVERIFY(object != 0); + QCOMPARE(object->property("test").toReal(), 11.); + delete object; + } +} + +void tst_qqmlecmascript::realToInt() +{ + QQmlComponent component(&engine, testFileUrl("realToInt.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + + QMetaObject::invokeMethod(object, "test1"); + QCOMPARE(object->value(), int(4)); + QMetaObject::invokeMethod(object, "test2"); + QCOMPARE(object->value(), int(8)); +} + +void tst_qqmlecmascript::urlProperty() +{ + { + QQmlComponent component(&engine, testFileUrl("urlProperty.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + 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() +{ + { + QQmlComponent component(&engine, testFileUrl("urlProperty.2.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(object != 0); + object->setStringProperty("http://qt-project.org"); + QUrl encoded; + encoded.setEncodedUrl("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() +{ + { + QQmlComponent component(&engine, testFileUrl("urlListProperty.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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 != 0 && msco2 != 0 && msco3 != 0 && msco4 != 0); + QUrl encoded; + encoded.setEncodedUrl("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() +{ + QQmlComponent component(&engine, testFileUrl("dynamicString.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + 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::automaticSemicolon() +{ + QQmlComponent component(&engine, testFileUrl("automaticSemicolon.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); +} + +void tst_qqmlecmascript::unaryExpression() +{ + QQmlComponent component(&engine, testFileUrl("unaryExpression.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); +} + +// Makes sure that a binding isn't double re-evaluated when it depends on the same variable twice +void tst_qqmlecmascript::doubleEvaluate() +{ + QQmlComponent component(&engine, testFileUrl("doubleEvaluate.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + WriteCounter *wc = qobject_cast<WriteCounter *>(object); + QVERIFY(wc != 0); + QCOMPARE(wc->count(), 1); + + wc->setProperty("x", 9); + + QCOMPARE(wc->count(), 2); + + delete object; +} + +static QStringList messages; +static void captureMsgHandler(QtMsgType, const char *msg) +{ + messages.append(QLatin1String(msg)); +} + +void tst_qqmlecmascript::nonNotifyable() +{ + QV4Compiler::enableV4(false); + QQmlComponent component(&engine, testFileUrl("nonNotifyable.qml")); + QV4Compiler::enableV4(true); + + QtMsgHandler old = qInstallMsgHandler(captureMsgHandler); + messages.clear(); + QObject *object = component.create(); + qInstallMsgHandler(old); + + QVERIFY(object != 0); + + QString expected1 = QLatin1String("QQmlExpression: Expression ") + + component.url().toString() + + QLatin1String(":5 depends on non-NOTIFYable properties:"); + QString expected2 = QLatin1String(" ") + + QLatin1String(object->metaObject()->className()) + + QLatin1String("::value"); + + QCOMPARE(messages.length(), 2); + QCOMPARE(messages.at(0), expected1); + QCOMPARE(messages.at(1), expected2); + + delete object; +} + +void tst_qqmlecmascript::forInLoop() +{ + QQmlComponent component(&engine, testFileUrl("forInLoop.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + + QMetaObject::invokeMethod(object, "listProperty"); + + QStringList r = object->property("listResult").toString().split("|", QString::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() +{ + QQmlComponent component(&engine, testFileUrl("deleteWhileBindingRunning.qml")); + QObject *object = component.create(); + QVERIFY(object != 0); + delete object; +} + +void tst_qqmlecmascript::qtbug_22679() +{ + MyQmlObject object; + object.setStringProperty(QLatin1String("Please work correctly")); + 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 != 0); + 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"); + + QQmlComponent component(&engine, testFileUrl(fileName)); + QString url = component.url().toString(); + QString warning1 = url.left(url.length()-3) + QLatin1String("js:4: SyntaxError: Unexpected token )"); + QString warning2 = url + QLatin1String(":5: TypeError: Object [object Object] has no method 'func'"); + + qRegisterMetaType<QList<QQmlError> >("QList<QQmlError>"); + QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); + for (int x = 0; x < 3; ++x) { + warningsSpy.clear(); + // For libraries, only the first import attempt should produce a + // SyntaxError warning; subsequent component creation should not + // attempt to reload the script. + bool expectSyntaxError = !library || (x == 0); + if (expectSyntaxError) + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1)); + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2)); + QObject *object = component.create(); + QVERIFY(object != 0); + QCOMPARE(warningsSpy.count(), 1 + (expectSyntaxError?1:0)); + delete object; + } +} + + +void tst_qqmlecmascript::switchStatement() +{ + { + QQmlComponent component(&engine, testFileUrl("switchStatement.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + // `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")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + // `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")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + // `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: Unable to assign [undefined] to int"; + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + // `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")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + // `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")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + // `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() +{ + { + QQmlComponent component(&engine, testFileUrl("withStatement.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + QCOMPARE(object->value(), 123); + } +} + +void tst_qqmlecmascript::tryStatement() +{ + { + QQmlComponent component(&engine, testFileUrl("tryStatement.1.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + QCOMPARE(object->value(), 123); + } + + { + QQmlComponent component(&engine, testFileUrl("tryStatement.2.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + QCOMPARE(object->value(), 321); + } + + { + QQmlComponent component(&engine, testFileUrl("tryStatement.3.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + QCOMPARE(object->value(), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("tryStatement.4.qml")); + MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); + QVERIFY(object != 0); + + QCOMPARE(object->value(), 1); + } +} + +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 != 0); + QVERIFY(object->property("result").value<bool>() == true); + + delete object; + } +} + +QTEST_MAIN(tst_qqmlecmascript) + +#include "tst_qqmlecmascript.moc" |