diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2021-12-14 17:20:15 +0100 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2021-12-18 14:40:19 +0100 |
commit | 362b4bf3d6df1e1799a163d3e6d7ee39f75ad080 (patch) | |
tree | f80e94f4bded1e82bab0a374d38c6c4007d4c0f7 /tests/auto/qml | |
parent | 844a939af5607c51d724658351b533aaa2b4508a (diff) |
qmltc: Be aware of Component-wrapped types
While implicit components (the ones bound to Component-based property)
are covered, cases like `Component { Text{} }` are not, so fix that
Revisit the logic in the QmlIR / QQmlJSScope passes and code generator
as part of this
This might be a fairly used pattern in QML so no reason not to address
this right away, especially given that we have most of the
infrastructure in place already
While at it, bring over extra tests that some other (non-merged) commit
introduced. These give good coverage for missing cases and also exercise
the feature supported here
Task-number: QTBUG-84368
Pick-to: 6.3
Change-Id: I8f5c74fc79380566475b1139d4cc5560fac123e3
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tests/auto/qml')
-rw-r--r-- | tests/auto/qml/qmltc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmltc/data/documentWithIds.qml | 96 | ||||
-rw-r--r-- | tests/auto/qml/qmltc/data/properties.qml | 39 | ||||
-rw-r--r-- | tests/auto/qml/qmltc/tst_qmltc.cpp | 173 | ||||
-rw-r--r-- | tests/auto/qml/qmltc/tst_qmltc.h | 2 |
5 files changed, 299 insertions, 12 deletions
diff --git a/tests/auto/qml/qmltc/CMakeLists.txt b/tests/auto/qml/qmltc/CMakeLists.txt index 1f58eaa3c9..92573f3fc6 100644 --- a/tests/auto/qml/qmltc/CMakeLists.txt +++ b/tests/auto/qml/qmltc/CMakeLists.txt @@ -20,6 +20,7 @@ set(qml_sources data/methods.qml data/properties.qml data/ObjectWithId.qml + data/documentWithIds.qml data/signalHandlers.qml data/javaScriptFunctions.qml diff --git a/tests/auto/qml/qmltc/data/documentWithIds.qml b/tests/auto/qml/qmltc/data/documentWithIds.qml new file mode 100644 index 0000000000..5936be96bd --- /dev/null +++ b/tests/auto/qml/qmltc/data/documentWithIds.qml @@ -0,0 +1,96 @@ +import QtQuick + +Item { + property QtObject rectProperty: Rectangle { + id: rectangle + objectName: "rectangle" + } + + Row { + id: row + objectName: "row" + + Rectangle { + Text { + id: textInRectangle + objectName: "textInRectangle" + } + } + + property list<QtObject> listOfObjects: [ + Item { + id: itemInList + objectName: "itemInList" + + property QtObject foobarProperty: QtObject { id: foobar; objectName: "foobar" } + }, + QtObject { + id: objectInList + objectName: "objectInList" + } + ] + + Item { + id: item + objectName: "item" + } + } + + property QtObject gridProperty: GridView { + id: gridView + objectName: "gridView" + } + + TableView { + id: tableView + objectName: "tableView" + + property Component before: Component { + id: beforeDelegate + Text { + id: beforeDelegateText + objectName: "beforeDelegateText" + } + } + Component { + id: beforeDelegateDefaultProperty + Text { + id: beforeDelegateDefaultPropertyText + objectName: "beforeDelegateDefaultPropertyText" + } + } + + delegate: Rectangle { + id: delegateRect + objectName: "delegateRect" + } + + Component { + id: afterDelegateDefaultProperty + Text { + id: afterDelegateDefaultPropertyText + objectName: "afterDelegateDefaultPropertyText" + } + } + property Component after: Component { + id: afterDelegate + Text { + id: afterDelegateText + objectName: "afterDelegateText" + } + } + } + + property QtObject explicitCompProperty: Component { + id: explicitComponent + Text { + id: explicitText + objectName: "explicitText" + } + } + + property QtObject sentinelProperty: QtObject { + id: sentinel + objectName: "sentinel" + } +} diff --git a/tests/auto/qml/qmltc/data/properties.qml b/tests/auto/qml/qmltc/data/properties.qml index 77ab781e5d..896cd88eac 100644 --- a/tests/auto/qml/qmltc/data/properties.qml +++ b/tests/auto/qml/qmltc/data/properties.qml @@ -5,6 +5,18 @@ QtObject { property double doubleP: 0.5 property int intP: 42 property list<QtObject> listQtObjP // always list of QML objects + listQtObjP: [ + Text { + id: listQtObjP_child_0 + text: "child0" + }, + QtObject { + property string what: "child1" + }, + Item { + Rectangle { id: listQtObjP_child_2_rect } + } + ] property real realP: 2.32 property string stringP: "hello, world" property url urlP: "https://www.qt.io/" @@ -27,10 +39,33 @@ QtObject { required property real requiredRealP // extra: - property Timer timerP - property list<Component> listNumP + property Timer timerP: Timer { + interval: 42 + } + property list<Component> listCompP // special: property QtObject nullObjP: null property var nullVarP: null + + // Component-wrapped + property QtObject table: TableView { + property Component before: Component { Text { text: "beforeDelegate" } } + delegate: Text { // implicit component + text: "delegate" + } + property Component after: Component { Text { text: "afterDelegate" } } + } + + property QtObject explicitCompP: Component { // explicit component + Text { + id: explicitText + text: "not a delegate" + } + } + + property QtObject sentinelForComponent: QtObject { + id: sentinel + property string text: "should be correctly created" + } } diff --git a/tests/auto/qml/qmltc/tst_qmltc.cpp b/tests/auto/qml/qmltc/tst_qmltc.cpp index 0a2622bc6b..56ca309491 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -36,6 +36,7 @@ #include "methods.h" #include "properties.h" #include "objectwithid.h" +#include "documentwithids.h" #include "signalhandlers.h" #include "javascriptfunctions.h" @@ -131,6 +132,7 @@ void tst_qmltc::initTestCase() QUrl("qrc:/QmltcTests/data/methods.qml"), QUrl("qrc:/QmltcTests/data/properties.qml"), QUrl("qrc:/QmltcTests/data/ObjectWithId.qml"), + QUrl("qrc:/QmltcTests/data/documentWithIds.qml"), QUrl("qrc:/QmltcTests/data/signalHandlers.qml"), QUrl("qrc:/QmltcTests/data/javaScriptFunctions.qml"), @@ -339,7 +341,11 @@ void tst_qmltc::properties() // extra: QCOMPARE(propertyMetaType("timerP"), QMetaType::fromType<QQmlTimer *>()); - QCOMPARE(propertyMetaType("listNumP"), QMetaType::fromType<QQmlListProperty<QQmlComponent>>()); + QCOMPARE(propertyMetaType("listCompP"), QMetaType::fromType<QQmlListProperty<QQmlComponent>>()); + + QCOMPARE(propertyMetaType("table"), QMetaType::fromType<QObject *>()); + QCOMPARE(propertyMetaType("explicitCompP"), QMetaType::fromType<QObject *>()); + QCOMPARE(propertyMetaType("sentinelForComponent"), QMetaType::fromType<QObject *>()); // now, test property values: QCOMPARE(created.boolP(), true); @@ -357,21 +363,170 @@ void tst_qmltc::properties() QCOMPARE(created.readonlyStringP(), u"foobar"_qs); + // object bindinds: + const auto objectCtx = e.contextForObject(&created); + QQmlListReference listQtObj(&created, "listQtObjP"); + QCOMPARE(listQtObj.size(), 3); + { + QQuickText *child0 = qobject_cast<QQuickText *>(listQtObj.at(0)); + QVERIFY(child0); + QCOMPARE(child0->text(), u"child0"_qs); + QCOMPARE(objectCtx->objectForName("listQtObjP_child_0"), child0); + + QObject *child1 = listQtObj.at(1); + QVERIFY(child1); + QCOMPARE(child1->property("what").toString(), u"child1"_qs); + + QQuickItem *child2 = qobject_cast<QQuickItem *>(listQtObj.at(2)); + QVERIFY(child2); + QQmlListReference data(child2, "data"); + QCOMPARE(data.size(), 1); + QQuickRectangle *child2Rect = qobject_cast<QQuickRectangle *>(data.at(0)); + QVERIFY(child2Rect); + QCOMPARE(objectCtx->objectForName("listQtObjP_child_2_rect"), child2Rect); + } + + QQmlTimer *timer = created.timerP(); + QVERIFY(timer); + QCOMPARE(timer->interval(), 42); + // nulls: QCOMPARE(created.nullObjP(), nullptr); QCOMPARE(created.nullVarP(), QVariant::fromValue(nullptr)); + + QQuickTableView *table = qobject_cast<QQuickTableView *>(created.table()); + QVERIFY(table); + { + QQmlComponent *beforeDelegate = qvariant_cast<QQmlComponent *>(table->property("before")); + QVERIFY(beforeDelegate); + QQmlComponent *delegate = table->delegate(); + QVERIFY(delegate); + QQmlComponent *afterDelegate = qvariant_cast<QQmlComponent *>(table->property("after")); + QVERIFY(afterDelegate); + + QScopedPointer<QObject> beforeDelegateObject(beforeDelegate->create()); + QVERIFY(beforeDelegateObject); + QVERIFY(qobject_cast<QQuickText *>(beforeDelegateObject.get())); + QCOMPARE(beforeDelegateObject->property("text").toString(), u"beforeDelegate"_qs); + + QScopedPointer<QObject> delegateObject(delegate->create()); + QVERIFY(delegateObject); + QVERIFY(qobject_cast<QQuickText *>(delegateObject.get())); + QCOMPARE(delegateObject->property("text").toString(), u"delegate"_qs); + + QScopedPointer<QObject> afterDelegateObject(afterDelegate->create()); + QVERIFY(afterDelegateObject); + QVERIFY(qobject_cast<QQuickText *>(afterDelegateObject.get())); + QCOMPARE(afterDelegateObject->property("text").toString(), u"afterDelegate"_qs); + } + + QQmlComponent *explicitComp = qobject_cast<QQmlComponent *>(created.explicitCompP()); + QVERIFY(explicitComp); + QScopedPointer<QObject> explicitCompObject(explicitComp->create()); + QVERIFY(explicitCompObject); + QVERIFY(qobject_cast<QQuickText *>(explicitCompObject.get())); + QCOMPARE(explicitCompObject->property("text").toString(), u"not a delegate"_qs); + + QObject *sentinelForComponent = created.sentinelForComponent(); + QVERIFY(sentinelForComponent); + QCOMPARE(sentinelForComponent->property("text").toString(), u"should be correctly created"_qs); } -void tst_qmltc::id() +void tst_qmltc::ids() { - QQmlEngine e; - PREPEND_NAMESPACE(ObjectWithId) created(&e); // shouldn't crash here + { + QQmlEngine e; + PREPEND_NAMESPACE(ObjectWithId) created(&e); // shouldn't crash here + + auto objectCtx = QQmlContextData::get(e.contextForObject(&created)); + QVERIFY(objectCtx); + QCOMPARE(objectCtx->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(objectCtx->asQQmlContext()->objectForName("objectWithId"), &created); + QCOMPARE(objectCtx->contextObject(), &created); + } - auto objectCtx = QQmlContextData::get(e.contextForObject(&created)); - QVERIFY(objectCtx); - QCOMPARE(objectCtx->parent(), QQmlContextData::get(e.rootContext())); - QCOMPARE(objectCtx->asQQmlContext()->objectForName("objectWithId"), &created); - QCOMPARE(objectCtx->contextObject(), &created); + { + QQmlEngine e; + PREPEND_NAMESPACE(documentWithIds) created(&e); // shouldn't crash here + + auto ctx = e.contextForObject(&created); + QVERIFY(ctx); + auto objectCtx = QQmlContextData::get(ctx); + QVERIFY(objectCtx); + QCOMPARE(objectCtx->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(objectCtx->contextObject(), &created); + + // first check that ids match object names + const auto objectNameById = [&ctx](const QString &id) { + auto object = ctx->objectForName(id); + if (!object) + return QString(); + return object->objectName(); + }; + + QCOMPARE(objectNameById("rectangle"), u"rectangle"_qs); + QCOMPARE(objectNameById("row"), u"row"_qs); + QCOMPARE(objectNameById("textInRectangle"), u"textInRectangle"_qs); + QCOMPARE(objectNameById("itemInList"), u"itemInList"_qs); + QCOMPARE(objectNameById("objectInList"), u"objectInList"_qs); + QCOMPARE(objectNameById("item"), u"item"_qs); + QCOMPARE(objectNameById("gridView"), u"gridView"_qs); + QCOMPARE(objectNameById("tableView"), u"tableView"_qs); + QCOMPARE(objectNameById("sentinel"), u"sentinel"_qs); + + const auto verifyComponent = [&](QQmlComponent *component, const QString &componentId, + const QString &objectId) { + QVERIFY(component); + if (!componentId.isEmpty()) // empty string for implicit components + QCOMPARE(ctx->objectForName(componentId), component); + QCOMPARE(ctx->objectForName(objectId), nullptr); + + QScopedPointer<QObject> root(component->create()); + QCOMPARE(root->objectName(), objectId); + auto rootCtx = e.contextForObject(root.get()); + QVERIFY(rootCtx); + QCOMPARE(rootCtx->objectForName(objectId), root.get()); + }; + + auto explicitComponent = qobject_cast<QQmlComponent *>(created.explicitCompProperty()); + verifyComponent(explicitComponent, u"explicitComponent"_qs, u"explicitText"_qs); + + QQmlListReference children(&created, "data"); + QCOMPARE(children.size(), 2); + QQuickTableView *table = qobject_cast<QQuickTableView *>(children.at(1)); + QVERIFY(table); + QCOMPARE(ctx->objectForName(u"tableView"_qs), table); + QCOMPARE(table->objectName(), u"tableView"_qs); + + auto before = qvariant_cast<QQmlComponent *>(table->property("before")); + verifyComponent(before, u"beforeDelegate"_qs, u"beforeDelegateText"_qs); + auto after = qvariant_cast<QQmlComponent *>(table->property("after")); + verifyComponent(after, u"afterDelegate"_qs, u"afterDelegateText"_qs); + + auto delegate = table->delegate(); + verifyComponent(delegate, /* implicit component */ QString(), u"delegateRect"_qs); + + // TableView is really special when you add Component to a default + // property. see QQuickFlickablePrivate::data_append + QQmlComponent *beforeChild = nullptr; + QQmlComponent *afterChild = nullptr; + const auto tableChildren = table->children(); // QObject::children() + QVERIFY(tableChildren.size() >= 2); + for (QObject *child : tableChildren) { + auto comp = qobject_cast<QQmlComponent *>(child); + if (!comp) + continue; + // this is bad, but there doesn't seem to be any better choice + if (ctx->objectForName(u"beforeDelegateDefaultProperty"_qs) == comp) + beforeChild = comp; + else if (ctx->objectForName(u"afterDelegateDefaultProperty"_qs) == comp) + afterChild = comp; + } + // we just used ctx->objectForName() to find these components, so + // there's no point in checking the same condition in verifyComponent() + verifyComponent(beforeChild, QString(), u"beforeDelegateDefaultPropertyText"_qs); + verifyComponent(afterChild, QString(), u"afterDelegateDefaultPropertyText"_qs); + } } void tst_qmltc::signalHandlers() diff --git a/tests/auto/qml/qmltc/tst_qmltc.h b/tests/auto/qml/qmltc/tst_qmltc.h index f6c64a3160..9e0d732d4d 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.h +++ b/tests/auto/qml/qmltc/tst_qmltc.h @@ -50,7 +50,7 @@ private slots: void enumerations(); void methods(); void properties(); - void id(); + void ids(); void signalHandlers(); void jsFunctions(); |