aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp')
-rw-r--r--tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp411
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"