diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2021-11-23 19:00:29 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2021-12-03 12:09:26 +0100 |
commit | 58ff7aa4fed5b3a27b6a859fc02f9ee27dd1a6cc (patch) | |
tree | cd8d455066d5e9e6d14988e945f4ba421765b743 /tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | |
parent | c2f50cf90d143d96313ab3cfdbb9e4552a095cbb (diff) | |
download | qtdeclarative-58ff7aa4fed5b3a27b6a859fc02f9ee27dd1a6cc.tar.gz |
Compile QML files ahead of time with qmlcachegen
qmlcachegen compiles bindings and functions to C++ as far as
QQmlJSAotCompiler can. It does respect "pragma Strict" and rejects the
file if it's violated. Furthermore, it sets up the logger to follow the
qt.qml.compiler.aot logging category. By default it's completely silent.
Compiling the examples with qmlcachegen exposes a bug in the type
resolver where it returns an invalid generic type. It should never do
that. Fix it by returning JSValue.
[ChangeLog][QtQml][Important Behavior Changes] QML bindings and
functions are now compiled to C++ by qmlcachegen, if possible. Use the
qt.qml.compiler.aot logging category to receive diagnostics about the
compilation.
Task-number: QTBUG-98305
Change-Id: I6953812c3fd20b68339617a5714fcbe16a384360
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp')
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 1623 |
1 files changed, 1623 insertions, 0 deletions
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp new file mode 100644 index 0000000000..11c70f2c72 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -0,0 +1,1623 @@ +/****************************************************************************** +** +** 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 <data/birthdayparty.h> +#include <data/cppbaseclass.h> +#include <data/objectwithmethod.h> + +#include <QtTest> +#include <QtQml> +#include <QtGui/qcolor.h> + +#if QT_CONFIG(process) +#include <QtCore/qprocess.h> +#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<QObject> object(component.create()); + QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData()); + QCOMPARE(object->property("foo").toInt(), int(3)); + + { + CppBaseClass *base = qobject_cast<CppBaseClass *>(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<QObject> 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<QObject> 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<QObject> 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<QFont>(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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QTRY_COMPARE(exitCode, 0); + + QObject *application = qvariant_cast<QObject *>(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<QUrl>(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<QUrl>(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<QObject> 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<QObject> 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<Qt::Alignment>(prop), Qt::AlignCenter); + } + { + QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/enumsInOtherObject.qml"_qs)); + QVERIFY2(!component.isError(), component.errorString().toUtf8()); + QScopedPointer<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QCOMPARE(object->property("ok").toBool(), true); + QVariant okFunc = object->property("okFunc"); + QCOMPARE(okFunc.metaType(), QMetaType::fromType<QJSValue>()); + 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<QObject> obj(component.create()); + QVERIFY(obj); + BirthdayParty *party(qobject_cast<BirthdayParty *>(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<QObject> 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<QObject> 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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + const QJSValue value1 = object->property("values1").value<QJSValue>(); + 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<QJSValue>(); + 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<QObject> 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<QObject> 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<QObject *>(object->property("nullObject1")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(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<QString>("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<QObject> 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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QCOMPARE(object->property("a").toInt(), 6); + QCOMPARE(qvariant_cast<QSizeF>(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<QObject> 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<QObject> 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<QObject> 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<QObject *>(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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> object(component.create()); + + QObject *drag = qvariant_cast<QObject *>(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<QObject> 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<QObject> 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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QCOMPARE(qvariant_cast<QDateTime>(object->property("dayz")), + QDateTime(QDate(2121, 1, 12), QTime())); + QCOMPARE(qvariant_cast<QDateTime>(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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + const QVariant t = object->property("t"); + QCOMPARE(t.metaType(), QMetaType::fromType<QString>()); + 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<QObject> 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<QObject> 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<QObject> 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<const char *> 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<QObject> object(component.create()); + QVERIFY(!object.isNull()); + QObject *header = qvariant_cast<QObject *>(object->property("header")); + QVERIFY(header); + QObject *background = qvariant_cast<QObject *>(header->property("background")); + QObject *parent = qvariant_cast<QObject *>(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<QObject> 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<QObject> object(component.create()); + + QFont font = qvariant_cast<QFont>(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<QObject> 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<bool>()); + QCOMPARE(foo.toBool(), expected); + + const QVariant bar = object->property("bar"); + QCOMPARE(bar.metaType(), QMetaType::fromType<bool>()); + QCOMPARE(bar.toBool(), expected); + + const QVariant visible = child->property("visible"); + QCOMPARE(visible.metaType(), QMetaType::fromType<bool>()); + 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject *>(root->property("objectAsObject")), object); + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsItem")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsObject")), item); + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsItem")), item); + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsObject")), rectangle); + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsItem")), rectangle); + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsRectangle")), rectangle); + QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsDummy")), nullptr); + + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsObject")), dummy); + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsItem")), dummy); + QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsRectangle")), nullptr); + QCOMPARE(qvariant_cast<QObject *>(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<QObject> root(component.create()); + QVERIFY(!root.isNull()); + + BirthdayParty *party = qobject_cast<BirthdayParty *>(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<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QObject *dialog = rootObject->property("dialog").value<QObject *>(); + 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<QObject> 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<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + Person *person = qobject_cast<Person *>(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<QObject> 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<QObject> 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<QObject> 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<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QCOMPARE(qvariant_cast<QColor>(rootObject->property("c")), QColor::fromRgb(0xdd, 0xdd, 0xdd)); + QCOMPARE(qvariant_cast<QColor>(rootObject->property("d")), QColor::fromRgb(0xaa, 0xaa, 0xaa)); + QCOMPARE(qvariant_cast<QColor>(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<QObject> rootObject(component.create()); + QVERIFY(rootObject); + + QCOMPARE(qvariant_cast<QUrl>(rootObject->property("c")), QUrl(u"http://dddddd.com"_qs)); + QCOMPARE(qvariant_cast<QUrl>(rootObject->property("d")), QUrl(u"http://aaaaaa.com"_qs)); + QCOMPARE(qvariant_cast<QUrl>(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<QObject> o(c.create()); + QVERIFY(o); + + QVERIFY(qvariant_cast<QQmlComponent *>(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<QObject> 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<QObject> 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<QObject> o(c.create()); + QVERIFY(o); + + const double inputs[] = { + qQNaN(), -qInf(), + std::numeric_limits<double>::min(), + std::numeric_limits<float>::min(), + std::numeric_limits<qint32>::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<qint32>::max(), + std::numeric_limits<quint32>::max(), + std::numeric_limits<float>::max(), + std::numeric_limits<double>::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<QObject> 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<QObject> 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" |