diff options
Diffstat (limited to 'tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp')
-rw-r--r-- | tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp | 411 |
1 files changed, 410 insertions, 1 deletions
diff --git a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp index ca66ddb618..2cacda5513 100644 --- a/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp +++ b/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp @@ -1,16 +1,21 @@ // Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/qtest.h> +#include <QtCore/qjsonobject.h> #include <QtCore/QConcatenateTablesProxyModel> #include <QtGui/QStandardItemModel> #include <QtQml/qqmlcomponent.h> +#include <QtQml/qqmlapplicationengine.h> #include <QtQmlModels/private/qqmldelegatemodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQuick/qquickview.h> #include <QtQuick/qquickitem.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtTest/QSignalSpy> +#include <forward_list> + class tst_QQmlDelegateModel : public QQmlDataTest { Q_OBJECT @@ -19,6 +24,7 @@ public: tst_QQmlDelegateModel(); private slots: + void resettingRolesRespected(); void valueWithoutCallingObjectFirst_data(); void valueWithoutCallingObjectFirst(); void qtbug_86017(); @@ -26,6 +32,16 @@ private slots: void contextAccessedByHandler(); void redrawUponColumnChange(); void nestedDelegates(); + void universalModelData(); + void typedModelData(); + void requiredModelData(); + void overriddenModelData(); + void deleteRace(); + void persistedItemsStayInCache(); + void unknownContainersAsModel(); + void doNotUnrefObjectUnderConstruction(); + void clearCacheDuringInsertion(); + void viewUpdatedOnDelegateChoiceAffectingRoleChange(); }; class AbstractItemModel : public QAbstractItemModel @@ -85,6 +101,61 @@ tst_QQmlDelegateModel::tst_QQmlDelegateModel() qmlRegisterType<AbstractItemModel>("Test", 1, 0, "AbstractItemModel"); } +class TableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return 1; + } + + int columnCount(const QModelIndex & = QModelIndex()) const override + { + return 1; + } + + QVariant data(const QModelIndex &index, int role) const override + { + switch (role) { + case 0: + return QString("foo: %1, %2").arg(index.column()).arg(index.row()); + case 1: + return 42; + default: + break; + } + + return QVariant(); + } + + Q_INVOKABLE void change() { beginResetModel(); toggle = !toggle; endResetModel(); } + + QHash<int, QByteArray> roleNames() const override + { + if (toggle) + return { {0, "foo"} }; + else + return { {1, "bar"} }; + } + + bool toggle = true; +}; + +void tst_QQmlDelegateModel::resettingRolesRespected() +{ + auto model = std::make_unique<TableModel>(); + QQmlApplicationEngine engine; + engine.setInitialProperties({ {"model", QVariant::fromValue(model.get()) }} ); + engine.load(testFileUrl("resetModelData.qml")); + QTRY_VERIFY(!engine.rootObjects().isEmpty()); + QObject *root = engine.rootObjects().constFirst(); + QVERIFY(!root->property("success").toBool()); + model->change(); + QTRY_VERIFY(root->property("success").toBool()); +} + void tst_QQmlDelegateModel::valueWithoutCallingObjectFirst_data() { QTest::addColumn<QUrl>("qmlFileUrl"); @@ -207,6 +278,344 @@ void tst_QQmlDelegateModel::nestedDelegates() QFAIL("Loader not found"); } +void tst_QQmlDelegateModel::universalModelData() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("universalModelData.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(o.data()); + QVERIFY(delegateModel); + + for (int i = 0; i < 6; ++i) { + delegateModel->setProperty("n", i); + QObject *delegate = delegateModel->object(0); + QObject *modelItem = delegate->property("modelSelf").value<QObject *>(); + QVERIFY(modelItem != nullptr); + switch (i) { + case 0: { + // list model with 1 role + QCOMPARE(delegate->property("modelA"), QStringLiteral("a")); + QVERIFY(!delegate->property("modelDataA").isValid()); + QCOMPARE(delegate->property("modelDataSelf"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelModelData"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelAnonymous"), QStringLiteral("a")); + break; + } + case 1: { + // list model with 2 roles + QCOMPARE(delegate->property("modelA"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelDataA"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelDataSelf"), QVariant::fromValue(modelItem)); + QCOMPARE(delegate->property("modelModelData"), QVariant::fromValue(modelItem)); + QCOMPARE(delegate->property("modelAnonymous"), QVariant::fromValue(modelItem)); + break; + } + case 2: { + // JS array of objects + QCOMPARE(delegate->property("modelA"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelDataA"), QStringLiteral("a")); + + // Do the comparison in QVariantMap. The values get converted back and forth a + // few times, making any JavaScript equality comparison impossible. + // This is only due to test setup, though. + const QVariantMap modelData = delegate->property("modelDataSelf").value<QVariantMap>(); + QVERIFY(!modelData.isEmpty()); + QCOMPARE(delegate->property("modelModelData").value<QVariantMap>(), modelData); + QCOMPARE(delegate->property("modelAnonymous").value<QVariantMap>(), modelData); + break; + } + case 3: { + // string list + QVERIFY(!delegate->property("modelA").isValid()); + QVERIFY(!delegate->property("modelDataA").isValid()); + QCOMPARE(delegate->property("modelDataSelf"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelModelData"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelAnonymous"), QStringLiteral("a")); + break; + } + case 4: { + // single object + QCOMPARE(delegate->property("modelA"), QStringLiteral("a")); + QCOMPARE(delegate->property("modelDataA"), QStringLiteral("a")); + QObject *modelData = delegate->property("modelDataSelf").value<QObject *>(); + QVERIFY(modelData != nullptr); + QCOMPARE(delegate->property("modelModelData"), QVariant::fromValue(modelData)); + QCOMPARE(delegate->property("modelAnonymous"), QVariant::fromValue(modelData)); + break; + } + case 5: { + // a number + QVERIFY(!delegate->property("modelA").isValid()); + QVERIFY(!delegate->property("modelDataA").isValid()); + const QVariant modelData = delegate->property("modelDataSelf"); + + // This is int on 32bit systems because qsizetype fits into int there. + // On 64bit systems it's double because qsizetype doesn't fit into int. + if (sizeof(qsizetype) > sizeof(int)) + QCOMPARE(modelData.metaType(), QMetaType::fromType<double>()); + else + QCOMPARE(modelData.metaType(), QMetaType::fromType<int>()); + + QCOMPARE(modelData.value<int>(), 0); + QCOMPARE(delegate->property("modelModelData"), modelData); + QCOMPARE(delegate->property("modelAnonymous"), modelData); + break; + } + default: + QFAIL("wrong model number"); + break; + } + } + +} + +void tst_QQmlDelegateModel::typedModelData() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("typedModelData.qml"); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(o.data()); + QVERIFY(delegateModel); + + for (int i = 0; i < 4; ++i) { + if (i == 0) { + for (int j = 0; j < 3; ++j) { + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlPointFValueType " + "to call with value QVariant(double, 11)"); + } + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":62:9: Unable to assign double to QPointF")); + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":61:9: Unable to assign double to QPointF")); + } + + delegateModel->setProperty("n", i); + QObject *delegate = delegateModel->object(0); + QVERIFY(delegate); + const QPointF modelItem = delegate->property("modelSelf").value<QPointF>(); + switch (i) { + case 0: { + // list model with 1 role. + // Does not work, for the most part, because the model is singular + QCOMPARE(delegate->property("modelX"), 11.0); + QCOMPARE(delegate->property("modelDataX"), 0.0); + QCOMPARE(delegate->property("modelSelf"), QPointF(11.0, 0.0)); + QCOMPARE(delegate->property("modelDataSelf"), QPointF()); + QCOMPARE(delegate->property("modelModelData"), QPointF()); + QCOMPARE(delegate->property("modelAnonymous"), QPointF()); + break; + } + case 1: { + // list model with 2 roles + QCOMPARE(delegate->property("modelX"), 13.0); + QCOMPARE(delegate->property("modelDataX"), 13.0); + QCOMPARE(delegate->property("modelSelf"), QVariant::fromValue(modelItem)); + QCOMPARE(delegate->property("modelDataSelf"), QVariant::fromValue(modelItem)); + QCOMPARE(delegate->property("modelModelData"), QVariant::fromValue(modelItem)); + QCOMPARE(delegate->property("modelAnonymous"), QVariant::fromValue(modelItem)); + break; + } + case 2: { + // JS array of objects + QCOMPARE(delegate->property("modelX"), 17.0); + QCOMPARE(delegate->property("modelDataX"), 17.0); + + const QPointF modelData = delegate->property("modelDataSelf").value<QPointF>(); + QCOMPARE(modelData, QPointF(17, 18)); + QCOMPARE(delegate->property("modelSelf"), QVariant::fromValue(modelData)); + QCOMPARE(delegate->property("modelModelData").value<QPointF>(), modelData); + QCOMPARE(delegate->property("modelAnonymous").value<QPointF>(), modelData); + break; + } + case 3: { + // single object + QCOMPARE(delegate->property("modelX"), 21); + QCOMPARE(delegate->property("modelDataX"), 21); + const QPointF modelData = delegate->property("modelDataSelf").value<QPointF>(); + QCOMPARE(modelData, QPointF(21, 22)); + QCOMPARE(delegate->property("modelSelf"), QVariant::fromValue(modelData)); + QCOMPARE(delegate->property("modelModelData"), QVariant::fromValue(modelData)); + QCOMPARE(delegate->property("modelAnonymous"), QVariant::fromValue(modelData)); + break; + } + default: + QFAIL("wrong model number"); + break; + } + } + +} + +void tst_QQmlDelegateModel::requiredModelData() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("requiredModelData.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(o.data()); + QVERIFY(delegateModel); + + for (int i = 0; i < 4; ++i) { + delegateModel->setProperty("n", i); + QObject *delegate = delegateModel->object(0); + QVERIFY(delegate); + const QVariant a = delegate->property("a"); + QCOMPARE(a.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(a.toString(), QLatin1String("a")); + } +} + +void tst_QQmlDelegateModel::overriddenModelData() +{ + QTest::failOnWarning(QRegularExpression( + "Final member [^ ]+ is overridden in class [^\\.]+. The override won't be used.")); + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("overriddenModelData.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + + QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel *>(o.data()); + QVERIFY(delegateModel); + + for (int i = 0; i < 3; ++i) { + delegateModel->setProperty("n", i); + QObject *delegate = delegateModel->object(0); + QVERIFY(delegate); + + if (i == 1 || i == 2) { + // You can actually not override if the model is a QObject or a JavaScript array. + // Someone is certainly relying on this. + // We need to find a migration mechanism to fix it. + QCOMPARE(delegate->objectName(), QLatin1String(" 0 0 e 0")); + } else { + QCOMPARE(delegate->objectName(), QLatin1String("a b c d e f")); + } + } +} + +void tst_QQmlDelegateModel::deleteRace() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("deleteRace.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QTRY_COMPARE(o->property("count").toInt(), 2); + QTRY_COMPARE(o->property("count").toInt(), 0); +} + +void tst_QQmlDelegateModel::persistedItemsStayInCache() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("persistedItemsCache.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> object(component.create()); + QVERIFY(object); + const QVariant properyListModel = object->property("testListModel"); + QQmlListModel *listModel = qvariant_cast<QQmlListModel *>(properyListModel); + QVERIFY(listModel); + QTRY_COMPARE(object->property("createCount").toInt(), 3); + listModel->clear(); + QTRY_COMPARE(object->property("destroyCount").toInt(), 3); +} + +Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(std::forward_list) +void tst_QQmlDelegateModel::unknownContainersAsModel() +{ + QQmlEngine engine; + + QQmlComponent modelComponent(&engine); + modelComponent.setData("import QtQml.Models\nDelegateModel {}\n", QUrl()); + QCOMPARE(modelComponent.status(), QQmlComponent::Ready); + + QScopedPointer<QObject> o(modelComponent.create()); + QQmlDelegateModel *delegateModel = qobject_cast<QQmlDelegateModel*>(o.data()); + QVERIFY(delegateModel); + + QQmlComponent delegateComponent(&engine); + delegateComponent.setData("import QtQml\nQtObject { objectName: modelData }\n", QUrl()); + QCOMPARE(delegateComponent.status(), QQmlComponent::Ready); + + delegateModel->setDelegate(&delegateComponent); + + QList<QJsonObject> json; + for (int i = 0; i < 10; ++i) + json.append(QJsonObject({{"foo", i}})); + + // Recognized as list + delegateModel->setModel(QVariant::fromValue(json)); + QObject *obj = delegateModel->object(0, QQmlIncubator::Synchronous); + QVERIFY(obj); + QCOMPARE(delegateModel->count(), 10); + QCOMPARE(delegateModel->model().metaType(), QMetaType::fromType<QList<QJsonObject>>()); + + QVERIFY(qMetaTypeId<std::forward_list<int>>() > 0); + std::forward_list<int> mess; + mess.push_front(4); + mess.push_front(5); + mess.push_front(6); + + // Converted into QVariantList + delegateModel->setModel(QVariant::fromValue(mess)); + obj = delegateModel->object(0, QQmlIncubator::Synchronous); + QVERIFY(obj); + QCOMPARE(delegateModel->count(), 3); + QCOMPARE(delegateModel->model().metaType(), QMetaType::fromType<QVariantList>()); +} + +void tst_QQmlDelegateModel::doNotUnrefObjectUnderConstruction() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("modifyObjectUnderConstruction.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> object(component.create()); + QVERIFY(object); + QTRY_COMPARE(object->property("testModel").toInt(), 0); +} + +void tst_QQmlDelegateModel::clearCacheDuringInsertion() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("clearCacheDuringInsertion.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> object(component.create()); + QVERIFY(object); + QTRY_COMPARE(object->property("testModel").toInt(), 0); +} + +void tst_QQmlDelegateModel::viewUpdatedOnDelegateChoiceAffectingRoleChange() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("viewUpdatedOnDelegateChoiceAffectingRoleChange.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> object(component.create()); + QVERIFY(object); + QQuickItem *listview = object->findChild<QQuickItem *>("listview"); + QVERIFY(listview); + QTRY_VERIFY(listview->property("count").toInt() > 0); + bool returnedValue = false; + QMetaObject::invokeMethod(object.get(), "verify", Q_RETURN_ARG(bool, returnedValue)); + QVERIFY(returnedValue); + returnedValue = false; + + object->setProperty("triggered", "true"); + QTRY_VERIFY(listview->property("count").toInt() > 0); + QMetaObject::invokeMethod(object.get(), "verify", Q_RETURN_ARG(bool, returnedValue)); + QVERIFY(returnedValue); +} + QTEST_MAIN(tst_QQmlDelegateModel) #include "tst_qqmldelegatemodel.moc" |