/****************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt JavaScript to C++ compiler. ** ** $QT_BEGIN_LICENSE:COMM$ ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** $QT_END_LICENSE$ ** ******************************************************************************/ #include #include #include #include #include #include #if QT_CONFIG(process) #include #endif Q_IMPORT_QML_PLUGIN(TestTypesPlugin) class tst_QmlCppCodegen : public QObject { Q_OBJECT private slots: void simpleBinding(); void anchorsFill(); void signalHandler(); void idAccess(); void globals(); void multiLookup(); void enums(); void funcWithParams(); void intOverflow(); void stringLength(); void scopeVsObject(); void compositeTypeMethod(); void excessiveParameters(); void jsImport(); void jsmoduleImport(); void runInterpreted(); void methods(); void math(); void unknownParameter(); void array(); void equalsUndefined(); void conversions(); void interestingFiles_data(); void interestingFiles(); void extendedTypes(); void construct(); void contextParam(); void attachedType(); void componentReturnType(); void onAssignment(); void failures(); void enumScope(); void unusedAttached(); void attachedBaseEnum(); void nullAccess(); void interceptor(); void nonNotifyable(); void importsFromImportPath(); void aliasLookup(); void outOfBoundsArray(); void compositeSingleton(); void lotsOfRegisters(); void inPlaceDecrement(); void shifts(); void valueTypeProperty(); void propertyOfParent(); void accessModelMethodFromOutSide(); void functionArguments(); void bindingExpression(); void voidFunction(); void overriddenProperty(); void listLength(); void parentProperty(); void registerElimination(); void asCast(); void noQQmlData(); void scopeObjectDestruction(); void colorAsVariant(); void bindToValueType(); void undefinedResets(); void innerObjectNonShadowable(); void ownPropertiesNonShadowable(); void modulePrefix(); void colorString(); void urlString(); void callContextPropertyLookupResult(); void deadShoeSize(); void listIndices(); void jsMathObject(); void intEnumCompare(); }; void tst_QmlCppCodegen::simpleBinding() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/Test.qml"_qs)); QScopedPointer object(component.create()); QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); QCOMPARE(object->property("foo").toInt(), int(3)); { CppBaseClass *base = qobject_cast(object.data()); Q_ASSERT(base); QVERIFY(!base->cppProp.hasBinding()); QCOMPARE(base->cppProp.value(), 7); QVERIFY(base->cppProp2.hasBinding()); QCOMPARE(base->cppProp2.value(), 14); base->cppProp.setValue(9); QCOMPARE(base->cppProp.value(), 9); QCOMPARE(base->cppProp2.value(), 18); } } void tst_QmlCppCodegen::anchorsFill() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/anchorsFill.qml"_qs)); QScopedPointer object(component.create()); QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); QCOMPARE(object->property("width").toInt(), 234); QCOMPARE(object->children().length(), 2); QObject *child = object->children().front(); QVERIFY(child); QCOMPARE(child->property("width").toInt(), 234); QObject *newParent = object->children().back(); QVERIFY(newParent); QCOMPARE(newParent->property("width").toInt(), 47); child->setProperty("parent", QVariant::fromValue(newParent)); QCOMPARE(child->property("width").toInt(), 47); } void tst_QmlCppCodegen::signalHandler() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/signal.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->objectName(), QString()); QCOMPARE(object->property("ff").toInt(), 4); object->setObjectName(u"foo"_qs); QCOMPARE(object->property("ff").toInt(), 12); } void tst_QmlCppCodegen::idAccess() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/idAccess.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QVERIFY(object->property("y").toInt() != 48); QCOMPARE(object->property("y").toInt(), 12); object->setProperty("z", 13); QCOMPARE(object->property("y").toInt(), 13); object->setProperty("x", QVariant::fromValue(333)); QCOMPARE(object->property("y").toInt(), 48); // The binding was broken by setting the property object->setProperty("z", 14); QCOMPARE(object->property("y").toInt(), 48); QObject *ttt = qmlContext(object.data())->objectForName(u"ttt"_qs); QFont f = qvariant_cast(ttt->property("font")); QCOMPARE(f.pointSize(), 22); } static QByteArray arg1() { const QStringList args = QCoreApplication::instance()->arguments(); return args.length() > 1 ? args[1].toUtf8() : QByteArray("undefined"); } void tst_QmlCppCodegen::globals() { QQmlEngine engine; int exitCode = -1; QObject::connect(&engine, &QQmlEngine::exit, [&](int code) { exitCode = code; }); QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/globals.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); const QByteArray message = QByteArray("Start 2 ") + arg1(); QTest::ignoreMessage(QtDebugMsg, message.constData()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QTRY_COMPARE(exitCode, 0); QObject *application = qvariant_cast(object->property("application")); QVERIFY(application); QCOMPARE(QString::fromUtf8(application->metaObject()->className()), u"QQuickApplication"_qs); QTest::ignoreMessage(QtDebugMsg, "End"); QMetaObject::invokeMethod(application, "aboutToQuit"); const QVariant somewhere = object->property("somewhere"); QCOMPARE(somewhere.userType(), QMetaType::QUrl); QCOMPARE(qvariant_cast(somewhere).toString(), u"qrc:/somewhere/else.qml"_qs); const QVariant somewhereString = object->property("somewhereString"); QCOMPARE(somewhereString.userType(), QMetaType::QString); QCOMPARE(somewhereString.toString(), u"qrc:/somewhere/else.qml"_qs); const QVariant plain = object->property("plain"); QCOMPARE(plain.userType(), QMetaType::QUrl); QCOMPARE(qvariant_cast(plain).toString(), u"/not/here.qml"_qs); } void tst_QmlCppCodegen::multiLookup() { // Multiple lookups of singletons (Qt in this case) don't clash with one another. QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/immediateQuit.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); const QByteArray message = QByteArray("End: ") + arg1(); QTest::ignoreMessage(QtDebugMsg, message.constData()); QSignalSpy quitSpy(&engine, &QQmlEngine::quit); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(quitSpy.count(), 1); } void tst_QmlCppCodegen::enums() { QQmlEngine engine; { QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/Enums.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/Enums.qml:4:1: " "QML Enums: Layout must be attached to Item elements"); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); bool ok = false; QCOMPARE(object->property("appState").toInt(&ok), 2); QVERIFY(ok); QCOMPARE(object->property("color").toString(), u"blue"_qs); QTRY_COMPARE(object->property("appState").toInt(&ok), 1); QVERIFY(ok); QCOMPARE(object->property("color").toString(), u"green"_qs); const auto func = qmlAttachedPropertiesFunction( object.data(), QMetaType::fromName("QQuickLayout*").metaObject()); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/enumsInOtherObject.qml:4:25: " "QML Enums: Layout must be attached to Item elements"); QObject *attached = qmlAttachedPropertiesObject(object.data(), func); const QVariant prop = attached->property("alignment"); QVERIFY(prop.isValid()); QCOMPARE(qvariant_cast(prop), Qt::AlignCenter); } { QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/enumsInOtherObject.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("color").toString(), u"blue"_qs); QTRY_COMPARE(object->property("color").toString(), u"green"_qs); } } void tst_QmlCppCodegen::funcWithParams() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/funcWithParams.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("bar").toInt(), 30); } void tst_QmlCppCodegen::intOverflow() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/intOverflow.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("a").toDouble(), 1.09951162778e+12); QCOMPARE(object->property("b").toInt(), 5); } void tst_QmlCppCodegen::stringLength() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/stringLength.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("stringLength").toInt(), 8); } void tst_QmlCppCodegen::scopeVsObject() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/scopeVsObject.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("objectName").toString(), u"foobar"_qs); } void tst_QmlCppCodegen::compositeTypeMethod() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/compositeTypeMethod.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QSignalSpy spy(object.data(), SIGNAL(foo())); QTRY_VERIFY(spy.count() > 0); } void tst_QmlCppCodegen::excessiveParameters() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/excessiveParameters.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QSignalSpy spy(object.data(), SIGNAL(foo())); QTRY_VERIFY(spy.count() > 0); } void tst_QmlCppCodegen::jsImport() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/jsimport.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("value").toInt(), 42); } void tst_QmlCppCodegen::jsmoduleImport() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/jsmoduleimport.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("ok").toBool(), true); QVariant okFunc = object->property("okFunc"); QCOMPARE(okFunc.metaType(), QMetaType::fromType()); QJSValue val = engine.toScriptValue(okFunc); QJSValue result = val.call(); QVERIFY(result.isBool()); QVERIFY(result.toBool()); } void tst_QmlCppCodegen::methods() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/methods.qml"_qs)); QVERIFY(component.isReady()); QTest::ignoreMessage(QtDebugMsg, "The Bar"); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"TypeError: .* is not a function"_qs)); QScopedPointer obj(component.create()); QVERIFY(obj); BirthdayParty *party(qobject_cast(obj.data())); QVERIFY(party && party->host()); QCOMPARE(party->guestCount(), 5); bool foundGreen = false; bool foundFoo = false; for (int ii = 0; ii < party->guestCount(); ++ii) { if (party->guest(ii)->name() == u"William Green"_qs) foundGreen = true; if (party->guest(ii)->name() == u"The Foo"_qs) foundFoo = true; } QVERIFY(foundGreen); QVERIFY(foundFoo); QCOMPARE(obj->property("n1").toString(), u"onGurk"_qs); QCOMPARE(obj->property("n2").toString(), u"onSemmeln"_qs); QCOMPARE(obj->property("n3"), QVariant()); { QVariant ret; obj->metaObject()->invokeMethod(obj.data(), "retrieveVar", Q_RETURN_ARG(QVariant, ret)); QCOMPARE(ret.typeId(), QMetaType::QString); QCOMPARE(ret.toString(), u"Jack Smith"_qs); } { QString ret; obj->metaObject()->invokeMethod(obj.data(), "retrieveString", Q_RETURN_ARG(QString, ret)); QCOMPARE(ret, u"Jack Smith"_qs); } QCOMPARE(party->host()->shoeSize(), 12); obj->metaObject()->invokeMethod(obj.data(), "storeElement"); QCOMPARE(party->host()->shoeSize(), 13); QJSManagedValue v = engine.toManagedValue(obj->property("dresses")); QVERIFY(v.isArray()); QJSManagedValue inner(v.property(2), &engine); QVERIFY(inner.isArray()); QCOMPARE(inner.property(0).toInt(), 1); QCOMPARE(inner.property(1).toInt(), 2); QCOMPARE(inner.property(2).toInt(), 3); QCOMPARE(obj->property("enumValue").toInt(), 2); } void tst_QmlCppCodegen::math() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/math.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("a").toInt(), 9); QCOMPARE(object->property("b").toDouble(), 50.0 / 22.0); } void tst_QmlCppCodegen::unknownParameter() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/unknownParameter.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("cppProp").toInt(), 18); } void tst_QmlCppCodegen::array() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/array.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); const QJSValue value1 = object->property("values1").value(); QVERIFY(value1.isArray()); QCOMPARE(value1.property(u"length"_qs).toInt(), 3); QCOMPARE(value1.property(0).toInt(), 1); QCOMPARE(value1.property(1).toInt(), 2); QCOMPARE(value1.property(2).toInt(), 3); const QJSValue value2 = object->property("values2").value(); QVERIFY(value2.isArray()); QCOMPARE(value2.property(u"length"_qs).toInt(), 0); } void tst_QmlCppCodegen::equalsUndefined() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/equalsUndefined.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("a").toInt(), 50); QCOMPARE(object->property("b").toInt(), 5000); } void tst_QmlCppCodegen::conversions() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/conversions.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/conversions.qml:42: TypeError: Type error"); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QVERIFY(object->property("nullIsNull").toBool()); QVERIFY(!object->property("intIsNull").toBool()); QVERIFY(!object->property("zeroIsNull").toBool()); QVERIFY(!object->property("arrayIsNull").toBool()); QVERIFY(!object->property("objectIsNull").toBool()); QVERIFY(!object->property("nullIsNotNull").toBool()); QVERIFY(object->property("intIsNotNull").toBool()); QVERIFY(object->property("zeroIsNotNull").toBool()); QVERIFY(object->property("arrayIsNotNull").toBool()); QVERIFY(object->property("objectIsNotNull").toBool()); QVERIFY(!object->property("neNull").toBool()); QVERIFY(object->property("eqNull").toBool()); QVERIFY(!object->property("boolEqualsBool").toBool()); QVERIFY(object->property("boolNotEqualsBool").toBool()); QCOMPARE(object->property("cmpEqInt").toInt(), 17); QCOMPARE(object->property("undefinedType").toString(), u"undefined"_qs); QCOMPARE(object->property("booleanType").toString(), u"boolean"_qs); QCOMPARE(object->property("numberType").toString(), u"number"_qs); QCOMPARE(object->property("stringType").toString(), u"string"_qs); QCOMPARE(object->property("objectType").toString(), u"object"_qs); QCOMPARE(object->property("symbolType").toString(), u"symbol"_qs); QJSManagedValue obj = engine.toManagedValue(object->property("anObject")); QCOMPARE(obj.property(u"a"_qs).toInt(), 12); QCOMPARE(obj.property(u"b"_qs).toInt(), 14); QCOMPARE(obj.property(u"c"_qs).toString(), u"somestring"_qs); QVERIFY(object->property("aInObject").toBool()); QVERIFY(object->property("lengthInArray").toBool()); QVERIFY(!object->property("fooInNumber").isValid()); QVERIFY(!object->property("numberInObject").toBool()); QVERIFY(object->property("numberInArray").toBool()); QCOMPARE(object->property("varPlusVar").toDouble(), 2.0); QVERIFY(qIsNaN(object->property("varMinusVar").toDouble())); QVERIFY(qIsNaN(object->property("varTimesVar").toDouble())); QCOMPARE(object->property("varDivVar").toDouble(), 0.0); const QVariant stringPlusString = object->property("stringPlusString"); QCOMPARE(stringPlusString.typeId(), QMetaType::QString); QCOMPARE(stringPlusString.toString(), u"1220"_qs); const QVariant stringMinusString = object->property("stringMinusString"); QCOMPARE(stringMinusString.typeId(), QMetaType::Double); QCOMPARE(stringMinusString.toDouble(), -8.0); const QVariant stringTimesString = object->property("stringTimesString"); QCOMPARE(stringTimesString.typeId(), QMetaType::Double); QCOMPARE(stringTimesString.toDouble(), 240.0); const QVariant stringDivString = object->property("stringDivString"); QCOMPARE(stringDivString.typeId(), QMetaType::Double); QCOMPARE(stringDivString.toDouble(), 0.6); QCOMPARE(object->property("uglyString").toString(), u"with\nnewlinewith\"quotwith\\slashes"_qs); QCOMPARE(qvariant_cast(object->property("nullObject1")), nullptr); QCOMPARE(qvariant_cast(object->property("nullObject2")), object.data()); QCOMPARE(object->property("passObjectLiteral").toInt(), 11); QCOMPARE(object->property("passArrayLiteral").toInt(), 4); QCOMPARE(object->property("doneStuff").toInt(), 19); QVariantList modulos = object->property("modulos").toList(); QCOMPARE(modulos.length(), 7); QCOMPARE(modulos[0].userType(), QMetaType::Double); QCOMPARE(modulos[0].toDouble(), 0.0); QCOMPARE(modulos[1].userType(), QMetaType::Int); QCOMPARE(modulos[1].toInt(), 0); QCOMPARE(modulos[2].userType(), QMetaType::Double); QCOMPARE(modulos[2].toDouble(), 2.4); QCOMPARE(modulos[3].userType(), QMetaType::Double); QCOMPARE(modulos[3].toDouble(), -0.4); QCOMPARE(modulos[4].userType(), QMetaType::Double); QCOMPARE(modulos[4].toDouble(), 0.0); QCOMPARE(modulos[5].userType(), QMetaType::Double); QVERIFY(qIsNaN(modulos[5].toDouble())); QCOMPARE(modulos[6].userType(), QMetaType::Double); QVERIFY(qIsNaN(modulos[6].toDouble())); QVariantList unaryOps = object->property("unaryOps").toList(); QCOMPARE(unaryOps.length(), 6); QCOMPARE(unaryOps[0].userType(), QMetaType::Double); QCOMPARE(unaryOps[0].toDouble(), 1221); QCOMPARE(unaryOps[1].userType(), QMetaType::Double); QCOMPARE(unaryOps[1].toDouble(), 1219); QCOMPARE(unaryOps[2].userType(), QMetaType::Double); QCOMPARE(unaryOps[2].toDouble(), 1220); QCOMPARE(unaryOps[3].userType(), QMetaType::Double); QCOMPARE(unaryOps[3].toDouble(), -1220); QCOMPARE(unaryOps[4].userType(), QMetaType::Double); QCOMPARE(unaryOps[4].toDouble(), 1220); QCOMPARE(unaryOps[5].userType(), QMetaType::Double); QCOMPARE(unaryOps[5].toDouble(), 1220); QVariant undef; QVERIFY(object->metaObject()->invokeMethod(object.data(), "retUndefined", Q_RETURN_ARG(QVariant, undef))); QVERIFY(!undef.isValid()); } void tst_QmlCppCodegen::interestingFiles_data() { QTest::addColumn("file"); QTest::addRow("conversions2") << u"conversions2.qml"_qs; QTest::addRow("TestCase") << u"TestCase.qml"_qs; QTest::addRow("layouts") << u"layouts.qml"_qs; QTest::addRow("interactive") << u"interactive.qml"_qs; QTest::addRow("Panel") << u"Panel.qml"_qs; QTest::addRow("ProgressBar") << u"ProgressBar/ProgressBar.ui.qml"_qs; QTest::addRow("Root") << u"ProgressBar/Root.qml"_qs; QTest::addRow("noscope") << u"noscope.qml"_qs; QTest::addRow("dynamicscene") << u"dynamicscene.qml"_qs; QTest::addRow("curlygrouped") << u"curlygrouped.qml"_qs; } void tst_QmlCppCodegen::interestingFiles() { QFETCH(QString, file); QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/%1"_qs.arg(file))); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); } void tst_QmlCppCodegen::extendedTypes() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/extendedTypes.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QTest::ignoreMessage(QtDebugMsg, "6 QSizeF(10, 20) 30"); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("a").toInt(), 6); QCOMPARE(qvariant_cast(object->property("b")), QSizeF(10, 20)); QCOMPARE(object->property("c").toInt(), 30); QCOMPARE(object->property("d").toString(), u"QSizeF(10, 20)"_qs); QCOMPARE(object->property("e").toInt(), 2); } void tst_QmlCppCodegen::construct() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/construct.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); const QJSManagedValue v = engine.toManagedValue(object->property("foo")); QVERIFY(v.isError()); QCOMPARE(v.toString(), u"Error: bar"_qs); QCOMPARE(object->property("aaa").toInt(), 12); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/construct.qml:9: Error: ouch"); object->metaObject()->invokeMethod(object.data(), "ouch"); QCOMPARE(object->property("aaa").toInt(), 13); } void tst_QmlCppCodegen::contextParam() { // The compiler cannot resolve context parameters. // Make sure the binding is interpreted. QQmlEngine engine; QVariantMap m; m.insert(u"foo"_qs, 10); engine.rootContext()->setContextProperty(u"contextParam"_qs, m); QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/contextParam.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("foo").toInt(), 10); } void tst_QmlCppCodegen::attachedType() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/text.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("dayz").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); QCOMPARE(object->property("oParty").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); QObject *party = qvariant_cast(object->property("party")); QVERIFY(party); QCOMPARE(party->property("eee").toInt(), 21); QCOMPARE(party->property("fff").toInt(), 33); QCOMPARE(object->property("ggg").toInt(), 37); } void tst_QmlCppCodegen::componentReturnType() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/componentReturnType.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QCOMPARE(object->property("count").toInt(), 10); QCOMPARE(QQmlListReference(object.data(), "children", &engine).count(), 11); } void tst_QmlCppCodegen::onAssignment() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/pressAndHoldButton.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QCOMPARE(object->property("pressed").toBool(), false); QCOMPARE(object->property("scale").toDouble(), 1.0); object->metaObject()->invokeMethod(object.data(), "press"); QTRY_COMPARE(object->property("pressed").toBool(), true); QCOMPARE(object->property("scale").toDouble(), 0.9); object->metaObject()->invokeMethod(object.data(), "release"); QCOMPARE(object->property("pressed").toBool(), false); QCOMPARE(object->property("scale").toDouble(), 1.0); } namespace QmlCacheGeneratedCode { namespace _0x5f_TestTypes_failures_qml { extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[]; } } void tst_QmlCppCodegen::failures() { const auto &aotFailure = QmlCacheGeneratedCode::_0x5f_TestTypes_failures_qml::aotBuiltFunctions[0]; QVERIFY(aotFailure.argumentTypes.isEmpty()); QVERIFY(!aotFailure.functionPtr); QCOMPARE(aotFailure.index, 0); } void tst_QmlCppCodegen::enumScope() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/enumScope.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QCOMPARE(object->property("flow").toInt(), 1); } void tst_QmlCppCodegen::unusedAttached() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/unusedAttached.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); const auto func = qmlAttachedPropertiesFunction( object.data(), QMetaType::fromName("QQuickKeyNavigationAttached*").metaObject()); QObject *attached = qmlAttachedPropertiesObject(object.data(), func); const QVariant prop = attached->property("priority"); QVERIFY(prop.isValid()); QCOMPARE(QByteArray(prop.metaType().name()), "QQuickKeyNavigationAttached::Priority"); bool ok = false; QCOMPARE(prop.toInt(&ok), 0); QVERIFY(ok); } void tst_QmlCppCodegen::attachedBaseEnum() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/attachedBaseEnum.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QObject *drag = qvariant_cast(object->property("drag")); QVERIFY(drag); // Drag.YAxis is 2, but we cannot #include it here. bool ok = false; QCOMPARE(drag->property("axis").toInt(&ok), 2); QVERIFY(ok); } void tst_QmlCppCodegen::nullAccess() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/nullAccess.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/nullAccess.qml:4:5: TypeError: " "Cannot read property 'width' of null"); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/nullAccess.qml:5:5: TypeError: " "Cannot read property 'height' of null"); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/nullAccess.qml:6: TypeError: Value is null and " "could not be converted to an object"); QScopedPointer object(component.create()); QCOMPARE(object->property("width").toDouble(), 0.0); QCOMPARE(object->property("height").toDouble(), 0.0); } void tst_QmlCppCodegen::interceptor() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/interceptor.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("x").toInt(), 100); QCOMPARE(object->property("y").toInt(), 100); QVERIFY(object->property("width").toInt() != 200); QVERIFY(object->property("height").toInt() != 200); QVERIFY(object->property("qProperty1").toInt() != 300); QVERIFY(object->property("qProperty2").toInt() != 300); QTRY_COMPARE(object->property("width").toInt(), 200); QTRY_COMPARE(object->property("height").toInt(), 200); QTRY_COMPARE(object->property("qProperty1").toInt(), 300); QTRY_COMPARE(object->property("qProperty2").toInt(), 300); } void tst_QmlCppCodegen::nonNotifyable() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/nonNotifyable.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(qvariant_cast(object->property("dayz")), QDateTime(QDate(2121, 1, 12), QTime())); QCOMPARE(qvariant_cast(object->property("oParty")), QDateTime(QDate(2111, 12, 11), QTime())); } void tst_QmlCppCodegen::importsFromImportPath() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/importsFromImportPath.qml"_qs)); // We might propagate the import path, eventually, but for now instantiating is not important. // If the compiler accepts the file, it's probably fine. QVERIFY(component.isError()); QCOMPARE(component.errorString(), u"qrc:/TestTypes/importsFromImportPath.qml:1 module \"Module\" is not installed\n"_qs); } void tst_QmlCppCodegen::aliasLookup() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/aliasLookup.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); const QVariant t = object->property("t"); QCOMPARE(t.metaType(), QMetaType::fromType()); QCOMPARE(t.toString(), u"12"_qs); } void tst_QmlCppCodegen::outOfBoundsArray() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/outOfBounds.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QTest::ignoreMessage(QtDebugMsg, "oob undefined"); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QVERIFY(object->metaObject()->indexOfProperty("oob") > 0); QVERIFY(!object->property("oob").isValid()); } void tst_QmlCppCodegen::compositeSingleton() { QQmlEngine engine; engine.addImportPath(u":/TestTypes/imports/"_qs); QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/compositesingleton.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer o(component.create()); QCOMPARE(o->property("x").toDouble(), 4.5); QCOMPARE(o->property("y").toDouble(), 10.0); QCOMPARE(o->property("smooth").toBool(), true); } void tst_QmlCppCodegen::lotsOfRegisters() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/page.qml"_qs)); QVERIFY2(!component.isError(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); const auto compare = [&]() { const qreal implicitBackgroundWidth = object->property("implicitBackgroundWidth").toDouble(); const qreal leftInset = object->property("leftInset").toDouble(); const qreal rightInset = object->property("rightInset").toDouble(); const qreal contentWidth = object->property("contentWidth").toDouble(); const qreal leftPadding = object->property("leftPadding").toDouble(); const qreal rightPadding = object->property("rightPadding").toDouble(); const qreal implicitFooterWidth = object->property("implicitFooterWidth").toDouble(); const qreal implicitHeaderWidth = object->property("implicitHeaderWidth").toDouble(); const qreal implicitWidth = object->property("implicitWidth").toDouble(); QCOMPARE(implicitWidth, qMax(qMax(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding), qMax(implicitHeaderWidth, implicitFooterWidth))); }; compare(); const QList props = { "leftInset", "rightInset", "contentWidth", "leftPadding", "rightPadding" }; for (int i = 0; i < 100; ++i) { QVERIFY(object->setProperty(props[i % props.length()], (i * 17) % 512)); compare(); } } void tst_QmlCppCodegen::inPlaceDecrement() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/dialog.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QObject *header = qvariant_cast(object->property("header")); QVERIFY(header); QObject *background = qvariant_cast(header->property("background")); QObject *parent = qvariant_cast(background->property("parent")); QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1); QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1); QVERIFY(object->setProperty("width", QVariant::fromValue(17))); QVERIFY(parent->property("width").toInt() > 0); QVERIFY(object->setProperty("height", QVariant::fromValue(53))); QVERIFY(parent->property("height").toInt() > 0); QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1); QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1); QCOMPARE(object->property("a").toInt(), 1024); } void tst_QmlCppCodegen::shifts() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/shifts.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("a").toInt(), 9728); QCOMPARE(object->property("b").toInt(), 4864); QCOMPARE(object->property("c").toInt(), 19448); QCOMPARE(object->property("d").toInt(), 9731); QCOMPARE(object->property("e").toInt(), 0); } void tst_QmlCppCodegen::valueTypeProperty() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/valueTypeProperty.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QFont font = qvariant_cast(object->property("font")); QCOMPARE(object->property("foo").toString(), font.family()); font.setFamily(u"Bar"_qs); object->setProperty("font", QVariant::fromValue(font)); QCOMPARE(object->property("foo").toString(), u"Bar"_qs); } void tst_QmlCppCodegen::propertyOfParent() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/RootWithoutId.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QObject *child = qmlContext(object.data())->objectForName(u"item"_qs); bool expected = false; for (int i = 0; i < 3; ++i) { const QVariant foo = object->property("foo"); QCOMPARE(foo.metaType(), QMetaType::fromType()); QCOMPARE(foo.toBool(), expected); const QVariant bar = object->property("bar"); QCOMPARE(bar.metaType(), QMetaType::fromType()); QCOMPARE(bar.toBool(), expected); const QVariant visible = child->property("visible"); QCOMPARE(visible.metaType(), QMetaType::fromType()); QCOMPARE(visible.toBool(), expected); expected = !expected; object->setProperty("foo", expected); } } void tst_QmlCppCodegen::accessModelMethodFromOutSide() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/AccessModelMethodsFromOutside.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QTest::ignoreMessage(QtDebugMsg, "3"); QTest::ignoreMessage(QtDebugMsg, "Apple"); QScopedPointer object(component.create()); QCOMPARE(object->property("cost1").toDouble(), 3); QCOMPARE(object->property("name1").toString(), u"Orange"_qs); QCOMPARE(object->property("cost2").toDouble(), 1.95); QCOMPARE(object->property("name2").toString(), u"Banana"_qs); } void tst_QmlCppCodegen::functionArguments() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/Dummy.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); const QMetaObject *metaObject = object->metaObject(); int result; int a = 1; bool b = false; QObject *c = nullptr; double d = -1.2; int e = 3; const QByteArray className = QByteArray(metaObject->className()) + '*'; metaObject->invokeMethod( object.data(), "someFunction", Q_RETURN_ARG(int, result), Q_ARG(int, a), Q_ARG(bool, b), QGenericArgument(className, &c), Q_ARG(double, d), Q_ARG(int, e)); QCOMPARE(result, 42); QString astr = u"foo"_qs; QString bstr = u"bar"_qs; QString concatenated; metaObject->invokeMethod( object.data(), "concat", Q_RETURN_ARG(QString, concatenated), Q_ARG(QString, astr), Q_ARG(QString, bstr)); QCOMPARE(concatenated, u"foobar"_qs); } void tst_QmlCppCodegen::bindingExpression() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/BindingExpression.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QObject *child = qmlContext(object.data())->objectForName(u"child"_qs); double width = 200; double y = 10; for (int i = 0; i < 10; ++i) { QCOMPARE(object->property("width").toDouble(), width); QCOMPARE(object->property("height").toDouble(), width); QCOMPARE(object->property("y").toDouble(), y); const double childY = y + (width - 100) / 2; QCOMPARE(child->property("y").toDouble(), childY); QCOMPARE(object->property("mass"), childY > 100 ? u"heavy"_qs : u"light"_qs); QCOMPARE(object->property("test_division").toDouble(), width / 1000 + 50); QCOMPARE(object->property("test_ternary").toDouble(), 2.2); const int test_switch = object->property("test_switch").toInt(); switch (int(width) % 3) { case 0: QCOMPARE(test_switch, 130); break; case 1: QCOMPARE(test_switch, 380); break; case 2: QCOMPARE(test_switch, 630); break; } width = 200 * i; y = 10 + i; object->setProperty("width", width); object->setProperty("y", y); } } void tst_QmlCppCodegen::voidFunction() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/voidfunction.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QVERIFY(object->objectName().isEmpty()); object->metaObject()->invokeMethod(object.data(), "doesNotReturnValue"); QCOMPARE(object->objectName(), u"barbar"_qs); } void tst_QmlCppCodegen::overriddenProperty() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/childobject.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->objectName(), u"kraut"_qs); QCOMPARE(object->property("doneThing").toInt(), 5); QCOMPARE(object->property("usingFinal").toInt(), 5); auto checkAssignment = [&]() { const QString newName = u"worscht"_qs; QMetaObject::invokeMethod(object.data(), "setChildObjectName", Q_ARG(QString, newName)); QCOMPARE(object->objectName(), newName); }; checkAssignment(); ObjectWithMethod *benign = new ObjectWithMethod(object.data()); benign->theThing = 10; benign->setObjectName(u"cabbage"_qs); object->setProperty("child", QVariant::fromValue(benign)); QCOMPARE(object->objectName(), u"cabbage"_qs); checkAssignment(); QCOMPARE(object->property("doneThing").toInt(), 10); QCOMPARE(object->property("usingFinal").toInt(), 10); OverriddenObjectName *evil = new OverriddenObjectName(object.data()); QTest::ignoreMessage(QtWarningMsg, "Final member fff is overridden in class OverriddenObjectName. " "The override won't be used."); object->setProperty("child", QVariant::fromValue(evil)); QCOMPARE(object->objectName(), u"borschtsch"_qs); checkAssignment(); QCOMPARE(object->property("doneThing").toInt(), 7); QCOMPARE(object->property("usingFinal").toInt(), 5); } void tst_QmlCppCodegen::listLength() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/listlength.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("l").toInt(), 2); } void tst_QmlCppCodegen::parentProperty() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/parentProp.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QCOMPARE(object->property("c").toInt(), 11); QCOMPARE(object->property("i").toInt(), 22); object->setProperty("a", QVariant::fromValue(22)); QCOMPARE(object->property("c").toInt(), 28); object->setProperty("implicitWidth", QVariant::fromValue(14)); QCOMPARE(object->property("i").toInt(), 26); QObject *child = qmlContext(object.data())->objectForName(u"child"_qs); QObject *sibling = qmlContext(object.data())->objectForName(u"sibling"_qs); QObject *evil = qmlContext(object.data())->objectForName(u"evil"_qs); child->setProperty("parent", QVariant::fromValue(sibling)); QCOMPARE(child->property("b").toInt(), 0); QCOMPARE(child->property("i").toInt(), 28); QCOMPARE(object->property("i").toInt(), 56); child->setProperty("parent", QVariant::fromValue(evil)); QCOMPARE(child->property("b").toInt(), 5994); QCOMPARE(object->property("c").toInt(), 5996); QCOMPARE(child->property("i").toInt(), 443); QCOMPARE(object->property("i").toInt(), 886); { QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/specificParent.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QCOMPARE(rootObject->property("a").toReal(), 77.0); } } void tst_QmlCppCodegen::registerElimination() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/registerelimination.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); // Increment of 23 hits both 0 and 460 for (int input = -23; input < 700; input += 23) { object->setProperty("input", input); if (input <= 0 || input >= 460) QCOMPARE(object->property("output").toInt(), 459); else QCOMPARE(object->property("output").toInt(), input); } } void tst_QmlCppCodegen::asCast() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/asCast.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QScopedPointer root(component.create()); QVERIFY(!root.isNull()); QQmlContext *context = qmlContext(root.data()); const QObject *object = context->objectForName(u"object"_qs); const QObject *item = context->objectForName(u"item"_qs); const QObject *rectangle = context->objectForName(u"rectangle"_qs); const QObject *dummy = context->objectForName(u"dummy"_qs); QCOMPARE(qvariant_cast(root->property("objectAsObject")), object); QCOMPARE(qvariant_cast(root->property("objectAsItem")), nullptr); QCOMPARE(qvariant_cast(root->property("objectAsRectangle")), nullptr); QCOMPARE(qvariant_cast(root->property("objectAsDummy")), nullptr); QCOMPARE(qvariant_cast(root->property("itemAsObject")), item); QCOMPARE(qvariant_cast(root->property("itemAsItem")), item); QCOMPARE(qvariant_cast(root->property("itemAsRectangle")), nullptr); QCOMPARE(qvariant_cast(root->property("itemAsDummy")), nullptr); QCOMPARE(qvariant_cast(root->property("rectangleAsObject")), rectangle); QCOMPARE(qvariant_cast(root->property("rectangleAsItem")), rectangle); QCOMPARE(qvariant_cast(root->property("rectangleAsRectangle")), rectangle); QCOMPARE(qvariant_cast(root->property("rectangleAsDummy")), nullptr); QCOMPARE(qvariant_cast(root->property("dummyAsObject")), dummy); QCOMPARE(qvariant_cast(root->property("dummyAsItem")), dummy); QCOMPARE(qvariant_cast(root->property("dummyAsRectangle")), nullptr); QCOMPARE(qvariant_cast(root->property("dummyAsDummy")), dummy); } void tst_QmlCppCodegen::noQQmlData() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/noQQmlData.qml"_qs)); QVERIFY2(component.isReady(), component.errorString().toUtf8()); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/noQQmlData.qml:7: TypeError: " "Cannot read property 'name' of null"); QScopedPointer root(component.create()); QVERIFY(!root.isNull()); BirthdayParty *party = qobject_cast(root.data()); QVERIFY(party != nullptr); QCOMPARE(party->host(), nullptr); QCOMPARE(party->property("n").toString(), QString()); Person *host1 = new Person(party); party->setHost(host1); QCOMPARE(party->property("n").toString(), u"Bart in da house!"_qs); host1->setName(u"Marge"_qs); QCOMPARE(party->property("n").toString(), u"Marge in da house!"_qs); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/noQQmlData.qml:7: TypeError: " "Cannot read property 'name' of null"); // Doesn't crash party->setHost(nullptr); // Lookups are initialized now, and we introduce an object without QQmlData Person *host2 = new Person(party); party->setHost(host2); QCOMPARE(party->property("n").toString(), u"Bart in da house!"_qs); host2->setName(u"Homer"_qs); QCOMPARE(party->property("n").toString(), u"Homer in da house!"_qs); QMetaObject::invokeMethod(party, "burn"); engine.collectGarbage(); // Does not crash party->setProperty("inDaHouse", u" burns!"_qs); // Mr Burns may or may not burn, depending on whether we use lookups. // If using lookups, the binding is aborted when we find the isQueuedForDeletion flag. // If reading the property directly, we don't have to care about it. QVERIFY(party->property("n").toString().startsWith(u"Mr Burns"_qs)); } void tst_QmlCppCodegen::scopeObjectDestruction() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/fileDialog.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QObject *dialog = rootObject->property("dialog").value(); QVERIFY(dialog); // We cannot check the warning messages. The AOT compiled code complains about reading the // "parent" property of an object scheduled for deletion. The runtime silently returns undefined // at that point and then complains about not being able to read a property on undefined. // Doesn't crash, even though it triggers bindings on scope objects scheduled for deletion. QMetaObject::invokeMethod(dialog, "open"); } static void checkColorProperties(QQmlComponent *component) { QVERIFY2(component->isReady(), qPrintable(component->errorString())); QScopedPointer rootObject(component->create()); QVERIFY(rootObject); const QMetaObject *mo = QMetaType::fromName("QQuickIcon").metaObject(); QVERIFY(mo != nullptr); const QMetaProperty prop = mo->property(mo->indexOfProperty("color")); QVERIFY(prop.isValid()); const QVariant a = rootObject->property("a"); QVERIFY(a.isValid()); const QVariant iconColor = prop.readOnGadget(rootObject->property("icon").data()); QVERIFY(iconColor.isValid()); const QMetaType colorType = QMetaType::fromName("QColor"); QVERIFY(colorType.isValid()); QCOMPARE(a.metaType(), colorType); QCOMPARE(iconColor.metaType(), colorType); QCOMPARE(iconColor, a); } void tst_QmlCppCodegen::colorAsVariant() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/colorAsVariant.qml"_qs)); checkColorProperties(&component); } void tst_QmlCppCodegen::bindToValueType() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/bindToValueType.qml"_qs)); checkColorProperties(&component); } void tst_QmlCppCodegen::undefinedResets() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/undefinedResets.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); Person *person = qobject_cast(rootObject.data()); QVERIFY(person); QCOMPARE(person->shoeSize(), 0); QCOMPARE(person->name(), u"Marge"_qs); person->setShoeSize(11); QCOMPARE(person->shoeSize(), 11); QCOMPARE(person->name(), u"Bart"_qs); person->setShoeSize(10); QCOMPARE(person->shoeSize(), 10); QCOMPARE(person->name(), u"Marge"_qs); } void tst_QmlCppCodegen::innerObjectNonShadowable() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/ownProperty.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QCOMPARE(rootObject->objectName(), u"foo"_qs); } void tst_QmlCppCodegen::ownPropertiesNonShadowable() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/overriddenMember.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QCOMPARE(rootObject->property("ppp").toInt(), 16); QCOMPARE(rootObject->property("ppp2").toInt(), 9); QCOMPARE(rootObject->property("ppp3").toInt(), 12); } void tst_QmlCppCodegen::modulePrefix() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/modulePrefix.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QCOMPARE(rootObject->property("foo").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); QCOMPARE(rootObject->property("bar").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime())); QCOMPARE(rootObject->property("baz").toString(), QStringLiteral("ItIsTheSingleton")); } void tst_QmlCppCodegen::colorString() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/colorString.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QCOMPARE(qvariant_cast(rootObject->property("c")), QColor::fromRgb(0xdd, 0xdd, 0xdd)); QCOMPARE(qvariant_cast(rootObject->property("d")), QColor::fromRgb(0xaa, 0xaa, 0xaa)); QCOMPARE(qvariant_cast(rootObject->property("e")), QColor::fromRgb(0x11, 0x22, 0x33)); } void tst_QmlCppCodegen::urlString() { QQmlEngine engine; QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/urlString.qml"_qs)); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer rootObject(component.create()); QVERIFY(rootObject); QCOMPARE(qvariant_cast(rootObject->property("c")), QUrl(u"http://dddddd.com"_qs)); QCOMPARE(qvariant_cast(rootObject->property("d")), QUrl(u"http://aaaaaa.com"_qs)); QCOMPARE(qvariant_cast(rootObject->property("e")), QUrl(u"http://a112233.de"_qs)); } void tst_QmlCppCodegen::callContextPropertyLookupResult() { QQmlEngine engine; QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/callContextPropertyLookupResult.qml"_qs)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(o); QVERIFY(qvariant_cast(o->property("c")) != nullptr); } void tst_QmlCppCodegen::deadShoeSize() { QQmlEngine engine; QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/deadShoeSize.qml"_qs)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/deadShoeSize.qml:5: Error: ouch"); QScopedPointer o(c.create()); QVERIFY(o); QCOMPARE(o->property("shoeSize").toInt(), 0); } void tst_QmlCppCodegen::listIndices() { QQmlEngine engine; QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/listIndices.qml"_qs)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(o); QQmlListReference list(o.data(), "items"); QCOMPARE(list.count(), 3); for (int i = 0; i < 3; ++i) QCOMPARE(list.at(i), o.data()); QCOMPARE(o->property("numItems").toInt(), 3); } void tst_QmlCppCodegen::jsMathObject() { QQmlEngine engine; QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/jsMathObject.qml"_qs)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(o); const double inputs[] = { qQNaN(), -qInf(), std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min(), -1000.2, -100, -2, -1.333, -1, -0.84, -0.5, std::copysign(0.0, -1), 0.0, 0.5, 0.77, 1, 1.4545, 2, 199, 2002.13, std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max(), }; QJSManagedValue math(engine.globalObject().property(QStringLiteral("Math")), &engine); const QMetaObject *metaObject = o->metaObject(); QString name; for (double a : inputs) { for (double b : inputs) { o->setProperty("a", a); o->setProperty("b", b); for (int i = 0, end = metaObject->propertyCount(); i != end; ++i) { const QMetaProperty prop = metaObject->property(i); name = QString::fromUtf8(prop.name()); if (!math.hasProperty(name)) continue; const double result = prop.read(o.data()).toDouble(); if (name == QStringLiteral("random")) { QVERIFY(result >= 0.0 && result < 1.0); continue; } QJSManagedValue jsMethod(math.property(name), &engine); const double expected = jsMethod.call(QJSValueList {a, b}).toNumber(); QCOMPARE(result, expected); } } } if (QTest::currentTestFailed()) qDebug() << name << "failed."; } void tst_QmlCppCodegen::intEnumCompare() { QQmlEngine engine; { QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/intEnumCompare.qml"_qs)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(o); QCOMPARE(o->property("a").toBool(), true); QCOMPARE(o->property("b").toBool(), false); QCOMPARE(o->property("c").toBool(), true); QCOMPARE(o->property("d").toBool(), false); } { // We cannot use Qt.red in QML because it's lower case. QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/enumInvalid.qml"_qs)); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(o); QCOMPARE(o->property("c").toBool(), false); QCOMPARE(o->property("d").toBool(), false); } } void tst_QmlCppCodegen::runInterpreted() { if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER")) QSKIP("Already running in interpreted mode"); #if QT_CONFIG(process) QProcess process; process.setProgram(QCoreApplication::applicationFilePath()); process.setEnvironment(QProcess::systemEnvironment() + QStringList(u"QV4_FORCE_INTERPRETER=1"_qs)); process.start(); QVERIFY(process.waitForFinished()); QCOMPARE(process.exitStatus(), QProcess::NormalExit); QCOMPARE(process.exitCode(), 0); #else QSKIP("Test needs QProcess"); #endif } QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.moc"