#include #include #include #include #include #include #include #include #include "testtypes.h" /* This test covers evaluation of ECMAScript expressions and bindings from within QML. This does not include static QML language issues. Static QML language issues are covered in qmllanguage */ inline QUrl TEST_FILE(const QString &filename) { QFileInfo fileInfo(__FILE__); return QUrl::fromLocalFile(fileInfo.absoluteDir().filePath("data/" + filename)); } inline QUrl TEST_FILE(const char *filename) { return TEST_FILE(QLatin1String(filename)); } class tst_qmlecmascript : public QObject { Q_OBJECT public: tst_qmlecmascript() {} private slots: void idShortcutInvalidates(); void boolPropertiesEvaluateAsBool(); void methods(); void signalAssignment(); void bindingLoop(); void basicExpressions(); void basicExpressions_data(); void arrayExpressions(); void contextPropertiesTriggerReeval(); void objectPropertiesTriggerReeval(); void deferredProperties(); void extensionObjects(); void enums(); void valueTypeFunctions(); void constantsOverrideBindings(); void outerBindingOverridesInnerBinding(); private: QmlEngine engine; }; void tst_qmlecmascript::idShortcutInvalidates() { { QmlComponent component(&engine, TEST_FILE("idShortcutInvalidates.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QVERIFY(object->objectProperty() != 0); delete object->objectProperty(); QVERIFY(object->objectProperty() == 0); } { QmlComponent component(&engine, TEST_FILE("idShortcutInvalidates.1.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QVERIFY(object->objectProperty() != 0); delete object->objectProperty(); QVERIFY(object->objectProperty() == 0); } } void tst_qmlecmascript::boolPropertiesEvaluateAsBool() { { QmlComponent component(&engine, TEST_FILE("boolPropertiesEvaluateAsBool.1.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->stringProperty(), QLatin1String("pass")); } { QmlComponent component(&engine, TEST_FILE("boolPropertiesEvaluateAsBool.2.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->stringProperty(), QLatin1String("pass")); } } void tst_qmlecmascript::signalAssignment() { { QmlComponent component(&engine, TEST_FILE("signalAssignment.1.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->string(), QString()); emit object->basicSignal(); QCOMPARE(object->string(), QString("pass")); } { QmlComponent component(&engine, TEST_FILE("signalAssignment.2.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->string(), QString()); emit object->argumentSignal(19, "Hello world!", 10.3); QCOMPARE(object->string(), QString("pass 19 Hello world! 10.3")); } } void tst_qmlecmascript::methods() { { QmlComponent component(&engine, TEST_FILE("methods.1.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->methodCalled(), false); QCOMPARE(object->methodIntCalled(), false); emit object->basicSignal(); QCOMPARE(object->methodCalled(), true); QCOMPARE(object->methodIntCalled(), false); } { QmlComponent component(&engine, TEST_FILE("methods.2.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->methodCalled(), false); QCOMPARE(object->methodIntCalled(), false); emit object->basicSignal(); QCOMPARE(object->methodCalled(), false); QCOMPARE(object->methodIntCalled(), true); } } void tst_qmlecmascript::bindingLoop() { QmlComponent component(&engine, TEST_FILE("bindingLoop.qml")); QTest::ignoreMessage(QtWarningMsg, "QML MyQmlObject (unknown location): Binding loop detected for property \"stringProperty\" "); QObject *object = component.create(); QVERIFY(object != 0); } void tst_qmlecmascript::basicExpressions_data() { QTest::addColumn("expression"); QTest::addColumn("result"); QTest::addColumn("nest"); QTest::newRow("Context property") << "a" << QVariant(1944) << false; QTest::newRow("Context property") << "a" << QVariant(1944) << true; QTest::newRow("Context property expression") << "a * 2" << QVariant(3888) << false; QTest::newRow("Context property expression") << "a * 2" << QVariant(3888) << true; QTest::newRow("Overridden context property") << "b" << QVariant("Milk") << false; QTest::newRow("Overridden context property") << "b" << QVariant("Cow") << true; QTest::newRow("Object property") << "object.stringProperty" << QVariant("Object1") << false; QTest::newRow("Object property") << "object.stringProperty" << QVariant("Object1") << true; QTest::newRow("Overridden object property") << "objectOverride.stringProperty" << QVariant("Object2") << false; QTest::newRow("Overridden object property") << "objectOverride.stringProperty" << QVariant("Object3") << true; QTest::newRow("Default object property") << "horseLegs" << QVariant(4) << false; QTest::newRow("Default object property") << "antLegs" << QVariant(6) << false; QTest::newRow("Default object property") << "emuLegs" << QVariant(2) << false; QTest::newRow("Nested default object property") << "horseLegs" << QVariant(4) << true; QTest::newRow("Nested default object property") << "antLegs" << QVariant(7) << true; QTest::newRow("Nested default object property") << "emuLegs" << QVariant(2) << true; QTest::newRow("Nested default object property") << "humanLegs" << QVariant(2) << true; QTest::newRow("Context property override default object property") << "millipedeLegs" << QVariant(100) << true; } void tst_qmlecmascript::basicExpressions() { QFETCH(QString, expression); QFETCH(QVariant, result); QFETCH(bool, nest); MyQmlObject object1; MyQmlObject object2; MyQmlObject object3; MyDefaultObject1 default1; MyDefaultObject2 default2; MyDefaultObject3 default3; object1.setStringProperty("Object1"); object2.setStringProperty("Object2"); object3.setStringProperty("Object3"); QmlContext context(engine.rootContext()); QmlContext nestedContext(&context); context.addDefaultObject(&default1); context.addDefaultObject(&default2); context.setContextProperty("a", QVariant(1944)); context.setContextProperty("b", QVariant("Milk")); context.setContextProperty("object", &object1); context.setContextProperty("objectOverride", &object2); nestedContext.addDefaultObject(&default3); nestedContext.setContextProperty("b", QVariant("Cow")); nestedContext.setContextProperty("objectOverride", &object3); nestedContext.setContextProperty("millipedeLegs", QVariant(100)); MyExpression expr(nest?&nestedContext:&context, expression); QCOMPARE(expr.value(), result); } Q_DECLARE_METATYPE(QList); void tst_qmlecmascript::arrayExpressions() { QObject obj1; QObject obj2; QObject obj3; QmlContext context(engine.rootContext()); context.setContextProperty("a", &obj1); context.setContextProperty("b", &obj2); context.setContextProperty("c", &obj3); MyExpression expr(&context, "[a, b, c, 10]"); QVariant result = expr.value(); QCOMPARE(result.userType(), qMetaTypeId >()); QList list = qvariant_cast >(result); QCOMPARE(list.count(), 4); QCOMPARE(list.at(0), &obj1); QCOMPARE(list.at(1), &obj2); QCOMPARE(list.at(2), &obj3); QCOMPARE(list.at(3), (QObject *)0); } // Tests that modifying a context property will reevaluate expressions void tst_qmlecmascript::contextPropertiesTriggerReeval() { QmlContext context(engine.rootContext()); MyQmlObject object1; MyQmlObject object2; MyQmlObject *object3 = new MyQmlObject; object1.setStringProperty("Hello"); object2.setStringProperty("World"); context.setContextProperty("testProp", QVariant(1)); context.setContextProperty("testObj", &object1); context.setContextProperty("testObj2", object3); { MyExpression expr(&context, "testProp + 1"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant(2)); context.setContextProperty("testProp", QVariant(2)); QCOMPARE(expr.changed, true); QCOMPARE(expr.value(), QVariant(3)); } { MyExpression expr(&context, "testProp + testProp + testProp"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant(6)); context.setContextProperty("testProp", QVariant(4)); QCOMPARE(expr.changed, true); QCOMPARE(expr.value(), QVariant(12)); } { MyExpression expr(&context, "testObj.stringProperty"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant("Hello")); context.setContextProperty("testObj", &object2); QCOMPARE(expr.changed, true); QCOMPARE(expr.value(), QVariant("World")); } { MyExpression expr(&context, "testObj.stringProperty /**/"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant("World")); context.setContextProperty("testObj", &object1); QCOMPARE(expr.changed, true); QCOMPARE(expr.value(), QVariant("Hello")); } { MyExpression expr(&context, "testObj2"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant::fromValue((QObject *)object3)); } } void tst_qmlecmascript::objectPropertiesTriggerReeval() { QmlContext context(engine.rootContext()); MyQmlObject object1; MyQmlObject object2; MyQmlObject object3; context.setContextProperty("testObj", &object1); object1.setStringProperty(QLatin1String("Hello")); object2.setStringProperty(QLatin1String("Dog")); object3.setStringProperty(QLatin1String("Cat")); { MyExpression expr(&context, "testObj.stringProperty"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant("Hello")); object1.setStringProperty(QLatin1String("World")); QCOMPARE(expr.changed, true); QCOMPARE(expr.value(), QVariant("World")); } { MyExpression expr(&context, "testObj.objectProperty.stringProperty"); QCOMPARE(expr.changed, false); QCOMPARE(expr.value(), QVariant()); object1.setObjectProperty(&object2); QCOMPARE(expr.changed, true); expr.changed = false; QCOMPARE(expr.value(), QVariant("Dog")); object1.setObjectProperty(&object3); QCOMPARE(expr.changed, true); expr.changed = false; QCOMPARE(expr.value(), QVariant("Cat")); object1.setObjectProperty(0); QCOMPARE(expr.changed, true); expr.changed = false; QCOMPARE(expr.value(), QVariant()); object1.setObjectProperty(&object3); QCOMPARE(expr.changed, true); expr.changed = false; QCOMPARE(expr.value(), QVariant("Cat")); object3.setStringProperty("Donkey"); QCOMPARE(expr.changed, true); expr.changed = false; QCOMPARE(expr.value(), QVariant("Donkey")); } } void tst_qmlecmascript::deferredProperties() { QmlComponent component(&engine, TEST_FILE("deferredProperties.qml")); MyDeferredObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->value(), 0); QVERIFY(object->objectProperty() == 0); QVERIFY(object->objectProperty2() != 0); qmlExecuteDeferred(object); QCOMPARE(object->value(), 10); QVERIFY(object->objectProperty() != 0); MyQmlObject *qmlObject = qobject_cast(object->objectProperty()); QVERIFY(qmlObject != 0); } void tst_qmlecmascript::extensionObjects() { QmlComponent component(&engine, TEST_FILE("extensionObjects.qml")); MyExtendedObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->baseProperty(), 13); QCOMPARE(object->coreProperty(), 9); object->setProperty("extendedProperty", QVariant(11)); object->setProperty("baseExtendedProperty", QVariant(92)); QCOMPARE(object->coreProperty(), 11); QCOMPARE(object->baseProperty(), 92); } void tst_qmlecmascript::enums() { QmlComponent component(&engine, TEST_FILE("enums.1.qml")); QObject *object = component.create(); QVERIFY(object != 0); QCOMPARE(object->property("a").toInt(), 0); QCOMPARE(object->property("b").toInt(), 1); QCOMPARE(object->property("c").toInt(), 2); QCOMPARE(object->property("d").toInt(), 3); QCOMPARE(object->property("e").toInt(), 0); QCOMPARE(object->property("f").toInt(), 1); QCOMPARE(object->property("g").toInt(), 2); QCOMPARE(object->property("h").toInt(), 3); QCOMPARE(object->property("i").toInt(), 19); QCOMPARE(object->property("j").toInt(), 19); } void tst_qmlecmascript::valueTypeFunctions() { QmlComponent component(&engine, TEST_FILE("valueTypeFunctions.qml")); MyTypeObject *obj = qobject_cast(component.create()); QVERIFY(obj != 0); QCOMPARE(obj->rectProperty(), QRect(0,0,100,100)); QCOMPARE(obj->rectFProperty(), QRectF(0,0.5,100,99.5)); } /* Tests that writing a constant to a property with a binding on it disables the binding. */ void tst_qmlecmascript::constantsOverrideBindings() { // From ECMAScript { QmlComponent component(&engine, TEST_FILE("constantsOverrideBindings.1.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("c2").toInt(), 0); object->setProperty("c1", QVariant(9)); QCOMPARE(object->property("c2").toInt(), 9); emit object->basicSignal(); QCOMPARE(object->property("c2").toInt(), 13); object->setProperty("c1", QVariant(8)); QCOMPARE(object->property("c2").toInt(), 13); } // During construction { QmlComponent component(&engine, TEST_FILE("constantsOverrideBindings.2.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("c1").toInt(), 0); QCOMPARE(object->property("c2").toInt(), 10); object->setProperty("c1", QVariant(9)); QCOMPARE(object->property("c1").toInt(), 9); QCOMPARE(object->property("c2").toInt(), 10); } // From C++ { QmlComponent component(&engine, TEST_FILE("constantsOverrideBindings.3.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("c2").toInt(), 0); object->setProperty("c1", QVariant(9)); QCOMPARE(object->property("c2").toInt(), 9); object->setProperty("c2", QVariant(13)); QCOMPARE(object->property("c2").toInt(), 13); object->setProperty("c1", QVariant(7)); QCOMPARE(object->property("c1").toInt(), 7); QCOMPARE(object->property("c2").toInt(), 13); } } /* Tests that assigning a binding to a property that already has a binding causes the original binding to be disabled. */ void tst_qmlecmascript::outerBindingOverridesInnerBinding() { QmlComponent component(&engine, TEST_FILE("outerBindingOverridesInnerBinding.qml")); MyQmlObject *object = qobject_cast(component.create()); QVERIFY(object != 0); QCOMPARE(object->property("c1").toInt(), 0); QCOMPARE(object->property("c2").toInt(), 0); QCOMPARE(object->property("c3").toInt(), 0); object->setProperty("c1", QVariant(9)); QCOMPARE(object->property("c1").toInt(), 9); QCOMPARE(object->property("c2").toInt(), 0); QCOMPARE(object->property("c3").toInt(), 0); object->setProperty("c3", QVariant(8)); QCOMPARE(object->property("c1").toInt(), 9); QCOMPARE(object->property("c2").toInt(), 8); QCOMPARE(object->property("c3").toInt(), 8); } QTEST_MAIN(tst_qmlecmascript) #include "tst_qmlecmascript.moc"