** $QT_END_LICENSE$
**
****************************************************************************/
-#include <qtest.h>
+#include <QtTest/QtTest>
#include <QtDeclarative/qdeclarativecomponent.h>
#include <QtDeclarative/qdeclarativeengine.h>
#include <QtDeclarative/qdeclarativeexpression.h>
#include <QtCore/qdir.h>
#include <QtCore/qnumeric.h>
#include <private/qdeclarativeengine_p.h>
+#include <private/qv8gccallback_p.h>
+#include <private/qdeclarativevmemetaobject_p.h>
+#include <private/qv4compiler_p.h>
#include "testtypes.h"
#include "testhttpserver.h"
-#include "../../../shared/util.h"
-
-#ifdef Q_OS_SYMBIAN
-// In Symbian OS test data is located in applications private dir
-#define SRCDIR "."
-#endif
+#include "../../shared/util.h"
/*
This test covers evaluation of ECMAScript expressions and bindings from within
*/
inline QUrl TEST_FILE(const QString &filename)
{
- QFileInfo fileInfo(__FILE__);
- return QUrl::fromLocalFile(fileInfo.absoluteDir().filePath("data/" + filename));
+ return QUrl::fromLocalFile(TESTDATA(filename));
}
inline QUrl TEST_FILE(const char *filename)
void signalWithJSValueInVariant_twoEngines();
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 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 qtbug_10696();
void qtbug_11606();
void qtbug_11600();
+ void qtbug_21864();
void nonscriptable();
void deleteLater();
void in();
+ void typeOf();
void sharedAttachedObject();
void objectName();
void writeRemovesBinding();
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 revisionErrors();
void revision();
void automaticSemicolon();
+ void unaryExpression();
+ void switchStatement();
+ void withStatement();
+ void tryStatement();
private:
+ static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
QDeclarativeEngine engine;
};
void tst_qdeclarativeecmascript::bindingLoop()
{
QDeclarativeComponent component(&engine, TEST_FILE("bindingLoop.qml"));
- QString warning = component.url().toString() + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\"";
+ 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);
QVERIFY(object->objectProperty() == 0);
QVERIFY(object->objectProperty2() == 0);
- QString warning = component.url().toString() + ":6: Unable to assign [undefined] to QObject* objectProperty";
+ QString warning = component.url().toString() + ":6: Unable to assign [undefined] to QObject*";
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
qmlExecuteDeferred(object);
{
QDeclarativeComponent component(&engine, TEST_FILE("enums.2.qml"));
- QString warning1 = component.url().toString() + ":5: Unable to assign [undefined] to int a";
- QString warning2 = component.url().toString() + ":6: Unable to assign [undefined] to int b";
+ 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));
{
QDeclarativeComponent component(&engine, TEST_FILE("nonExistentAttachedObject.qml"));
- QString warning = component.url().toString() + ":4: Unable to assign [undefined] to QString stringProperty";
+ QString warning = component.url().toString() + ":4: Unable to assign [undefined] to QString";
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
QObject *object = component.create();
QString warning1 = url.left(url.length() - 3) + "js:2: Error: Invalid write to global property \"a\"";
QString warning2 = url + ":5: ReferenceError: Can't find variable: a";
QString warning3 = url.left(url.length() - 3) + "js:4: Error: Invalid write to global property \"a\"";
- QString warning4 = url + ":10: ReferenceError: Can't find variable: a";
- QString warning5 = url + ":8: ReferenceError: Can't find variable: a";
- QString warning6 = url + ":7: Unable to assign [undefined] to int x";
- QString warning7 = url + ":12: Error: Cannot assign to read-only property \"trueProperty\"";
- QString warning8 = url + ":13: Error: Cannot assign to non-existent property \"fakeProperty\"";
+ QString warning4 = url + ":13: ReferenceError: Can't find variable: a";
+ QString warning5 = url + ":11: ReferenceError: Can't find variable: a";
+ QString warning6 = url + ":10: Unable to assign [undefined] to int";
+ QString warning7 = url + ":15: Error: Cannot assign to read-only property \"trueProperty\"";
+ QString warning8 = url + ":16: Error: Cannot assign to non-existent property \"fakeProperty\"";
QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
QTest::ignoreMessage(QtWarningMsg, warning2.toLatin1().constData());
delete object;
// test that if an exception occurs while invoking js function from cpp, it is reported as expected.
- QDeclarativeComponent componentTwo(&engine, TEST_FILE("scarceresources/scarceResourceFunctionFail.qml"));
+ QDeclarativeComponent componentTwo(&engine, TEST_FILE("scarceResourceFunctionFail.var.qml"));
url = componentTwo.url().toString();
object = componentTwo.create();
QVERIFY(object != 0);
QString srpname = object->property("srp_name").toString();
- warning = url + QLatin1String(":17: TypeError: Property 'scarceResource' of object ") + srpname +
- QLatin1String(" is not a function");
+ 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;
void tst_qdeclarativeecmascript::compositePropertyType()
{
QDeclarativeComponent component(&engine, TEST_FILE("compositePropertyType.qml"));
+
QTest::ignoreMessage(QtDebugMsg, "hello world");
QObject *object = qobject_cast<QObject *>(component.create());
delete object;
}
}
+// Aliases to variant properties should work
+void tst_qdeclarativeecmascript::qtbug_22464()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("qtbug_22464.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test").toBool(), true);
+
+ delete object;
+}
+
+void tst_qdeclarativeecmascript::qtbug_21580()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("qtbug_21580.qml"));
+
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test").toBool(), true);
+
+ delete object;
+}
+
// QTBUG-6781
void tst_qdeclarativeecmascript::bug1()
{
delete object;
}
+// ownership transferred to JS, ensure that GC runs the dtor
+void tst_qdeclarativeecmascript::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.
+ {
+ QDeclarativeEngine dcoEngine;
+ QDeclarativeComponent component(&dcoEngine, TEST_FILE("dynamicCreationOwnership.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ MyDynamicCreationDestructionObject *mdcdo = object->findChild<MyDynamicCreationDestructionObject*>("mdcdo");
+ QVERIFY(mdcdo != 0);
+ mdcdo->setDtorCount(&dtorCount);
+
+ for (int i = 1; i < 105; ++i, ++expectedDtorCount) {
+ QMetaObject::invokeMethod(object, "dynamicallyCreateJsOwnedObject");
+ if (i % 90 == 0) {
+ // we do this once manually, but it should be done automatically
+ // when the engine goes out of scope (since it should gc in dtor)
+ QMetaObject::invokeMethod(object, "performGc");
+ }
+ if (i % 10 == 0) {
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ }
+ }
+
+ delete object;
+ }
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ QCOMPARE(dtorCount, expectedDtorCount);
+}
+
//QTBUG-9367
void tst_qdeclarativeecmascript::regExpBug()
{
}
}
-void tst_qdeclarativeecmascript::importScripts()
+void tst_qdeclarativeecmascript::importScripts_data()
{
- QObject *object = 0;
+ QTest::addColumn<QUrl>("testfile");
+ QTest::addColumn<QString>("errorMessage");
+ QTest::addColumn<QStringList>("warningMessages");
+ QTest::addColumn<QStringList>("propertyNames");
+ QTest::addColumn<QVariantList>("propertyValues");
- // first, ensure that the required behaviour works.
- QDeclarativeComponent component(&engine, TEST_FILE("jsimport/testImport.qml"));
- object = component.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("importedScriptStringValue"), QVariant(QString(QLatin1String("Hello, World!"))));
- QCOMPARE(object->property("importedScriptFunctionValue"), QVariant(20));
- QCOMPARE(object->property("importedModuleAttachedPropertyValue"), QVariant(19));
- QCOMPARE(object->property("importedModuleEnumValue"), QVariant(2));
- delete object;
+ QTest::newRow("basic functionality")
+ << TEST_FILE("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")
+ << TEST_FILE("jsimport/testImportScoping.qml")
+ << QString()
+ << QStringList()
+ << (QStringList() << QLatin1String("componentError"))
+ << (QVariantList() << QVariant(5));
- QDeclarativeComponent componentTwo(&engine, TEST_FILE("jsimport/testImportScoping.qml"));
- object = componentTwo.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("componentError"), QVariant(5));
- delete object;
+ QTest::newRow("parent scope shouldn't be inherited by import with imports")
+ << TEST_FILE("jsimportfail/failOne.qml")
+ << QString()
+ << (QStringList() << QString(QLatin1String("file://") + TEST_FILE("jsimportfail/failOne.qml").toLocalFile() + QLatin1String(":6: TypeError: Cannot call method 'greetingString' of undefined")))
+ << (QStringList() << QLatin1String("importScriptFunctionValue"))
+ << (QVariantList() << QVariant(QString()));
- // then, ensure that unintended behaviour does not work.
- QDeclarativeComponent failOneComponent(&engine, TEST_FILE("jsimportfail/failOne.qml"));
- QString expectedWarning = QLatin1String("file://") + TEST_FILE("jsimportfail/failOne.qml").toLocalFile() + QLatin1String(":6: TypeError: Cannot call method 'greetingString' of undefined");
- QTest::ignoreMessage(QtWarningMsg, expectedWarning.toAscii().constData());
- object = failOneComponent.create();
- QVERIFY(object != 0);
- QVERIFY(object->property("importScriptFunctionValue").toString().isEmpty());
- delete object;
- QDeclarativeComponent failTwoComponent(&engine, TEST_FILE("jsimportfail/failTwo.qml"));
- expectedWarning = QLatin1String("file://") + TEST_FILE("jsimportfail/failTwo.qml").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: ImportOneJs");
- QTest::ignoreMessage(QtWarningMsg, expectedWarning.toAscii().constData());
- object = failTwoComponent.create();
- QVERIFY(object != 0);
- QVERIFY(object->property("importScriptFunctionValue").toString().isEmpty());
- delete object;
- QDeclarativeComponent failThreeComponent(&engine, TEST_FILE("jsimportfail/failThree.qml"));
- expectedWarning = QLatin1String("file://") + TEST_FILE("jsimportfail/failThree.qml").toLocalFile() + QLatin1String(":7: TypeError: Cannot read property 'JsQtTest' of undefined");
- QTest::ignoreMessage(QtWarningMsg, expectedWarning.toAscii().constData());
- object = failThreeComponent.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("importedModuleAttachedPropertyValue"), QVariant(false));
- delete object;
- QDeclarativeComponent failFourComponent(&engine, TEST_FILE("jsimportfail/failFour.qml"));
- expectedWarning = QLatin1String("file://") + TEST_FILE("jsimportfail/failFour.qml").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: JsQtTest");
- QTest::ignoreMessage(QtWarningMsg, expectedWarning.toAscii().constData());
- object = failFourComponent.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("importedModuleEnumValue"), QVariant(0));
- delete object;
- QDeclarativeComponent failFiveComponent(&engine, TEST_FILE("jsimportfail/failFive.qml"));
- expectedWarning = QLatin1String("file://") + TEST_FILE("jsimportfail/importWithImports.js").toLocalFile() + QLatin1String(":8: ReferenceError: Can't find variable: Component");
- QTest::ignoreMessage(QtWarningMsg, expectedWarning.toAscii().constData());
- expectedWarning = QLatin1String("file://") + TEST_FILE("jsimportfail/importPragmaLibrary.js").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: Component");
- QTest::ignoreMessage(QtWarningMsg, expectedWarning.toAscii().constData());
- object = failFiveComponent.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("componentError"), QVariant(0));
- delete object;
+ QTest::newRow("javascript imports in an import should be private to the import scope")
+ << TEST_FILE("jsimportfail/failTwo.qml")
+ << QString()
+ << (QStringList() << QString(QLatin1String("file://") + TEST_FILE("jsimportfail/failTwo.qml").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: ImportOneJs")))
+ << (QStringList() << QLatin1String("importScriptFunctionValue"))
+ << (QVariantList() << QVariant(QString()));
- // also, test that importing scripts with .pragma library works as required
- QDeclarativeComponent pragmaLibraryComponent(&engine, TEST_FILE("jsimport/testImportPragmaLibrary.qml"));
- object = pragmaLibraryComponent.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("testValue"), QVariant(31));
- delete object;
+ QTest::newRow("module imports in an import should be private to the import scope")
+ << TEST_FILE("jsimportfail/failThree.qml")
+ << QString()
+ << (QStringList() << QString(QLatin1String("file://") + TEST_FILE("jsimportfail/failThree.qml").toLocalFile() + QLatin1String(":7: TypeError: Cannot read property 'JsQtTest' of undefined")))
+ << (QStringList() << QLatin1String("importedModuleAttachedPropertyValue"))
+ << (QVariantList() << QVariant(false));
- // and that .pragma library scripts don't inherit imports from any .qml file
- QDeclarativeComponent pragmaLibraryComponentTwo(&engine, TEST_FILE("jsimportfail/testImportPragmaLibrary.qml"));
- object = pragmaLibraryComponentTwo.create();
- QVERIFY(object != 0);
- QCOMPARE(object->property("testValue"), QVariant(0));
- delete object;
+ QTest::newRow("typenames in an import should be private to the import scope")
+ << TEST_FILE("jsimportfail/failFour.qml")
+ << QString()
+ << (QStringList() << QString(QLatin1String("file://") + TEST_FILE("jsimportfail/failFour.qml").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: JsQtTest")))
+ << (QStringList() << QLatin1String("importedModuleEnumValue"))
+ << (QVariantList() << QVariant(0));
+
+ QTest::newRow("import with imports has it's own activation scope")
+ << TEST_FILE("jsimportfail/failFive.qml")
+ << QString()
+ << (QStringList() << QString(QLatin1String("file://") + TEST_FILE("jsimportfail/importWithImports.js").toLocalFile() + QLatin1String(":8: ReferenceError: Can't find variable: Component"))
+ << QString(QLatin1String("file://") + TEST_FILE("jsimportfail/importPragmaLibrary.js").toLocalFile() + QLatin1String(":6: ReferenceError: Can't find variable: Component")))
+ << (QStringList() << QLatin1String("componentError"))
+ << (QVariantList() << QVariant(0));
+
+ QTest::newRow("import pragma library script")
+ << TEST_FILE("jsimport/testImportPragmaLibrary.qml")
+ << QString()
+ << QStringList()
+ << (QStringList() << QLatin1String("testValue"))
+ << (QVariantList() << QVariant(31));
+
+ QTest::newRow("pragma library imports shouldn't inherit parent imports or scope")
+ << TEST_FILE("jsimportfail/testImportPragmaLibrary.qml")
+ << QString()
+ << QStringList()
+ << (QStringList() << QLatin1String("testValue"))
+ << (QVariantList() << QVariant(0));
+
+ QTest::newRow("import pragma library script which has an import")
+ << TEST_FILE("jsimport/testImportPragmaLibraryWithImports.qml")
+ << QString()
+ << QStringList()
+ << (QStringList() << QLatin1String("testValue"))
+ << (QVariantList() << QVariant(55));
+
+ QTest::newRow("import pragma library script which has a pragma library import")
+ << TEST_FILE("jsimport/testImportPragmaLibraryWithPragmaLibraryImports.qml")
+ << QString()
+ << QStringList()
+ << (QStringList() << QLatin1String("testValue"))
+ << (QVariantList() << QVariant(18));
}
-void tst_qdeclarativeecmascript::scarceResources()
+void tst_qdeclarativeecmascript::importScripts()
+{
+ QFETCH(QUrl, testfile);
+ QFETCH(QString, errorMessage);
+ QFETCH(QStringList, warningMessages);
+ QFETCH(QStringList, propertyNames);
+ QFETCH(QVariantList, propertyValues);
+
+ QDeclarativeComponent 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_qdeclarativeecmascript::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;
QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(&engine);
ScarceResourceObject *eo = 0;
+ QObject *srsc = 0;
QObject *object = 0;
- // 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.
- QDeclarativeComponent component(&engine, TEST_FILE("scarceresources/scarceResourceCopy.qml"));
- object = component.create();
- QVERIFY(object != 0);
- QVERIFY(object->property("scarceResourceCopy").isValid());
- QCOMPARE(object->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
- QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
+ /* property var semantics */
+
+ // test that scarce resources are handled properly in signal invocation
+ QDeclarativeComponent varComponentTen(&engine, TEST_FILE("scarceResourceSignal.var.qml"));
+ object = varComponentTen.create();
+ srsc = object->findChild<QObject*>("srsc");
+ QVERIFY(srsc);
+ QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // hasn't been instantiated yet.
+ QCOMPARE(srsc->property("width"), QVariant(5)); // default value is 5.
+ eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
+ QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
+ QMetaObject::invokeMethod(srsc, "testSignal");
+ QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // still hasn't been instantiated
+ QCOMPARE(srsc->property("width"), QVariant(10)); // but width was assigned to 10.
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
- QVERIFY(!eo->scarceResourceIsDetached()); // there are two copies of it in existence: the property of object, and the property of eo.
+ QVERIFY(eo->scarceResourceIsDetached()); // should still be no other copies of it at this stage.
+ QMetaObject::invokeMethod(srsc, "testSignal2"); // assigns scarceResourceCopy to the scarce pixmap.
+ QVERIFY(srsc->property("scarceResourceCopy").isValid());
+ QCOMPARE(srsc->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
+ eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
+ QVERIFY(!(eo->scarceResourceIsDetached())); // should be another copy of the resource now.
+ QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
delete object;
- QDeclarativeComponent componentTwo(&engine, TEST_FILE("scarceresources/scarceResourceCopyFromJs.qml"));
- object = componentTwo.create();
+ // test that scarce resources are handled properly from js functions in qml files
+ QDeclarativeComponent varComponentEleven(&engine, TEST_FILE("scarceResourceFunction.var.qml"));
+ object = varComponentEleven.create();
QVERIFY(object != 0);
- QVERIFY(object->property("scarceResourceCopy").isValid());
+ QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
+ eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
+ QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
+ QMetaObject::invokeMethod(object, "retrieveScarceResource");
+ QVERIFY(object->property("scarceResourceCopy").isValid()); // assigned, so should be valid.
QCOMPARE(object->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
- QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
- QVERIFY(!eo->scarceResourceIsDetached()); // there are two copies of it in existence: the property of object, and the property of eo.
- delete object;
-
- QDeclarativeComponent componentThree(&engine, TEST_FILE("scarceresources/scarceResourceDestroyedCopy.qml"));
- object = componentThree.create();
- QVERIFY(object != 0);
- QVERIFY(!(object->property("scarceResourceCopy").isValid())); // was manually released prior to being returned.
- QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
+ QVERIFY(!eo->scarceResourceIsDetached()); // should be a copy of the resource at this stage.
+ QMetaObject::invokeMethod(object, "releaseScarceResource");
+ QVERIFY(!object->property("scarceResourceCopy").isValid()); // just released, so should not be valid
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
- QVERIFY(eo->scarceResourceIsDetached()); // should have explicitly been released during the evaluation of the binding.
- delete object;
-
- // in the following three cases, no other copy should exist in memory,
- // and so it should be detached (unless explicitly preserved).
- QDeclarativeComponent componentFour(&engine, TEST_FILE("scarceresources/scarceResourceTest.qml"));
- object = componentFour.create();
- QVERIFY(object != 0);
- QVERIFY(object->property("scarceResourceTest").isValid());
- QCOMPARE(object->property("scarceResourceTest").toInt(), 100);
+ QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
- eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
- QVERIFY(eo->scarceResourceIsDetached()); // the resource should have been released after the binding was evaluated.
delete object;
- QDeclarativeComponent componentFive(&engine, TEST_FILE("scarceresources/scarceResourceTestPreserve.qml"));
- object = componentFive.create();
+ // test that if an exception occurs while invoking js function from cpp, that the resources are released.
+ QDeclarativeComponent varComponentTwelve(&engine, TEST_FILE("scarceResourceFunctionFail.var.qml"));
+ object = varComponentTwelve.create();
QVERIFY(object != 0);
- QVERIFY(object->property("scarceResourceTest").isValid());
- QCOMPARE(object->property("scarceResourceTest").toInt(), 100);
- QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
+ QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
- QVERIFY(!eo->scarceResourceIsDetached()); // this won't be detached since we explicitly preserved it.
- delete object;
-
- QDeclarativeComponent componentSix(&engine, TEST_FILE("scarceresources/scarceResourceTestMultiple.qml"));
- object = componentSix.create();
- QVERIFY(object != 0);
- QVERIFY(object->property("scarceResourceTest").isValid());
- QCOMPARE(object->property("scarceResourceTest").toInt(), 100);
- QVERIFY(ep->scarceResources.isEmpty()); // should have been released by this point.
+ QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
+ srp_name = object->property("srp_name").toString();
+ expectedWarning = varComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ") + srp_name + QLatin1String(" is not a function");
+ QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed.
+ QMetaObject::invokeMethod(object, "retrieveScarceResource");
+ QVERIFY(!object->property("scarceResourceCopy").isValid()); // due to exception, assignment will NOT have occurred.
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
- QVERIFY(eo->scarceResourceIsDetached()); // all resources were released manually or automatically released.
- delete object;
-
- // test that scarce resources are handled correctly for imports
- QDeclarativeComponent componentSeven(&engine, TEST_FILE("scarceresources/scarceResourceCopyImportNoBinding.qml"));
- object = componentSeven.create();
- QVERIFY(object != 0); // the import should have caused the addition of a resource to the ScarceResources list
- QVERIFY(ep->scarceResources.isEmpty()); // but they should have been released by this point.
- delete object;
-
- QDeclarativeComponent componentEight(&engine, TEST_FILE("scarceresources/scarceResourceCopyImportFail.qml"));
- object = componentEight.create();
- QVERIFY(object != 0);
- QVERIFY(!object->property("scarceResourceCopy").isValid()); // wasn't preserved, so shouldn't be valid.
+ 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;
- QDeclarativeComponent componentNine(&engine, TEST_FILE("scarceresources/scarceResourceCopyImport.qml"));
- object = componentNine.create();
+ // 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.
+ QDeclarativeComponent varComponentThirteen(&engine, TEST_FILE("scarceResourceObjectGc.var.qml"));
+ object = varComponentThirteen.create();
QVERIFY(object != 0);
- QVERIFY(object->property("scarceResourceCopy").isValid()); // preserved, so should be valid.
- QCOMPARE(object->property("scarceResourceCopy").value<QPixmap>(), origPixmap);
- QVERIFY(object->property("scarceResourceAssignedCopyOne").isValid()); // assigned before destroy(), so should be valid.
- QCOMPARE(object->property("scarceResourceAssignedCopyOne").value<QPixmap>(), origPixmap);
- QVERIFY(!object->property("scarceResourceAssignedCopyTwo").isValid()); // assigned after destroy(), so should be invalid.
- QVERIFY(ep->scarceResources.isEmpty()); // this will still be zero, because "preserve()" REMOVES it from this list.
+ 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
- QDeclarativeComponent componentTen(&engine, TEST_FILE("scarceresources/scarceResourceSignal.qml"));
- object = componentTen.create();
+ QDeclarativeComponent variantComponentTen(&engine, TEST_FILE("scarceResourceSignal.variant.qml"));
+ object = variantComponentTen.create();
QVERIFY(object != 0);
- QObject *srsc = object->findChild<QObject*>("srsc");
+ srsc = object->findChild<QObject*>("srsc");
QVERIFY(srsc);
QVERIFY(!srsc->property("scarceResourceCopy").isValid()); // hasn't been instantiated yet.
QCOMPARE(srsc->property("width"), QVariant(5)); // default value is 5.
delete object;
// test that scarce resources are handled properly from js functions in qml files
- QDeclarativeComponent componentEleven(&engine, TEST_FILE("scarceresources/scarceResourceFunction.qml"));
- object = componentEleven.create();
+ QDeclarativeComponent variantComponentEleven(&engine, TEST_FILE("scarceResourceFunction.variant.qml"));
+ object = variantComponentEleven.create();
QVERIFY(object != 0);
QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
delete object;
// test that if an exception occurs while invoking js function from cpp, that the resources are released.
- QDeclarativeComponent componentTwelve(&engine, TEST_FILE("scarceresources/scarceResourceFunctionFail.qml"));
- object = componentTwelve.create();
+ QDeclarativeComponent variantComponentTwelve(&engine, TEST_FILE("scarceResourceFunctionFail.variant.qml"));
+ object = variantComponentTwelve.create();
QVERIFY(object != 0);
QVERIFY(!object->property("scarceResourceCopy").isValid()); // not yet assigned, so should not be valid
eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage.
- QString srp_name = object->property("srp_name").toString();
- QString expectedWarning = componentTwelve.url().toString() + QLatin1String(":17: TypeError: Property 'scarceResource' of object ") + srp_name + QLatin1String(" is not a function");
+ 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.
delete object;
}
+void tst_qdeclarativeecmascript::scarceResources_data()
+{
+ QTest::addColumn<QUrl>("qmlFile");
+ QTest::addColumn<bool>("readDetachStatus");
+ QTest::addColumn<bool>("expectedDetachStatus");
+ QTest::addColumn<QStringList>("propertyNames");
+ QTest::addColumn<QVariantList>("expectedValidity");
+ QTest::addColumn<QVariantList>("expectedValues");
+ QTest::addColumn<QStringList>("expectedErrors");
+
+ QPixmap origPixmap(100, 100);
+ origPixmap.fill(Qt::blue);
+
+ /* property var semantics */
+
+ // in the following three cases, the instance created from the component
+ // has a property which is a copy of the scarce resource; hence, the
+ // resource should NOT be detached prior to deletion of the object instance,
+ // unless the resource is destroyed explicitly.
+ QTest::newRow("var: import scarce resource copy directly")
+ << TEST_FILE("scarceResourceCopy.var.qml")
+ << true
+ << false // won't be detached, because assigned to property and not explicitly released
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << origPixmap)
+ << QStringList();
+
+ QTest::newRow("var: import scarce resource copy from JS")
+ << TEST_FILE("scarceResourceCopyFromJs.var.qml")
+ << true
+ << false // won't be detached, because assigned to property and not explicitly released
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << origPixmap)
+ << QStringList();
+
+ QTest::newRow("var: import released scarce resource copy from JS")
+ << TEST_FILE("scarceResourceDestroyedCopy.var.qml")
+ << true
+ << true // explicitly released, so it will be detached
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << false)
+ << (QList<QVariant>() << QVariant())
+ << QStringList();
+
+ // in the following three cases, no other copy should exist in memory,
+ // and so it should be detached (unless explicitly preserved).
+ QTest::newRow("var: import auto-release SR from JS in binding side-effect")
+ << TEST_FILE("scarceResourceTest.var.qml")
+ << true
+ << true // auto released, so it will be detached
+ << (QStringList() << QLatin1String("scarceResourceTest"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << QVariant(100))
+ << QStringList();
+ QTest::newRow("var: import explicit-preserve SR from JS in binding side-effect")
+ << TEST_FILE("scarceResourceTestPreserve.var.qml")
+ << true
+ << false // won't be detached because we explicitly preserve it
+ << (QStringList() << QLatin1String("scarceResourceTest"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << QVariant(100))
+ << QStringList();
+ QTest::newRow("var: import explicit-preserve SR from JS in binding side-effect")
+ << TEST_FILE("scarceResourceTestMultiple.var.qml")
+ << true
+ << true // will be detached because all resources were released manually or automatically.
+ << (QStringList() << QLatin1String("scarceResourceTest"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << QVariant(100))
+ << QStringList();
+
+ // In the following three cases, test that scarce resources are handled
+ // correctly for imports.
+ QTest::newRow("var: import with no binding")
+ << TEST_FILE("scarceResourceCopyImportNoBinding.var.qml")
+ << false // cannot check detach status.
+ << false
+ << QStringList()
+ << QList<QVariant>()
+ << QList<QVariant>()
+ << QStringList();
+ QTest::newRow("var: import with binding without explicit preserve")
+ << TEST_FILE("scarceResourceCopyImportNoBinding.var.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << false) // will have been released prior to evaluation of binding.
+ << (QList<QVariant>() << QVariant())
+ << QStringList();
+ QTest::newRow("var: import with explicit release after binding evaluation")
+ << TEST_FILE("scarceResourceCopyImport.var.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("scarceResourceImportedCopy") << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo") << QLatin1String("arePropertiesEqual"))
+ << (QList<QVariant>() << false << false << false << true) // since property var = JS object reference, by releasing the provider's resource, all handles are invalidated.
+ << (QList<QVariant>() << QVariant() << QVariant() << QVariant() << QVariant(true))
+ << QStringList();
+ QTest::newRow("var: import with different js objects")
+ << TEST_FILE("scarceResourceCopyImportDifferent.var.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo") << QLatin1String("arePropertiesEqual"))
+ << (QList<QVariant>() << false << true << true) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object.
+ << (QList<QVariant>() << QVariant() << QVariant(origPixmap) << QVariant(false))
+ << QStringList();
+ QTest::newRow("var: import with different js objects and explicit release")
+ << TEST_FILE("scarceResourceMultipleDifferentNoBinding.var.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("resourceOne") << QLatin1String("resourceTwo"))
+ << (QList<QVariant>() << true << false) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object.
+ << (QList<QVariant>() << QVariant(origPixmap) << QVariant())
+ << QStringList();
+ QTest::newRow("var: import with same js objects and explicit release")
+ << TEST_FILE("scarceResourceMultipleSameNoBinding.var.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("resourceOne") << QLatin1String("resourceTwo"))
+ << (QList<QVariant>() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object.
+ << (QList<QVariant>() << QVariant() << QVariant())
+ << QStringList();
+ QTest::newRow("var: binding with same js objects and explicit release")
+ << TEST_FILE("scarceResourceMultipleSameWithBinding.var.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("resourceOne") << QLatin1String("resourceTwo"))
+ << (QList<QVariant>() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object.
+ << (QList<QVariant>() << QVariant() << QVariant())
+ << QStringList();
+
+
+ /* property variant semantics */
+
+ // in the following three cases, the instance created from the component
+ // has a property which is a copy of the scarce resource; hence, the
+ // resource should NOT be detached prior to deletion of the object instance,
+ // unless the resource is destroyed explicitly.
+ QTest::newRow("variant: import scarce resource copy directly")
+ << TEST_FILE("scarceResourceCopy.variant.qml")
+ << true
+ << false // won't be detached, because assigned to property and not explicitly released
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << origPixmap)
+ << QStringList();
+
+ QTest::newRow("variant: import scarce resource copy from JS")
+ << TEST_FILE("scarceResourceCopyFromJs.variant.qml")
+ << true
+ << false // won't be detached, because assigned to property and not explicitly released
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << origPixmap)
+ << QStringList();
+
+ QTest::newRow("variant: import released scarce resource copy from JS")
+ << TEST_FILE("scarceResourceDestroyedCopy.variant.qml")
+ << true
+ << true // explicitly released, so it will be detached
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << false)
+ << (QList<QVariant>() << QVariant())
+ << QStringList();
+
+ // in the following three cases, no other copy should exist in memory,
+ // and so it should be detached (unless explicitly preserved).
+ QTest::newRow("variant: import auto-release SR from JS in binding side-effect")
+ << TEST_FILE("scarceResourceTest.variant.qml")
+ << true
+ << true // auto released, so it will be detached
+ << (QStringList() << QLatin1String("scarceResourceTest"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << QVariant(100))
+ << QStringList();
+ QTest::newRow("variant: import explicit-preserve SR from JS in binding side-effect")
+ << TEST_FILE("scarceResourceTestPreserve.variant.qml")
+ << true
+ << false // won't be detached because we explicitly preserve it
+ << (QStringList() << QLatin1String("scarceResourceTest"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << QVariant(100))
+ << QStringList();
+ QTest::newRow("variant: import multiple scarce resources")
+ << TEST_FILE("scarceResourceTestMultiple.variant.qml")
+ << true
+ << true // will be detached because all resources were released manually or automatically.
+ << (QStringList() << QLatin1String("scarceResourceTest"))
+ << (QList<QVariant>() << true)
+ << (QList<QVariant>() << QVariant(100))
+ << QStringList();
+
+ // In the following three cases, test that scarce resources are handled
+ // correctly for imports.
+ QTest::newRow("variant: import with no binding")
+ << TEST_FILE("scarceResourceCopyImportNoBinding.variant.qml")
+ << false // cannot check detach status.
+ << false
+ << QStringList()
+ << QList<QVariant>()
+ << QList<QVariant>()
+ << QStringList();
+ QTest::newRow("variant: import with binding without explicit preserve")
+ << TEST_FILE("scarceResourceCopyImportNoBinding.variant.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("scarceResourceCopy"))
+ << (QList<QVariant>() << false) // will have been released prior to evaluation of binding.
+ << (QList<QVariant>() << QVariant())
+ << QStringList();
+ QTest::newRow("variant: import with explicit release after binding evaluation")
+ << TEST_FILE("scarceResourceCopyImport.variant.qml")
+ << false
+ << false
+ << (QStringList() << QLatin1String("scarceResourceImportedCopy") << QLatin1String("scarceResourceAssignedCopyOne") << QLatin1String("scarceResourceAssignedCopyTwo"))
+ << (QList<QVariant>() << true << true << false) // since property variant = variant copy, releasing the provider's resource does not invalidate previously assigned copies.
+ << (QList<QVariant>() << origPixmap << origPixmap << QVariant())
+ << QStringList();
+}
+
+void tst_qdeclarativeecmascript::scarceResources()
+{
+ QFETCH(QUrl, qmlFile);
+ QFETCH(bool, readDetachStatus);
+ QFETCH(bool, expectedDetachStatus);
+ QFETCH(QStringList, propertyNames);
+ QFETCH(QVariantList, expectedValidity);
+ QFETCH(QVariantList, expectedValues);
+ QFETCH(QStringList, expectedErrors);
+
+ QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(&engine);
+ ScarceResourceObject *eo = 0;
+ QObject *object = 0;
+
+ QDeclarativeComponent c(&engine, qmlFile);
+ object = c.create();
+ QVERIFY(object != 0);
+ for (int i = 0; i < propertyNames.size(); ++i) {
+ QString prop = propertyNames.at(i);
+ bool validity = expectedValidity.at(i).toBool();
+ QVariant value = expectedValues.at(i);
+
+ QCOMPARE(object->property(prop.toLatin1().constData()).isValid(), validity);
+ if (value.type() == QVariant::Int) {
+ QCOMPARE(object->property(prop.toLatin1().constData()).toInt(), value.toInt());
+ } else if (value.type() == QVariant::Pixmap) {
+ QCOMPARE(object->property(prop.toLatin1().constData()).value<QPixmap>(), value.value<QPixmap>());
+ }
+ }
+
+ if (readDetachStatus) {
+ eo = qobject_cast<ScarceResourceObject*>(QDeclarativeProperty::read(object, "a").value<QObject*>());
+ QCOMPARE(eo->scarceResourceIsDetached(), expectedDetachStatus);
+ }
+
+ QVERIFY(ep->scarceResources.isEmpty());
+ delete object;
+}
+
void tst_qdeclarativeecmascript::propertyChangeSlots()
{
// ensure that allowable property names are allowed and onPropertyNameChanged slots are generated correctly.
delete object;
}
-// Ensure that QObject type conversion works on binding assignment
-void tst_qdeclarativeecmascript::elementAssign()
+void tst_qdeclarativeecmascript::propertyVar_data()
{
- QDeclarativeComponent component(&engine, TEST_FILE("elementAssign.qml"));
-
- QObject *object = component.create();
- QVERIFY(object != 0);
-
- QCOMPARE(object->property("test").toBool(), true);
+ QTest::addColumn<QUrl>("qmlFile");
- delete object;
+ // valid
+ QTest::newRow("non-bindable object subproperty changed") << TEST_FILE("propertyVar.1.qml");
+ QTest::newRow("non-bindable object changed") << TEST_FILE("propertyVar.2.qml");
+ QTest::newRow("primitive changed") << TEST_FILE("propertyVar.3.qml");
+ QTest::newRow("javascript array modification") << TEST_FILE("propertyVar.4.qml");
+ QTest::newRow("javascript map modification") << TEST_FILE("propertyVar.5.qml");
+ QTest::newRow("javascript array assignment") << TEST_FILE("propertyVar.6.qml");
+ QTest::newRow("javascript map assignment") << TEST_FILE("propertyVar.7.qml");
+ QTest::newRow("literal property assignment") << TEST_FILE("propertyVar.8.qml");
+ QTest::newRow("qobject property assignment") << TEST_FILE("propertyVar.9.qml");
}
-// QTBUG-12457
-void tst_qdeclarativeecmascript::objectPassThroughSignals()
+void tst_qdeclarativeecmascript::propertyVar()
{
- QDeclarativeComponent component(&engine, TEST_FILE("objectsPassThroughSignals.qml"));
+ QFETCH(QUrl, qmlFile);
+ QDeclarativeComponent component(&engine, qmlFile);
QObject *object = component.create();
QVERIFY(object != 0);
delete object;
}
-// QTBUG-20242
-void tst_qdeclarativeecmascript::booleanConversion()
+// Tests that we can write QVariant values to var properties from C++
+void tst_qdeclarativeecmascript::propertyVarCpp()
{
- QDeclarativeComponent component(&engine, TEST_FILE("booleanConversion.qml"));
+ QObject *object = 0;
- QObject *object = component.create();
+ // 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.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVarCpp.qml"));
+ 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);
-
+ // 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;
}
-void tst_qdeclarativeecmascript::handleReferenceManagement()
+static void gc(QDeclarativeEngine &engine)
{
+ engine.collectGarbage();
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+}
- int dtorCount = 0;
+void tst_qdeclarativeecmascript::propertyVarOwnership()
+{
+ // Referenced JS objects are not collected
{
- // Linear QObject reference
- QDeclarativeEngine hrmEngine;
- QDeclarativeComponent component(&hrmEngine, TEST_FILE("handleReferenceManagement.object.1.qml"));
- QObject *object = component.create();
- QVERIFY(object != 0);
- CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
- cro->setDtorCount(&dtorCount);
+ QDeclarativeComponent component(&engine, TEST_FILE("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
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("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
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVarOwnership.3.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test2").toBool(), false);
+ QCOMPARE(object->property("test2").toBool(), false);
+
+ QMetaObject::invokeMethod(object, "runTest");
+ QCOMPARE(object->property("test1").toBool(), true);
+
+ QPointer<QObject> referencedObject = object->property("object").value<QObject*>();
+ QVERIFY(!referencedObject.isNull());
+ gc(engine);
+ QVERIFY(!referencedObject.isNull());
+
+ QMetaObject::invokeMethod(object, "runTest2");
+ QCOMPARE(object->property("test2").toBool(), true);
+ gc(engine);
+ QVERIFY(referencedObject.isNull());
+
+ delete object;
+ }
+ // Self reference does not prevent Qt object collection
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVarOwnership.4.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test").toBool(), true);
+
+ QPointer<QObject> referencedObject = object->property("object").value<QObject*>();
+ QVERIFY(!referencedObject.isNull());
+ gc(engine);
+ QVERIFY(!referencedObject.isNull());
+
+ QMetaObject::invokeMethod(object, "runTest");
+ gc(engine);
+ QVERIFY(referencedObject.isNull());
+
+ delete object;
+ }
+}
+
+void tst_qdeclarativeecmascript::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.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVarImplicitOwnership.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignCircular");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QObject *rootObject = object->property("vp").value<QObject*>();
+ QVERIFY(rootObject != 0);
+ QObject *childObject = rootObject->findChild<QObject*>("text");
+ QVERIFY(childObject != 0);
+ QCOMPARE(rootObject->property("rectCanary").toInt(), 5);
+ QCOMPARE(childObject->property("textCanary").toInt(), 10);
+ QMetaObject::invokeMethod(childObject, "constructQObject"); // creates a reference to a constructed QObject.
+ QWeakPointer<QObject> qobjectGuard(childObject->property("vp").value<QObject*>()); // get the pointer prior to processing deleteLater events.
+ QVERIFY(!qobjectGuard.isNull());
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QVERIFY(!qobjectGuard.isNull());
+ QMetaObject::invokeMethod(object, "deassignCircular");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QVERIFY(qobjectGuard.isNull()); // should have been collected now.
+ delete object;
+}
+
+void tst_qdeclarativeecmascript::propertyVarReparent()
+{
+ // ensure that nothing breaks if we re-parent objects
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.reparent.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignVarProp");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QObject *rect = object->property("vp").value<QObject*>();
+ QObject *text = rect->findChild<QObject*>("textOne");
+ QObject *text2 = rect->findChild<QObject*>("textTwo");
+ QWeakPointer<QObject> rectGuard(rect);
+ QWeakPointer<QObject> textGuard(text);
+ QWeakPointer<QObject> text2Guard(text2);
+ QVERIFY(!rectGuard.isNull());
+ QVERIFY(!textGuard.isNull());
+ QVERIFY(!text2Guard.isNull());
+ QCOMPARE(text->property("textCanary").toInt(), 11);
+ QCOMPARE(text2->property("textCanary").toInt(), 12);
+ // now construct an image which we will reparent.
+ QMetaObject::invokeMethod(text2, "constructQObject");
+ QObject *image = text2->property("vp").value<QObject*>();
+ QWeakPointer<QObject> imageGuard(image);
+ QVERIFY(!imageGuard.isNull());
+ QCOMPARE(image->property("imageCanary").toInt(), 13);
+ // now reparent the "Image" object (currently, it has JS ownership)
+ image->setParent(text); // shouldn't be collected after deassignVp now, since has a parent.
+ QMetaObject::invokeMethod(text2, "deassignVp");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ 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::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QVERIFY(imageGuard.isNull()); // should now have been deleted, due to parent being deleted.
+ delete object;
+}
+
+void tst_qdeclarativeecmascript::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.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.reparent.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignVarProp");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QObject *rect = object->property("vp").value<QObject*>();
+ QObject *text = rect->findChild<QObject*>("textOne");
+ QObject *text2 = rect->findChild<QObject*>("textTwo");
+ QWeakPointer<QObject> rectGuard(rect);
+ QWeakPointer<QObject> textGuard(text);
+ QWeakPointer<QObject> text2Guard(text2);
+ QVERIFY(!rectGuard.isNull());
+ QVERIFY(!textGuard.isNull());
+ QVERIFY(!text2Guard.isNull());
+ QCOMPARE(text->property("textCanary").toInt(), 11);
+ QCOMPARE(text2->property("textCanary").toInt(), 12);
+ // now construct an image which we will reparent.
+ QMetaObject::invokeMethod(text2, "constructQObject");
+ QObject *image = text2->property("vp").value<QObject*>();
+ QWeakPointer<QObject> imageGuard(image);
+ QVERIFY(!imageGuard.isNull());
+ QCOMPARE(image->property("imageCanary").toInt(), 13);
+ // now reparent the "Image" object (currently, it has JS ownership)
+ image->setParent(object); // reparented to base object. after deassignVarProp, the ctxt will be invalid.
+ QMetaObject::invokeMethod(object, "deassignVarProp"); // now deassign the root-object's vp, causing gc of rect+text+text2
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ 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_qdeclarativeecmascript::propertyVarCircular()
+{
+ // enforce behaviour regarding circular references - ensure qdvmemo deletion.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.circular.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignCircular"); // cause assignment and gc
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QCOMPARE(object->property("canaryInt"), QVariant(5));
+ QVariant canaryResourceVariant = object->property("canaryResource");
+ QVERIFY(canaryResourceVariant.isValid());
+ QPixmap canaryResourcePixmap = canaryResourceVariant.value<QPixmap>();
+ canaryResourceVariant = QVariant(); // invalidate it to remove one copy of the pixmap from memory.
+ QMetaObject::invokeMethod(object, "deassignCanaryResource"); // remove one copy of the pixmap from memory
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QVERIFY(!canaryResourcePixmap.isDetached()); // two copies extant - this and the propertyVar.vp.vp.vp.vp.memoryHog.
+ QMetaObject::invokeMethod(object, "deassignCircular"); // cause deassignment and gc
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ 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_qdeclarativeecmascript::propertyVarCircular2()
+{
+ // track deletion of JS-owned parent item with Cpp-owned child
+ // where the child has a var property referencing its parent.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.circular.2.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignCircular");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QObject *rootObject = object->property("vp").value<QObject*>();
+ QVERIFY(rootObject != 0);
+ QObject *childObject = rootObject->findChild<QObject*>("text");
+ QVERIFY(childObject != 0);
+ QWeakPointer<QObject> rootObjectTracker(rootObject);
+ QVERIFY(!rootObjectTracker.isNull());
+ QWeakPointer<QObject> childObjectTracker(childObject);
+ QVERIFY(!childObjectTracker.isNull());
+ gc(engine);
+ QCOMPARE(rootObject->property("rectCanary").toInt(), 5);
+ QCOMPARE(childObject->property("textCanary").toInt(), 10);
+ QMetaObject::invokeMethod(object, "deassignCircular");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QVERIFY(rootObjectTracker.isNull()); // should have been collected
+ QVERIFY(childObjectTracker.isNull()); // should have been collected
+ delete object;
+}
+
+void tst_qdeclarativeecmascript::propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter)
+{
+ *(int*)(parameter) += 1;
+ qPersistentDispose(object);
+}
+
+void tst_qdeclarativeecmascript::propertyVarInheritance()
+{
+ int propertyVarWeakRefCallbackCount = 0;
+
+ // enforce behaviour regarding element inheritance - ensure handle disposal.
+ // The particular component under test here has a chain of references.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.inherit.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignCircular"); // cause assignment and gc
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ // we want to be able to track when the varProperties array of the last metaobject is disposed
+ QObject *cco5 = object->property("varProperty").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>();
+ QObject *ico5 = object->property("varProperty").value<QObject*>()->property("inheritanceVarProperty").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>()->property("vp").value<QObject*>();
+ QDeclarativeVMEMetaObject *icovmemo = ((QDeclarativeVMEMetaObject *)(ico5->metaObject()));
+ QDeclarativeVMEMetaObject *ccovmemo = ((QDeclarativeVMEMetaObject *)(cco5->metaObject()));
+ v8::Persistent<v8::Value> icoCanaryHandle;
+ v8::Persistent<v8::Value> ccoCanaryHandle;
+ {
+ v8::HandleScope hs;
+ // XXX NOTE: this is very implementation dependent. QDVMEMO->vmeProperty() is the only
+ // public function which can return us a handle to something in the varProperties array.
+ icoCanaryHandle = qPersistentNew(icovmemo->vmeProperty(41));
+ ccoCanaryHandle = qPersistentNew(ccovmemo->vmeProperty(41));
+ // 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::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ // 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_qdeclarativeecmascript::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.
+ QDeclarativeComponent component(&engine, TEST_FILE("propertyVar.circular.2.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QMetaObject::invokeMethod(object, "assignCircular");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QObject *rootObject = object->property("vp").value<QObject*>();
+ QVERIFY(rootObject != 0);
+ QObject *childObject = rootObject->findChild<QObject*>("text");
+ QVERIFY(childObject != 0);
+ QCOMPARE(rootObject->property("rectCanary").toInt(), 5);
+ QCOMPARE(childObject->property("textCanary").toInt(), 10);
+ v8::Persistent<v8::Value> childObjectVarArrayValueHandle;
+ {
+ v8::HandleScope hs;
+ propertyVarWeakRefCallbackCount = 0; // reset callback count.
+ childObjectVarArrayValueHandle = qPersistentNew(((QDeclarativeVMEMetaObject *)(childObject->metaObject()))->vmeProperty(58));
+ childObjectVarArrayValueHandle.MakeWeak(&propertyVarWeakRefCallbackCount, propertyVarWeakRefCallback);
+ gc(engine);
+ QVERIFY(propertyVarWeakRefCallbackCount == 0); // should not have been collected yet.
+ QCOMPARE(childObject->property("textCanary").toInt(), 10);
+ }
+ QMetaObject::invokeMethod(object, "deassignCircular");
+ QCoreApplication::processEvents(QEventLoop::DeferredDeletion); // process deleteLater() events from QV8QObjectWrapper.
+ QVERIFY(propertyVarWeakRefCallbackCount == 1); // should have been collected now.
+ delete object;
+}
+
+// Ensure that QObject type conversion works on binding assignment
+void tst_qdeclarativeecmascript::elementAssign()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("elementAssign.qml"));
+
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test").toBool(), true);
+
+ delete object;
+}
+
+// QTBUG-12457
+void tst_qdeclarativeecmascript::objectPassThroughSignals()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("objectsPassThroughSignals.qml"));
+
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test").toBool(), true);
+
+ delete object;
+}
+
+// QTBUG-21626
+void tst_qdeclarativeecmascript::objectConversion()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("objectConversion.qml"));
+
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QVariant retn;
+ QMetaObject::invokeMethod(object, "circularObject", Q_RETURN_ARG(QVariant, retn));
+ QCOMPARE(retn.value<QVariantMap>().value("test"), QVariant(100));
+
+ delete object;
+}
+
+
+// QTBUG-20242
+void tst_qdeclarativeecmascript::booleanConversion()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("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_qdeclarativeecmascript::handleReferenceManagement()
+{
+
+ int dtorCount = 0;
+ {
+ // Linear QObject reference
+ QDeclarativeEngine hrmEngine;
+ QDeclarativeComponent component(&hrmEngine, TEST_FILE("handleReferenceManagement.object.1.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
+ cro->setDtorCount(&dtorCount);
QMetaObject::invokeMethod(object, "createReference");
- QMetaObject::invokeMethod(object, "performGc");
- QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ gc(engine);
QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference
delete object;
hrmEngine.collectGarbage();
CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>("cro");
cro->setDtorCount(&dtorCount);
QMetaObject::invokeMethod(object, "circularReference");
- QMetaObject::invokeMethod(object, "performGc");
- QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ gc(engine);
QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive.
delete object;
hrmEngine.collectGarbage();
// now we have to reparent second and make second owned by JS.
second->setParent(0);
QDeclarativeEngine::setObjectOwnership(second, QDeclarativeEngine::JavaScriptOwnership);
- QMetaObject::invokeMethod(object, "performGc");
- QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ gc(engine);
QCOMPARE(dtorCount, 0); // due to reference from first to second, second shouldn't be collected.
delete object;
hrmEngine.collectGarbage();
second->setParent(0);
QDeclarativeEngine::setObjectOwnership(first, QDeclarativeEngine::JavaScriptOwnership);
QDeclarativeEngine::setObjectOwnership(second, QDeclarativeEngine::JavaScriptOwnership);
- QMetaObject::invokeMethod(object, "performGc");
- QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ gc(engine);
QCOMPARE(dtorCount, 2); // despite circular references, both will be collected.
delete object;
hrmEngine.collectGarbage();
// now we have to reparent second2 and make second2 owned by JS.
second2->setParent(0);
QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership);
- QMetaObject::invokeMethod(object1, "performGc");
- QMetaObject::invokeMethod(object2, "performGc");
+ gc(engine);
QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
QCOMPARE(dtorCount, 0); // due to reference from first1 to second2, second2 shouldn't be collected.
delete object1;
QDeclarativeEngine::setObjectOwnership(second1, QDeclarativeEngine::JavaScriptOwnership);
QDeclarativeEngine::setObjectOwnership(first2, QDeclarativeEngine::JavaScriptOwnership);
QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership);
- QMetaObject::invokeMethod(object1, "performGc");
- QMetaObject::invokeMethod(object2, "performGc");
+ gc(engine);
QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
QCOMPARE(dtorCount, 4); // circular references shouldn't keep them alive.
delete object1;
QDeclarativeEngine::setObjectOwnership(second1, QDeclarativeEngine::JavaScriptOwnership);
QDeclarativeEngine::setObjectOwnership(first2, QDeclarativeEngine::JavaScriptOwnership);
QDeclarativeEngine::setObjectOwnership(second2, QDeclarativeEngine::JavaScriptOwnership);
- QMetaObject::invokeMethod(object1, "performGc");
- QMetaObject::invokeMethod(object2, "performGc");
- QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ gc(engine);
QCOMPARE(dtorCount, 0);
delete hrmEngine2;
- QMetaObject::invokeMethod(object1, "performGc");
- QCoreApplication::processEvents(QEventLoop::DeferredDeletion);
+ gc(engine);
QCOMPARE(dtorCount, 0);
delete object1;
delete object2;
delete object;
}
+void tst_qdeclarativeecmascript::readonlyDeclaration()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("readonlyDeclaration.qml"));
+
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->property("test").toBool(), true);
+
+ delete object;
+}
+
+Q_DECLARE_METATYPE(QList<int>)
+Q_DECLARE_METATYPE(QList<qreal>)
+Q_DECLARE_METATYPE(QList<bool>)
+Q_DECLARE_METATYPE(QList<QString>)
+Q_DECLARE_METATYPE(QList<QUrl>)
+void tst_qdeclarativeecmascript::sequenceConversionRead()
+{
+ {
+ QUrl qmlFile = TEST_FILE("sequenceConversion.read.qml");
+ QDeclarativeComponent component(&engine, qmlFile);
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
+ QVERIFY(seq != 0);
+
+ QMetaObject::invokeMethod(object, "readSequences");
+ QList<int> intList; intList << 1 << 2 << 3 << 4;
+ QCOMPARE(object->property("intListLength").toInt(), intList.length());
+ QCOMPARE(object->property("intList").value<QList<int> >(), intList);
+ QList<qreal> qrealList; qrealList << 1.1 << 2.2 << 3.3 << 4.4;
+ QCOMPARE(object->property("qrealListLength").toInt(), qrealList.length());
+ QCOMPARE(object->property("qrealList").value<QList<qreal> >(), qrealList);
+ QList<bool> boolList; boolList << true << false << true << false;
+ QCOMPARE(object->property("boolListLength").toInt(), boolList.length());
+ QCOMPARE(object->property("boolList").value<QList<bool> >(), boolList);
+ QList<QString> stringList; stringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth");
+ QCOMPARE(object->property("stringListLength").toInt(), stringList.length());
+ QCOMPARE(object->property("stringList").value<QList<QString> >(), stringList);
+ QList<QUrl> urlList; urlList << QUrl("http://www.example1.com") << QUrl("http://www.example2.com") << QUrl("http://www.example3.com");
+ QCOMPARE(object->property("urlListLength").toInt(), urlList.length());
+ QCOMPARE(object->property("urlList").value<QList<QUrl> >(), urlList);
+ QStringList qstringList; qstringList << QLatin1String("first") << QLatin1String("second") << QLatin1String("third") << QLatin1String("fourth");
+ QCOMPARE(object->property("qstringListLength").toInt(), qstringList.length());
+ QCOMPARE(object->property("qstringList").value<QStringList>(), qstringList);
+
+ QMetaObject::invokeMethod(object, "readSequenceElements");
+ QCOMPARE(object->property("intVal").toInt(), 2);
+ QCOMPARE(object->property("qrealVal").toReal(), 2.2);
+ QCOMPARE(object->property("boolVal").toBool(), false);
+ QCOMPARE(object->property("stringVal").toString(), QString(QLatin1String("second")));
+ QCOMPARE(object->property("urlVal").toUrl(), QUrl("http://www.example2.com"));
+ QCOMPARE(object->property("qstringVal").toString(), QString(QLatin1String("second")));
+
+ QMetaObject::invokeMethod(object, "enumerateSequenceElements");
+ QCOMPARE(object->property("enumerationMatches").toBool(), true);
+
+ intList.clear(); intList << 1 << 2 << 3 << 4 << 5; // set by the enumerateSequenceElements test.
+ QDeclarativeProperty seqProp(seq, "intListProperty");
+ QCOMPARE(seqProp.read().value<QList<int> >(), intList);
+ QDeclarativeProperty seqProp2(seq, "intListProperty", &engine);
+ QCOMPARE(seqProp2.read().value<QList<int> >(), intList);
+
+ QMetaObject::invokeMethod(object, "testReferenceDeletion");
+ QCOMPARE(object->property("referenceDeletion").toBool(), true);
+
+ delete object;
+ }
+
+ {
+ QUrl qmlFile = TEST_FILE("sequenceConversion.read.error.qml");
+ QDeclarativeComponent component(&engine, qmlFile);
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
+ QVERIFY(seq != 0);
+
+ // we haven't registered QList<QPoint> as a sequence type.
+ QString warningOne = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'QList<QPoint>' for property 'MySequenceConversionObject::pointListProperty'");
+ QString warningTwo = qmlFile.toString() + QLatin1String(":18: TypeError: Cannot read property 'length' of undefined");
+ QTest::ignoreMessage(QtWarningMsg, warningOne.toAscii().constData());
+ QTest::ignoreMessage(QtWarningMsg, warningTwo.toAscii().constData());
+
+ QMetaObject::invokeMethod(object, "performTest");
+
+ // QList<QPoint> has not been registered as a sequence type.
+ QCOMPARE(object->property("pointListLength").toInt(), 0);
+ QVERIFY(!object->property("pointList").isValid());
+ QTest::ignoreMessage(QtWarningMsg, "QMetaProperty::read: Unable to handle unregistered datatype 'QList<QPoint>' for property 'MySequenceConversionObject::pointListProperty'");
+ QDeclarativeProperty seqProp(seq, "pointListProperty", &engine);
+ QVERIFY(!seqProp.read().isValid()); // not a valid/known sequence type
+
+ delete object;
+ }
+}
+
+void tst_qdeclarativeecmascript::sequenceConversionWrite()
+{
+ {
+ QUrl qmlFile = TEST_FILE("sequenceConversion.write.qml");
+ QDeclarativeComponent component(&engine, qmlFile);
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
+ QVERIFY(seq != 0);
+
+ QMetaObject::invokeMethod(object, "writeSequences");
+ QCOMPARE(object->property("success").toBool(), true);
+
+ QMetaObject::invokeMethod(object, "writeSequenceElements");
+ QCOMPARE(object->property("success").toBool(), true);
+
+ QMetaObject::invokeMethod(object, "writeOtherElements");
+ QCOMPARE(object->property("success").toBool(), true);
+
+ QMetaObject::invokeMethod(object, "testReferenceDeletion");
+ QCOMPARE(object->property("referenceDeletion").toBool(), true);
+
+ delete object;
+ }
+
+ {
+ QUrl qmlFile = TEST_FILE("sequenceConversion.write.error.qml");
+ QDeclarativeComponent component(&engine, qmlFile);
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>("msco");
+ QVERIFY(seq != 0);
+
+ // we haven't registered QList<QPoint> as a sequence type, so writing shouldn't work.
+ QString warningOne = qmlFile.toString() + QLatin1String(":16: Error: Cannot assign QVariantList to void");
+ QTest::ignoreMessage(QtWarningMsg, warningOne.toAscii().constData());
+
+ QMetaObject::invokeMethod(object, "performTest");
+
+ QList<QPoint> pointList; pointList << QPoint(1, 2) << QPoint(3, 4) << QPoint(5, 6); // original values, shouldn't have changed
+ QCOMPARE(seq->pointListProperty(), pointList);
+
+ delete object;
+ }
+}
+
+void tst_qdeclarativeecmascript::sequenceConversionArray()
+{
+ // ensure that in JS the returned sequences act just like normal JS Arrays.
+ QUrl qmlFile = TEST_FILE("sequenceConversion.array.qml");
+ QDeclarativeComponent 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_qdeclarativeecmascript::sequenceConversionThreads()
+{
+ // ensure that sequence conversion operations work correctly in a worker thread
+ // and that serialisation between the main and worker thread succeeds.
+ QUrl qmlFile = TEST_FILE("sequenceConversion.threads.qml");
+ QDeclarativeComponent 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_qdeclarativeecmascript::sequenceConversionBindings()
+{
+ {
+ QUrl qmlFile = TEST_FILE("sequenceConversion.bindings.qml");
+ QDeclarativeComponent component(&engine, qmlFile);
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ QList<int> intList; intList << 1 << 2 << 3 << 12 << 7;
+ QCOMPARE(object->property("boundSequence").value<QList<int> >(), intList);
+ QCOMPARE(object->property("boundElement").toInt(), intList.at(3));
+ QList<int> intListTwo; intListTwo << 1 << 2 << 3 << 12 << 14;
+ QCOMPARE(object->property("boundSequenceTwo").value<QList<int> >(), intListTwo);
+ delete object;
+ }
+
+ {
+ QUrl qmlFile = TEST_FILE("sequenceConversion.bindings.error.qml");
+ QString warning = QString(QLatin1String("%1:17: Unable to assign QList<int> to QList<bool>")).arg(qmlFile.toString());
+ QTest::ignoreMessage(QtWarningMsg, warning.toAscii().constData());
+ QDeclarativeComponent component(&engine, qmlFile);
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ delete object;
+ }
+}
+
+void tst_qdeclarativeecmascript::sequenceConversionCopy()
+{
+ QUrl qmlFile = TEST_FILE("sequenceConversion.copy.qml");
+ QDeclarativeComponent 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_qdeclarativeecmascript::assignSequenceTypes()
+{
+ // test binding array to sequence type property
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("assignSequenceTypes.1.qml"));
+ MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
+ QVERIFY(object != 0);
+ QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2));
+ QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2));
+ QCOMPARE(object->boolListProperty(), (QList<bool>() << false << true));
+ QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com") << QUrl("http://www.example2.com")));
+ QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one") << QLatin1String("two")));
+ QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("one") << QLatin1String("two")));
+ delete object;
+ }
+
+ // test binding literal to sequence type property
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("assignSequenceTypes.2.qml"));
+ MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
+ QVERIFY(object != 0);
+ QCOMPARE(object->intListProperty(), (QList<int>() << 1));
+ QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1));
+ QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
+ QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com")));
+ QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one")));
+ QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("two")));
+ delete object;
+ }
+
+ // test binding single value to sequence type property
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("assignSequenceTypes.3.qml"));
+ MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
+ QVERIFY(object != 0);
+ QCOMPARE(object->intListProperty(), (QList<int>() << 1));
+ QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1));
+ QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
+ QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl(TEST_FILE("example.html"))));
+ delete object;
+ }
+
+ // test assigning array to sequence type property in js function
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("assignSequenceTypes.4.qml"));
+ MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
+ QVERIFY(object != 0);
+ QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2));
+ QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2));
+ QCOMPARE(object->boolListProperty(), (QList<bool>() << false << true));
+ QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com") << QUrl("http://www.example2.com")));
+ QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one") << QLatin1String("two")));
+ QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("one") << QLatin1String("two")));
+ delete object;
+ }
+
+ // test assigning literal to sequence type property in js function
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("assignSequenceTypes.5.qml"));
+ MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
+ QVERIFY(object != 0);
+ QCOMPARE(object->intListProperty(), (QList<int>() << 1));
+ QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1));
+ QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
+ QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com")));
+ QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one")));
+ QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("two")));
+ delete object;
+ }
+
+ // test assigning single value to sequence type property in js function
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("assignSequenceTypes.6.qml"));
+ MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(component.create());
+ QVERIFY(object != 0);
+ QCOMPARE(object->intListProperty(), (QList<int>() << 1));
+ QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1));
+ QCOMPARE(object->boolListProperty(), (QList<bool>() << false));
+ QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl(TEST_FILE("example.html"))));
+ delete object;
+ }
+}
+
// Test that assigning a null object works
// Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4
void tst_qdeclarativeecmascript::nullObjectBinding()
{
TestHTTPServer server(8111);
QVERIFY(server.isValid());
- server.serveDirectory(SRCDIR "/data");
+ server.serveDirectory(TESTDATA(""));
QDeclarativeComponent component(&engine, TEST_FILE("include_remote.qml"));
QObject *o = component.create();
{
TestHTTPServer server(8111);
QVERIFY(server.isValid());
- server.serveDirectory(SRCDIR "/data");
+ server.serveDirectory(TESTDATA(""));
QDeclarativeComponent component(&engine, TEST_FILE("include_remote_missing.qml"));
QObject *o = component.create();
delete o;
}
+void tst_qdeclarativeecmascript::qtbug_21864()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("qtbug_21864.qml"));
+ 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_qdeclarativeecmascript::nonscriptable()
{
delete o;
}
+void tst_qdeclarativeecmascript::typeOf()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("typeOf.qml"));
+
+ // These warnings should not happen once QTBUG-21864 is fixed
+ QString warning1 = component.url().toString() + QLatin1String(":16: Error: Cannot assign [undefined] to QString");
+ QString warning2 = component.url().resolved(QUrl("typeOf.js")).toString() + QLatin1String(":1: ReferenceError: Can't find variable: a");
+
+ QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));
+ QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
+
+ QObject *o = component.create();
+ QVERIFY(o != 0);
+
+ QEXPECT_FAIL("", "QTBUG-21864", Abort);
+ QCOMPARE(o->property("test1").toString(), QLatin1String("undefined"));
+ QCOMPARE(o->property("test2").toString(), QLatin1String("object"));
+ QCOMPARE(o->property("test3").toString(), QLatin1String("number"));
+ QCOMPARE(o->property("test4").toString(), QLatin1String("string"));
+ QCOMPARE(o->property("test5").toString(), QLatin1String("function"));
+ QCOMPARE(o->property("test6").toString(), QLatin1String("object"));
+ QCOMPARE(o->property("test7").toString(), QLatin1String("undefined"));
+ QCOMPARE(o->property("test8").toString(), QLatin1String("boolean"));
+ QCOMPARE(o->property("test9").toString(), QLatin1String("object"));
+
+ delete o;
+}
+
void tst_qdeclarativeecmascript::sharedAttachedObject()
{
QDeclarativeComponent component(&engine, TEST_FILE("sharedAttachedObject.qml"));
delete object;
}
+void tst_qdeclarativeecmascript::qtbug_20344()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("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_qdeclarativeecmascript::revisionErrors()
{
{
QVERIFY(object != 0);
}
+void tst_qdeclarativeecmascript::unaryExpression()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("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_qdeclarativeecmascript::doubleEvaluate()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("doubleEvaluate.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ WriteCounter *wc = qobject_cast<WriteCounter *>(object);
+ QVERIFY(wc != 0);
+ QCOMPARE(wc->count(), 1);
+
+ wc->setProperty("x", 9);
+
+ QCOMPARE(wc->count(), 2);
+
+ delete object;
+}
+
+static QStringList messages;
+static void captureMsgHandler(QtMsgType, const char *msg)
+{
+ messages.append(QLatin1String(msg));
+}
+
+void tst_qdeclarativeecmascript::nonNotifyable()
+{
+ QV4Compiler::enableV4(false);
+ QDeclarativeComponent component(&engine, TEST_FILE("nonNotifyable.qml"));
+ QV4Compiler::enableV4(true);
+
+ QtMsgHandler old = qInstallMsgHandler(captureMsgHandler);
+ messages.clear();
+ QObject *object = component.create();
+ qInstallMsgHandler(old);
+
+ QVERIFY(object != 0);
+
+ QString expected1 = QLatin1String("QDeclarativeExpression: 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_qdeclarativeecmascript::forInLoop()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("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_qdeclarativeecmascript::deleteWhileBindingRunning()
+{
+ QDeclarativeComponent component(&engine, TEST_FILE("deleteWhileBindingRunning.qml"));
+ QObject *object = component.create();
+ QVERIFY(object != 0);
+ delete object;
+}
+
+void tst_qdeclarativeecmascript::qtbug_22679()
+{
+ MyQmlObject object;
+ object.setStringProperty(QLatin1String("Please work correctly"));
+ engine.rootContext()->setContextProperty("contextProp", &object);
+
+ QDeclarativeComponent component(&engine, TEST_FILE("qtbug_22679.qml"));
+ qRegisterMetaType<QList<QDeclarativeError> >("QList<QDeclarativeError>");
+ QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QDeclarativeError>)));
+
+ QObject *o = component.create();
+ QVERIFY(o != 0);
+ QCOMPARE(warningsSpy.count(), 0);
+ delete o;
+}
+
+void tst_qdeclarativeecmascript::qtbug_22843_data()
+{
+ QTest::addColumn<bool>("library");
+
+ QTest::newRow("without .pragma library") << false;
+ QTest::newRow("with .pragma library") << true;
+}
+
+void tst_qdeclarativeecmascript::qtbug_22843()
+{
+ QFETCH(bool, library);
+
+ QString fileName("qtbug_22843");
+ if (library)
+ fileName += QLatin1String(".library");
+ fileName += QLatin1String(".qml");
+
+ QDeclarativeComponent component(&engine, TEST_FILE(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<QDeclarativeError> >("QList<QDeclarativeError>");
+ QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QDeclarativeError>)));
+ 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_qdeclarativeecmascript::switchStatement()
+{
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("switchStatement.1.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ // `object->value()' is the number of executed statements
+
+ object->setStringProperty("A");
+ QCOMPARE(object->value(), 5);
+
+ object->setStringProperty("S");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("D");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("F");
+ QCOMPARE(object->value(), 4);
+
+ object->setStringProperty("something else");
+ QCOMPARE(object->value(), 1);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("switchStatement.2.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ // `object->value()' is the number of executed statements
+
+ object->setStringProperty("A");
+ QCOMPARE(object->value(), 5);
+
+ object->setStringProperty("S");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("D");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("F");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("something else");
+ QCOMPARE(object->value(), 4);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("switchStatement.3.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ // `object->value()' is the number of executed statements
+
+ object->setStringProperty("A");
+ QCOMPARE(object->value(), 5);
+
+ object->setStringProperty("S");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("D");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("F");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("something else");
+ QCOMPARE(object->value(), 6);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("switchStatement.4.qml"));
+
+ QString warning = component.url().toString() + ":4: Unable to assign [undefined] to int";
+ QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
+
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ // `object->value()' is the number of executed statements
+
+ object->setStringProperty("A");
+ QCOMPARE(object->value(), 5);
+
+ object->setStringProperty("S");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("D");
+ QCOMPARE(object->value(), 3);
+
+ object->setStringProperty("F");
+ QCOMPARE(object->value(), 3);
+
+ QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
+
+ object->setStringProperty("something else");
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("switchStatement.5.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ // `object->value()' is the number of executed statements
+
+ object->setStringProperty("A");
+ QCOMPARE(object->value(), 1);
+
+ object->setStringProperty("S");
+ QCOMPARE(object->value(), 1);
+
+ object->setStringProperty("D");
+ QCOMPARE(object->value(), 1);
+
+ object->setStringProperty("F");
+ QCOMPARE(object->value(), 1);
+
+ object->setStringProperty("something else");
+ QCOMPARE(object->value(), 1);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("switchStatement.6.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ // `object->value()' is the number of executed statements
+
+ object->setStringProperty("A");
+ QCOMPARE(object->value(), 123);
+
+ object->setStringProperty("S");
+ QCOMPARE(object->value(), 123);
+
+ object->setStringProperty("D");
+ QCOMPARE(object->value(), 321);
+
+ object->setStringProperty("F");
+ QCOMPARE(object->value(), 321);
+
+ object->setStringProperty("something else");
+ QCOMPARE(object->value(), 0);
+ }
+}
+
+void tst_qdeclarativeecmascript::withStatement()
+{
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("withStatement.1.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->value(), 123);
+ }
+}
+
+void tst_qdeclarativeecmascript::tryStatement()
+{
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("tryStatement.1.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->value(), 123);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("tryStatement.2.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->value(), 321);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("tryStatement.3.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->value(), 1);
+ }
+
+ {
+ QDeclarativeComponent component(&engine, TEST_FILE("tryStatement.4.qml"));
+ MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create());
+ QVERIFY(object != 0);
+
+ QCOMPARE(object->value(), 1);
+ }
+}
+
QTEST_MAIN(tst_qdeclarativeecmascript)
#include "tst_qdeclarativeecmascript.moc"