/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include #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 assignDate_data(); void assignDate(); 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 componentCreation_data(); void componentCreation(); 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 ownershipRootObject(); void ownershipConsistency(); 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 qtbug_24448(); void sharedAttachedObject(); void objectName(); void writeRemovesBinding(); void aliasBindingsAssignCorrectly(); void aliasBindingsOverrideTarget(); void aliasWritesOverrideBindings(); void aliasToCompositeElement(); void realToInt(); void urlProperty(); void urlPropertyWithEncoding(); void urlListPropertyWithEncoding(); void dynamicString(); void include(); void 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 realTypePrecision(); void automaticSemicolon(); void unaryExpression(); void switchStatement(); void withStatement(); void tryStatement(); private: static void propertyVarWeakRefCallback(v8::Persistent object, void* parameter); QQmlEngine engine; }; void tst_qqmlecmascript::initTestCase() { QQmlDataTest::initTestCase(); registerTypes(); QString dataDir(dataDirectory() + QLatin1Char('/') + QLatin1String("lib")); engine.addImportPath(dataDir); } void tst_qqmlecmascript::assignBasicTypes() { { QQmlComponent component(&engine, testFileUrl("assignBasicTypes.qml")); MyTypeObject *object = qobject_cast(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), Qt::UTC)); QCOMPARE(object->pointProperty(), QPoint(99,13)); QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3)); QCOMPARE(object->sizeProperty(), QSize(99, 13)); QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2)); QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200)); QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99)); QCOMPARE(object->boolProperty(), true); QCOMPARE(object->variantProperty(), QVariant("Hello World!")); QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2)); QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml"))); delete object; } { QQmlComponent component(&engine, testFileUrl("assignBasicTypes.2.qml")); MyTypeObject *object = qobject_cast(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), Qt::UTC)); QCOMPARE(object->pointProperty(), QPoint(99,13)); QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3)); QCOMPARE(object->sizeProperty(), QSize(99, 13)); QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2)); QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200)); QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99)); QCOMPARE(object->boolProperty(), true); QCOMPARE(object->variantProperty(), QVariant("Hello World!")); QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2)); QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml"))); delete object; } } void tst_qqmlecmascript::assignDate_data() { QTest::addColumn("source"); QTest::newRow("Component.onComplete JS") << testFileUrl("assignDate.qml"); QTest::newRow("Binding JS") << testFileUrl("assignDate.2.qml"); QTest::newRow("Binding UTC") << testFileUrl("assignDate.3.qml"); QTest::newRow("Binding JS UTC") << testFileUrl("assignDate.4.qml"); QTest::newRow("Binding UTC+2") << testFileUrl("assignDate.5.qml"); QTest::newRow("Binding JS UTC+2 ") << testFileUrl("assignDate.6.qml"); } void tst_qqmlecmascript::assignDate() { QFETCH(QUrl, source); QQmlComponent component(&engine, source); QScopedPointer obj(component.create()); MyTypeObject *object = qobject_cast(obj.data()); QVERIFY(object != 0); QCOMPARE(object->dateProperty(), QDate(1982, 11, 25)); QCOMPARE(object->dateTimeProperty(), QDateTime(QDate(2009, 5, 12), QTime(13, 22, 1), Qt::UTC)); } void tst_qqmlecmascript::idShortcutInvalidates() { { QQmlComponent component(&engine, testFileUrl("idShortcutInvalidates.qml")); MyQmlObject *object = qobject_cast(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(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(component.create()); QVERIFY(object != 0); QCOMPARE(object->stringProperty(), QLatin1String("pass")); delete object; } { QQmlComponent component(&engine, testFileUrl("boolPropertiesEvaluateAsBool.2.qml")); MyQmlObject *object = qobject_cast(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(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(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; } { QQmlComponent component(&engine, testFileUrl("signalAssignment.3.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->string(), QString()); emit object->unnamedArgumentSignal(19, 10.25, "Hello world!"); QEXPECT_FAIL("", "QTBUG-24481", Continue); QCOMPARE(object->string(), QString("pass 19 Hello world!")); delete object; } { QQmlComponent component(&engine, testFileUrl("signalAssignment.4.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->string(), QString()); emit object->signalWithGlobalName(19); QCOMPARE(object->string(), QString("pass 5")); delete object; } } void tst_qqmlecmascript::methods() { { QQmlComponent component(&engine, testFileUrl("methods.1.qml")); MyQmlObject *object = qobject_cast(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(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("expression"); QTest::addColumn("result"); QTest::addColumn("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 list = qvariant_cast >(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(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(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(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(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(qvariant_cast(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(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(qmlAttachedPropertiesObject(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; } // Enums as literals { QQmlComponent component(&engine, testFileUrl("enums.3.qml")); QObject *object = component.create(); QVERIFY(object != 0); // check the values are what we expect QCOMPARE(object->property("a").toInt(), 4); QCOMPARE(object->property("b").toInt(), 5); QCOMPARE(object->property("c").toInt(), 9); QCOMPARE(object->property("d").toInt(), 13); QCOMPARE(object->property("e").toInt(), 2); QCOMPARE(object->property("f").toInt(), 3); QCOMPARE(object->property("h").toInt(), 2); QCOMPARE(object->property("i").toInt(), 3); // count of change signals QCOMPARE(object->property("ac").toInt(), 0); QCOMPARE(object->property("bc").toInt(), 0); QCOMPARE(object->property("cc").toInt(), 0); QCOMPARE(object->property("dc").toInt(), 0); QCOMPARE(object->property("ec").toInt(), 0); QCOMPARE(object->property("fc").toInt(), 0); QCOMPARE(object->property("hc").toInt(), 1); // namespace -> binding QCOMPARE(object->property("ic").toInt(), 1); // namespace -> binding delete object; } } void tst_qqmlecmascript::valueTypeFunctions() { QQmlComponent component(&engine, testFileUrl("valueTypeFunctions.qml")); MyTypeObject *obj = qobject_cast(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(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(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(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(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(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(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(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(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() != 0); QCOMPARE(object->property("aliasIsUndefined"), QVariant(false)); QMetaObject::invokeMethod(object, "resetAliased"); QVERIFY(object->property("sourceComponentAlias").value() == 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() != 0); QCOMPARE(object->property("loaderSourceComponentIsUndefined"), QVariant(false)); QMetaObject::invokeMethod(object, "resetAlias"); QVERIFY(object->property("sourceComponentAlias").value() == 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() != 0); QCOMPARE(object->property("loaderOneSourceComponentIsUndefined"), QVariant(false)); QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined"), QVariant(false)); QMetaObject::invokeMethod(object, "resetAlias"); QVERIFY(object->property("sourceComponentAlias").value() == 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() != 0); QObject *loader = object->findChild("loader"); QVERIFY(loader != 0); delete loader; QVERIFY(object->property("sourceComponentAlias").value() == 0); // deletion should have caused value unset. QMetaObject::invokeMethod(object, "resetAlias"); // shouldn't crash. QVERIFY(object->property("sourceComponentAlias").value() == 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() == 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() == 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(), 12); QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false)); QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData()); QMetaObject::invokeMethod(object, "resetAlias"); QCOMPARE(object->property("intAlias").value(), 12); QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false)); delete object; } void tst_qqmlecmascript::componentCreation_data() { QTest::addColumn("method"); QTest::addColumn("creationError"); QTest::addColumn("createdParent"); QTest::newRow("url") << "url" << "" << ""; QTest::newRow("urlMode") << "urlMode" << "" << ""; QTest::newRow("urlParent") << "urlParent" << "" << "obj"; QTest::newRow("urlNullParent") << "urlNullParent" << "" << "null"; QTest::newRow("urlModeParent") << "urlModeParent" << "" << "obj"; QTest::newRow("urlModeNullParent") << "urlModeNullParent" << "" << "null"; QTest::newRow("invalidSecondArg") << "invalidSecondArg" << ":40: Error: Qt.createComponent(): Invalid arguments" << ""; QTest::newRow("invalidThirdArg") << "invalidThirdArg" << ":45: Error: Qt.createComponent(): Invalid parent object" << ""; QTest::newRow("invalidMode") << "invalidMode" << ":50: Error: Qt.createComponent(): Invalid arguments" << ""; } /* Test using createComponent to dynamically generate a component. */ void tst_qqmlecmascript::componentCreation() { QFETCH(QString, method); QFETCH(QString, creationError); QFETCH(QString, createdParent); QUrl testUrl(testFileUrl("componentCreation.qml")); if (!creationError.isEmpty()) { QString warning = testUrl.toString() + creationError; QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); } QQmlComponent component(&engine, testUrl); MyTypeObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QMetaObject::invokeMethod(object, method.toUtf8()); QQmlComponent *created = object->componentProperty(); if (creationError.isEmpty()) { QVERIFY(created); QObject *expectedParent; if (createdParent.isEmpty()) { // For now, the parent should be the engine; this will change for QTBUG-24841 expectedParent = &engine; } else if (createdParent == QLatin1String("obj")) { expectedParent = object; } else if (createdParent == QLatin1String("null")) { expectedParent = 0; } QCOMPARE(created->parent(), expectedParent); } } void tst_qqmlecmascript::dynamicCreation_data() { QTest::addColumn("method"); QTest::addColumn("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(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 object = qobject_cast(component.create()); QVERIFY(object != 0); QQmlGuard 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(o->property("objectProperty")) == 0); QMetaObject::invokeMethod(o, "create"); QVERIFY(qvariant_cast(o->property("objectProperty")) != 0); QMetaObject::invokeMethod(o, "destroy"); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); QCoreApplication::processEvents(); QVERIFY(qvariant_cast(o->property("objectProperty")) == 0); delete o; } { // QTBUG-23451 QQmlGuard createdQmlObject = 0; QQmlComponent component(&engine, testFileUrl("dynamicDeletion.3.qml")); QObject *o = component.create(); QVERIFY(o != 0); QVERIFY(qvariant_cast(o->property("objectProperty")) == 0); QMetaObject::invokeMethod(o, "create"); createdQmlObject = qvariant_cast(o->property("objectProperty")); QVERIFY(createdQmlObject); QMetaObject::invokeMethod(o, "destroy"); QVERIFY(qvariant_cast(o->property("test")) == false); for (int ii = 0; createdQmlObject && ii < 50; ++ii) { // After 5 seconds we should give up QTest::qWait(100); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); QCoreApplication::processEvents(); } QVERIFY(qvariant_cast(o->property("objectProperty")) == 0); QVERIFY(qvariant_cast(o->property("test")) == true); delete o; } } /* tests that id.toString() works */ void tst_qqmlecmascript::objectToString() { QQmlComponent component(&engine, testFileUrl("qmlToString.qml")); MyQmlObject *object = qobject_cast(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() == true); QMetaObject::invokeMethod(object, "testHasOwnPropertyFailure"); QVERIFY(object->property("result").value() == false); // now test other types in QML QObject *child = object->findChild("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: a is not defined"; QString warning3 = url.left(url.length() - 3) + "js:4: Error: Invalid write to global property \"a\""; QString warning4 = url + ":13: ReferenceError: a is not defined"; QString warning5 = url + ":11: ReferenceError: a is not defined"; QString warning6 = url + ":10: 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(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(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(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(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(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(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")); 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(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(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("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(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(component.create()); QVERIFY(!object); QCOMPARE(component.errorString(), err); } } static inline bool evaluate_error(QV8Engine *engine, v8::Handle o, const char *source) { QString functionSource = QLatin1String("(function(object) { return ") + QLatin1String(source) + QLatin1String(" })"); v8::TryCatch tc; v8::Local program = v8::Script::Compile(engine->toString(functionSource)); if (tc.HasCaught()) return false; v8::Handle function = v8::Handle::Cast(program->Run()); if (function.IsEmpty()) return false; v8::Handle args[] = { o }; function->Call(engine->global(), 1, args); return tc.HasCaught(); } static inline bool evaluate_value(QV8Engine *engine, v8::Handle o, const char *source, v8::Handle result) { QString functionSource = QLatin1String("(function(object) { return ") + QLatin1String(source) + QLatin1String(" })"); v8::TryCatch tc; v8::Local program = v8::Script::Compile(engine->toString(functionSource)); if (tc.HasCaught()) return false; v8::Handle function = v8::Handle::Cast(program->Run()); if (function.IsEmpty()) return false; v8::Handle args[] = { o }; v8::Handle value = function->Call(engine->global(), 1, args); if (tc.HasCaught()) return false; return value->StrictEquals(result); } static inline v8::Handle evaluate(QV8Engine *engine, v8::Handle o, const char *source) { QString functionSource = QLatin1String("(function(object) { return ") + QLatin1String(source) + QLatin1String(" })"); v8::TryCatch tc; v8::Local program = v8::Script::Compile(engine->toString(functionSource)); if (tc.HasCaught()) return v8::Handle(); v8::Handle function = v8::Handle::Cast(program->Run()); if (function.IsEmpty()) return v8::Handle(); v8::Handle args[] = { o }; v8::Handle value = function->Call(engine->global(), 1, args); if (tc.HasCaught()) return v8::Handle(); 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 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 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 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 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(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(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(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(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(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(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(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(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(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()); QVERIFY(qvariant_cast(v).object() == &container); delete object; } // QTBUG-16316 Q_DECLARE_METATYPE(QQmlListProperty) void tst_qqmlecmascript::listAssignment() { QQmlComponent component(&engine, testFileUrl("listAssignment.qml")); QObject *obj = component.create(); QCOMPARE(obj->property("list1length").toInt(), 2); QQmlListProperty list1 = obj->property("list1").value >(); QQmlListProperty list2 = obj->property("list2").value >(); 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(qmlAttachedPropertiesObject(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(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(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(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(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(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(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(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(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(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(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 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 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); } //the return value from getObject will be JS ownership, //unless strong Cpp ownership has been set class OwnershipChangingObject : public QObject { Q_OBJECT public: OwnershipChangingObject(): object(0) { } QPointer object; public slots: QObject *getObject() { return object; } void setObject(QObject *obj) { object = obj; } }; void tst_qqmlecmascript::ownershipRootObject() { OwnershipChangingObject own; QQmlContext *context = new QQmlContext(engine.rootContext()); context->setContextObject(&own); QQmlComponent component(&engine, testFileUrl("ownershipRootObject.qml")); QQmlGuard object = component.create(context); QVERIFY(object); engine.collectGarbage(); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); QCoreApplication::processEvents(); QVERIFY(own.object != 0); delete context; delete object; } void tst_qqmlecmascript::ownershipConsistency() { OwnershipChangingObject own; QQmlContext *context = new QQmlContext(engine.rootContext()); context->setContextObject(&own); QString expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":19: Error: Invalid attempt to destroy() an indestructible object"); QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":15: Error: Invalid attempt to destroy() an indestructible object"); QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":6: Error: Invalid attempt to destroy() an indestructible object"); QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. expectedWarning = testFileUrl("ownershipConsistency.qml").toString() + QLatin1String(":10: Error: Invalid attempt to destroy() an indestructible object"); QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. QQmlComponent component(&engine, testFileUrl("ownershipConsistency.qml")); QQmlGuard object = component.create(context); QVERIFY(object); engine.collectGarbage(); QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); QCoreApplication::processEvents(); QVERIFY(own.object != 0); delete context; delete object; } class QListQObjectMethodsObject : public QObject { Q_OBJECT public: QListQObjectMethodsObject() { m_objects.append(new MyQmlObject()); m_objects.append(new MyQmlObject()); } ~QListQObjectMethodsObject() { qDeleteAll(m_objects); } public slots: QList getObjects() { return m_objects; } private: QList m_objects; }; // Tests that returning a QList 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(object->property("test24")), QColor(0x11,0x22,0x33)); QCOMPARE(qvariant_cast(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(component.create()); QVERIFY(object != 0); MyQmlObject::MyType type; type.value = 0x8971123; emit object->signalWithUnknownType(type); MyQmlObject::MyType result = qvariant_cast(object->variant()); QCOMPARE(result.value, type.value); delete object; } void tst_qqmlecmascript::signalWithJSValueInVariant_data() { QTest::addColumn("expression"); QTest::addColumn("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 object(qobject_cast(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 object(qobject_cast(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 object(qobject_cast(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("testfile"); QTest::addColumn("errorMessage"); QTest::addColumn("warningMessages"); QTest::addColumn("readProperties"); QTest::addColumn("readExpectedValues"); QTest::addColumn("writeProperties"); QTest::addColumn("writeValues"); QTest::addColumn("readBackProperties"); QTest::addColumn("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(testFileUrl("moduleapi/qobjectModuleApiWriting.qml").toString() + QLatin1String(":15: Error: Cannot assign to read-only property \"qobjectTestProperty\""))) << (QStringList() << "readOnlyProperty" << "writableProperty" << "writableFinalProperty") << (QVariantList() << 20 << 50 << 10) << (QStringList() << "firstProperty" << "secondProperty") << (QVariantList() << 30 << 30) << (QStringList() << "readOnlyProperty" << "writableProperty" << "writableFinalProperty") << (QVariantList() << 20 << 30 << 30); QTest::newRow("script, writing + readonly constraints") << testFileUrl("moduleapi/scriptModuleApiWriting.qml") << QString() << (QStringList() << QString(testFileUrl("moduleapi/scriptModuleApiWriting.qml").toString() + 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("testfile"); QTest::addColumn("errorMessage"); QTest::addColumn("warningMessages"); QTest::addColumn("propertyNames"); QTest::addColumn("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(testFileUrl("jsimportfail/failOne.qml").toString() + QLatin1String(":6: TypeError: Cannot call method 'greetingString' of undefined"))) << (QStringList() << QLatin1String("importScriptFunctionValue")) << (QVariantList() << QVariant(QString())); QTest::newRow("javascript imports in an import should be private to the import scope") << testFileUrl("jsimportfail/failTwo.qml") << QString() << (QStringList() << QString(testFileUrl("jsimportfail/failTwo.qml").toString() + QLatin1String(":6: ReferenceError: ImportOneJs is not defined"))) << (QStringList() << QLatin1String("importScriptFunctionValue")) << (QVariantList() << QVariant(QString())); QTest::newRow("module imports in an import should be private to the import scope") << testFileUrl("jsimportfail/failThree.qml") << QString() << (QStringList() << QString(testFileUrl("jsimportfail/failThree.qml").toString() + QLatin1String(":7: TypeError: Cannot read property 'JsQtTest' of undefined"))) << (QStringList() << QLatin1String("importedModuleAttachedPropertyValue")) << (QVariantList() << QVariant(false)); QTest::newRow("typenames in an import should be private to the import scope") << testFileUrl("jsimportfail/failFour.qml") << QString() << (QStringList() << QString(testFileUrl("jsimportfail/failFour.qml").toString() + QLatin1String(":6: ReferenceError: JsQtTest is not defined"))) << (QStringList() << QLatin1String("importedModuleEnumValue")) << (QVariantList() << QVariant(0)); QTest::newRow("import with imports has it's own activation scope") << testFileUrl("jsimportfail/failFive.qml") << QString() << (QStringList() << QString(testFileUrl("jsimportfail/importWithImports.js").toString() + QLatin1String(":8: ReferenceError: Component is not defined"))) << (QStringList() << QLatin1String("componentError")) << (QVariantList() << QVariant(0)); QTest::newRow("import pragma library script") << testFileUrl("jsimport/testImportPragmaLibrary.qml") << 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(testFileUrl("jsimportfail/importPragmaLibrary.js").toString() + QLatin1String(":6: ReferenceError: Component is not defined"))) << (QStringList() << QLatin1String("testValue")) << (QVariantList() << QVariant(0)); QTest::newRow("import pragma library script which has an import") << testFileUrl("jsimport/testImportPragmaLibraryWithImports.qml") << 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)); QTest::newRow("import module api into js import") << testFileUrl("jsimport/testImportModuleApi.qml") << QString() << QStringList() << (QStringList() << QLatin1String("testValue")) << (QVariantList() << QVariant(20)); QTest::newRow("import module which exports a script") << testFileUrl("jsimport/testJsImport.qml") << QString() << QStringList() << (QStringList() << QLatin1String("importedScriptStringValue") << QLatin1String("renamedScriptStringValue") << QLatin1String("reimportedScriptStringValue")) << (QVariantList() << QVariant(QString("Hello")) << QVariant(QString("Hello")) << QVariant(QString("Hello"))); } 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("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(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(), origPixmap); eo = qobject_cast(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(), origPixmap); eo = qobject_cast(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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("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(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(), origPixmap); eo = qobject_cast(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(), origPixmap); eo = qobject_cast(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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(QQmlProperty::read(object, "a").value()); 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("qmlFile"); QTest::addColumn("readDetachStatus"); QTest::addColumn("expectedDetachStatus"); QTest::addColumn("propertyNames"); QTest::addColumn("expectedValidity"); QTest::addColumn("expectedValues"); QTest::addColumn("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() << true) << (QList() << 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() << true) << (QList() << 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() << false) << (QList() << 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() << true) << (QList() << 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() << true) << (QList() << 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() << true) << (QList() << 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() << QList() << QStringList(); QTest::newRow("var: import with binding without explicit preserve") << testFileUrl("scarceResourceCopyImportNoBinding.var.qml") << false << false << (QStringList() << QLatin1String("scarceResourceCopy")) << (QList() << false) // will have been released prior to evaluation of binding. << (QList() << 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() << 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(true)) << QStringList(); QTest::newRow("var: import with different js objects") << testFileUrl("scarceResourceCopyImportDifferent.var.qml") << false << false << (QStringList() << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo") << QLatin1String("arePropertiesEqual")) << (QList() << false << true << true) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object. << (QList() << 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() << true << false) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object. << (QList() << 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() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object. << (QList() << 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() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object. << (QList() << 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() << true) << (QList() << 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() << true) << (QList() << 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() << false) << (QList() << 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() << true) << (QList() << 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() << true) << (QList() << 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() << true) << (QList() << 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() << QList() << QStringList(); QTest::newRow("variant: import with binding without explicit preserve") << testFileUrl("scarceResourceCopyImportNoBinding.variant.qml") << false << false << (QStringList() << QLatin1String("scarceResourceCopy")) << (QList() << false) // will have been released prior to evaluation of binding. << (QList() << 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() << true << true << false) // since property variant = variant copy, releasing the provider's resource does not invalidate previously assigned copies. << (QList() << 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(), value.value()); } } if (readDetachStatus) { eo = qobject_cast(QQmlProperty::read(object, "a").value()); 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("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 referencedObject = object->property("object").value(); 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 referencedObject = object->property("object").value(); QVERIFY(!referencedObject.isNull()); gc(engine); QVERIFY(!referencedObject.isNull()); QMetaObject::invokeMethod(object, "runTest"); gc(engine); QVERIFY(referencedObject.isNull()); delete object; } // Garbage collection cannot result in attempted dereference of empty handle { QQmlComponent component(&engine, testFileUrl("propertyVarOwnership.5.qml")); QObject *object = component.create(); QVERIFY(object != 0); QMetaObject::invokeMethod(object, "runTest"); QCOMPARE(object->property("test").toBool(), true); delete object; } } void tst_qqmlecmascript::propertyVarImplicitOwnership() { // The childObject has a reference to a different QObject. We want to ensure // that the different item will not be cleaned up until required. IE, the childObject // has implicit ownership of the constructed QObject. 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(); QVERIFY(rootObject != 0); QObject *childObject = rootObject->findChild("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 qobjectGuard(childObject->property("vp").value()); // 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 *text = rect->findChild("textOne"); QObject *text2 = rect->findChild("textTwo"); QWeakPointer rectGuard(rect); QWeakPointer textGuard(text); QWeakPointer 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(); QWeakPointer 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 *text = rect->findChild("textOne"); QObject *text2 = rect->findChild("textTwo"); QWeakPointer rectGuard(rect); QWeakPointer textGuard(text); QWeakPointer 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(); QWeakPointer 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(); 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(); QVERIFY(rootObject != 0); QObject *childObject = rootObject->findChild("text"); QVERIFY(childObject != 0); QWeakPointer rootObjectTracker(rootObject); QVERIFY(!rootObjectTracker.isNull()); QWeakPointer 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 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()->property("vp").value()->property("vp").value()->property("vp").value()->property("vp").value(); QObject *ico5 = object->property("varProperty").value()->property("inheritanceVarProperty").value()->property("vp").value()->property("vp").value()->property("vp").value()->property("vp").value(); QQmlVMEMetaObject *icovmemo = ((QQmlVMEMetaObject *)(ico5->metaObject())); QQmlVMEMetaObject *ccovmemo = ((QQmlVMEMetaObject *)(cco5->metaObject())); v8::Persistent icoCanaryHandle; v8::Persistent 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(); QVERIFY(rootObject != 0); QObject *childObject = rootObject->findChild("text"); QVERIFY(childObject != 0); QCOMPARE(rootObject->property("rectCanary").toInt(), 5); QCOMPARE(childObject->property("textCanary").toInt(), 10); v8::Persistent 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(), 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().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("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("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("crh"); QVERIFY(crh != 0); crh->setEngine(&hrmEngine); crh->setDtorCount(&dtorCount); QMetaObject::invokeMethod(object, "createReference"); CircularReferenceHandle *first = object->property("first").value(); CircularReferenceHandle *second = object->property("second").value(); 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("crh"); QVERIFY(crh != 0); crh->setEngine(&hrmEngine); crh->setDtorCount(&dtorCount); QMetaObject::invokeMethod(object, "circularReference"); CircularReferenceHandle *first = object->property("first").value(); CircularReferenceHandle *second = object->property("second").value(); 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("crh"); CircularReferenceHandle *crh2 = object2->findChild("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 *second1 = object1->property("second").value(); CircularReferenceHandle *first2 = object2->property("first").value(); CircularReferenceHandle *second2 = object2->property("second").value(); 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("crh"); CircularReferenceHandle *crh2 = object2->findChild("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 *second1 = object1->property("second").value(); CircularReferenceHandle *first2 = object2->property("first").value(); CircularReferenceHandle *second2 = object2->property("second").value(); 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("crh"); CircularReferenceHandle *crh2 = object2->findChild("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 *second1 = object1->property("second").value(); CircularReferenceHandle *first2 = object2->property("first").value(); CircularReferenceHandle *second2 = object2->property("second").value(); 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) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QList) 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("msco"); QVERIFY(seq != 0); QMetaObject::invokeMethod(object, "readSequences"); QList intList; intList << 1 << 2 << 3 << 4; QCOMPARE(object->property("intListLength").toInt(), intList.length()); QCOMPARE(object->property("intList").value >(), intList); QList qrealList; qrealList << 1.1 << 2.2 << 3.3 << 4.4; QCOMPARE(object->property("qrealListLength").toInt(), qrealList.length()); QCOMPARE(object->property("qrealList").value >(), qrealList); QList boolList; boolList << true << false << true << false; QCOMPARE(object->property("boolListLength").toInt(), boolList.length()); QCOMPARE(object->property("boolList").value >(), boolList); QList stringList; stringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth"); QCOMPARE(object->property("stringListLength").toInt(), stringList.length()); QCOMPARE(object->property("stringList").value >(), stringList); QList 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 >(), 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); 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 >(), intList); QQmlProperty seqProp2(seq, "intListProperty", &engine); QCOMPARE(seqProp2.read().value >(), 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("msco"); QVERIFY(seq != 0); // we haven't registered QList as a sequence type. QString warningOne = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'QList' 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 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' 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("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("msco"); QVERIFY(seq != 0); // we haven't registered QList 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 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"); QTest::ignoreMessage(QtWarningMsg, qPrintable(w1)); QTest::ignoreMessage(QtWarningMsg, qPrintable(w2)); QTest::ignoreMessage(QtWarningMsg, qPrintable(w3)); 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 intList; intList << 1 << 2 << 3 << 12 << 7; QCOMPARE(object->property("boundSequence").value >(), intList); QCOMPARE(object->property("boundElement").toInt(), intList.at(3)); QList intListTwo; intListTwo << 1 << 2 << 3 << 12 << 14; QCOMPARE(object->property("boundSequenceTwo").value >(), intListTwo); delete object; } { QUrl qmlFile = testFileUrl("sequenceConversion.bindings.error.qml"); QString warning = QString(QLatin1String("%1:17: Unable to assign QList to QList")).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(component.create()); QVERIFY(object != 0); QCOMPARE(object->intListProperty(), (QList() << 1 << 2)); QCOMPARE(object->qrealListProperty(), (QList() << 1.1 << 2.2)); QCOMPARE(object->boolListProperty(), (QList() << false << true)); QCOMPARE(object->urlListProperty(), (QList() << QUrl("http://www.example1.com") << QUrl("http://www.example2.com"))); QCOMPARE(object->stringListProperty(), (QList() << 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(component.create()); QVERIFY(object != 0); QCOMPARE(object->intListProperty(), (QList() << 1)); QCOMPARE(object->qrealListProperty(), (QList() << 1.1)); QCOMPARE(object->boolListProperty(), (QList() << false)); QCOMPARE(object->urlListProperty(), (QList() << QUrl("http://www.example1.com"))); QCOMPARE(object->stringListProperty(), (QList() << 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(component.create()); QVERIFY(object != 0); QCOMPARE(object->intListProperty(), (QList() << 1)); QCOMPARE(object->qrealListProperty(), (QList() << 1.1)); QCOMPARE(object->boolListProperty(), (QList() << false)); QCOMPARE(object->urlListProperty(), (QList() << 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(component.create()); QVERIFY(object != 0); QCOMPARE(object->intListProperty(), (QList() << 1 << 2)); QCOMPARE(object->qrealListProperty(), (QList() << 1.1 << 2.2)); QCOMPARE(object->boolListProperty(), (QList() << false << true)); QCOMPARE(object->urlListProperty(), (QList() << QUrl("http://www.example1.com") << QUrl("http://www.example2.com"))); QCOMPARE(object->stringListProperty(), (QList() << 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(component.create()); QVERIFY(object != 0); QCOMPARE(object->intListProperty(), (QList() << 1)); QCOMPARE(object->qrealListProperty(), (QList() << 1.1)); QCOMPARE(object->boolListProperty(), (QList() << false)); QCOMPARE(object->urlListProperty(), (QList() << QUrl("http://www.example1.com"))); QCOMPARE(object->stringListProperty(), (QList() << 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(component.create()); QVERIFY(object != 0); QCOMPARE(object->intListProperty(), (QList() << 1)); QCOMPARE(object->qrealListProperty(), (QList() << 1.1)); QCOMPARE(object->boolListProperty(), (QList() << false)); QCOMPARE(object->urlListProperty(), (QList() << QUrl(testFileUrl("example.html")))); delete object; } // test QList 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(QLatin1String("msco1")); MySequenceConversionObject *msco2 = object->findChild(QLatin1String("msco2")); MySequenceConversionObject *msco3 = object->findChild(QLatin1String("msco3")); MySequenceConversionObject *msco4 = object->findChild(QLatin1String("msco4")); MySequenceConversionObject *msco5 = object->findChild(QLatin1String("msco5")); QVERIFY(msco1 != 0 && msco2 != 0 && msco3 != 0 && msco4 != 0 && msco5 != 0); QCOMPARE(msco1->urlListProperty(), (QList() << QUrl(testFileUrl("example.html")))); QCOMPARE(msco2->urlListProperty(), (QList() << QUrl(testFileUrl("example.html")))); QCOMPARE(msco3->urlListProperty(), (QList() << QUrl(testFileUrl("example.html")) << QUrl(testFileUrl("example2.html")))); QCOMPARE(msco4->urlListProperty(), (QList() << QUrl(testFileUrl("example.html")) << QUrl(testFileUrl("example2.html")))); QCOMPARE(msco5->urlListProperty(), (QList() << 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(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(o->property("object")); QVERIFY(nested != 0); QVERIFY(qvariant_cast(nested->property("nestedObject")) == o); delete nested; nested = qvariant_cast(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(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(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:25: Unable to assign a function to a property."; QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); MyQmlObject *o = qobject_cast(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(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("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(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:17: Unable to assign QString to int"; QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); o->setProperty("assignWrongType", true); warning = url + ":71:29: 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, ": 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; } { QQmlComponent component(&engine, testFileUrl("rewriteMultiLineStrings_crlf.1.qml")); QObject *o = component.create(); QVERIFY(o != 0); 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")); QObject *o = component.create(); QVERIFY(o != 0); QCOMPARE(o->property("test1").toString(), QLatin1String("undefined")); QCOMPARE(o->property("test2").toString(), QLatin1String("object")); QCOMPARE(o->property("test3").toString(), QLatin1String("number")); QCOMPARE(o->property("test4").toString(), QLatin1String("string")); QCOMPARE(o->property("test5").toString(), QLatin1String("function")); QCOMPARE(o->property("test6").toString(), QLatin1String("object")); QCOMPARE(o->property("test7").toString(), QLatin1String("undefined")); QCOMPARE(o->property("test8").toString(), QLatin1String("boolean")); QCOMPARE(o->property("test9").toString(), QLatin1String("object")); delete o; } void tst_qqmlecmascript::qtbug_24448() { QQmlComponent component(&engine, testFileUrl("qtbug_24448.qml")); QScopedPointer o(component.create()); QVERIFY(o != 0); QVERIFY(o->property("test").toBool()); } 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: prop2 is not defined"; QString warning2 = url + ":11: ReferenceError: prop2 is not defined"; QString warning3 = url + ":13: ReferenceError: method2 is not defined"; QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData()); MyRevisionedClass *object = qobject_cast(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: prop2 is not defined"; QString warning2 = url + ":14: ReferenceError: prop2 is not defined"; QString warning3 = url + ":10: ReferenceError: prop4 is not defined"; QString warning4 = url + ":16: ReferenceError: prop4 is not defined"; QString warning5 = url + ":20: ReferenceError: method2 is not defined"; QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning4.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning5.toLatin1().constData()); MyRevisionedClass *object = qobject_cast(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: methodD is not defined"; QString warning2 = url + ":10: ReferenceError: propD is not defined"; QString warning3 = url + ":20: ReferenceError: propD is not defined"; QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData()); QTest::ignoreMessage(QtWarningMsg, warning3.toLatin1().constData()); MyRevisionedClass *object = qobject_cast(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(component.create()); QVERIFY(object != 0); delete object; } { QQmlComponent component(&engine, testFileUrl("metaobjectRevision2.qml")); QString url = component.url().toString(); MyRevisionedClass *object = qobject_cast(component.create()); QVERIFY(object != 0); delete object; } { QQmlComponent component(&engine, testFileUrl("metaobjectRevision3.qml")); QString url = component.url().toString(); MyRevisionedClass *object = qobject_cast(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(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(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(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(QLatin1String("msco1")); MySequenceConversionObject *msco2 = object->findChild(QLatin1String("msco2")); MySequenceConversionObject *msco3 = object->findChild(QLatin1String("msco3")); MySequenceConversionObject *msco4 = object->findChild(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() << encoded)); QCOMPARE(msco2->urlListProperty(), (QList() << encoded)); QCOMPARE(msco3->urlListProperty(), (QList() << encoded << encoded)); QCOMPARE(msco4->urlListProperty(), (QList() << 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(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"); QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList))); QObject *o = component.create(); QVERIFY(o != 0); QCOMPARE(warningsSpy.count(), 0); delete o; } void tst_qqmlecmascript::qtbug_22843_data() { QTest::addColumn("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"); QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList))); 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(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(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(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(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(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(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(component.create()); QVERIFY(object != 0); QCOMPARE(object->value(), 123); } } void tst_qqmlecmascript::tryStatement() { { QQmlComponent component(&engine, testFileUrl("tryStatement.1.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->value(), 123); } { QQmlComponent component(&engine, testFileUrl("tryStatement.2.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->value(), 321); } { QQmlComponent component(&engine, testFileUrl("tryStatement.3.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->value(), 1); } { QQmlComponent component(&engine, testFileUrl("tryStatement.4.qml")); MyQmlObject *object = qobject_cast(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() == true); delete object; } } void tst_qqmlecmascript::realTypePrecision() { // Properties and signal parameters of type real should have double precision. QQmlComponent component(&engine, testFileUrl("realTypePrecision.qml")); QScopedPointer object(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("test").toDouble(), 1234567890.); QCOMPARE(object->property("test2").toDouble(), 1234567890.); QCOMPARE(object->property("test3").toDouble(), 1234567890.); QCOMPARE(object->property("test4").toDouble(), 1234567890.); QCOMPARE(object->property("test5").toDouble(), 1234567890.); QCOMPARE(object->property("test6").toDouble(), 1234567890.*2); } QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc"