aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qqmldelegatemodel
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qml/qqmldelegatemodel')
-rw-r--r--tests/auto/qml/qqmldelegatemodel/CMakeLists.txt6
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/abstractItemModel.qml2
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/clearCacheDuringInsertion.qml138
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml50
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/integerModel.qml2
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/listModel.qml2
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/modifyObjectUnderConstruction.qml18
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/overriddenModelData.qml55
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/persistedItemsCache.qml62
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/requiredModelData.qml50
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/resetModelData.qml16
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/typedModelData.qml64
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/universalModelData.qml72
-rw-r--r--tests/auto/qml/qqmldelegatemodel/data/viewUpdatedOnDelegateChoiceAffectingRoleChange.qml93
-rw-r--r--tests/auto/qml/qqmldelegatemodel/tst_qqmldelegatemodel.cpp411
15 files changed, 1037 insertions, 4 deletions
diff --git a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt
index 705ee6f357..8d8a90e0a7 100644
--- a/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt
+++ b/tests/auto/qml/qqmldelegatemodel/CMakeLists.txt
@@ -7,6 +7,12 @@
## tst_qqmldelegatemodel Test:
#####################################################################
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qqmldelegatemodel LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
# Collect test data
file(GLOB_RECURSE test_data_glob
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/abstractItemModel.qml b/tests/auto/qml/qqmldelegatemodel/data/abstractItemModel.qml
index a61e94ea6d..5ef502601c 100644
--- a/tests/auto/qml/qqmldelegatemodel/data/abstractItemModel.qml
+++ b/tests/auto/qml/qqmldelegatemodel/data/abstractItemModel.qml
@@ -1,5 +1,5 @@
// 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
import QtQml.Models 2.15
import QtQuick 2.15
diff --git a/tests/auto/qml/qqmldelegatemodel/data/clearCacheDuringInsertion.qml b/tests/auto/qml/qqmldelegatemodel/data/clearCacheDuringInsertion.qml
new file mode 100644
index 0000000000..37e508302e
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/clearCacheDuringInsertion.qml
@@ -0,0 +1,138 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQml.Models
+
+Window {
+ id: root
+ width: 640
+ height: 480
+ visible: true
+ color: "#111111"
+
+ Column {
+ spacing: 1
+ Repeater {
+ model: 1000
+
+ Rectangle {
+ width: 100
+ height: 100
+ color: "grey"
+ DelegateModel {
+ id: delegateModel
+ delegate: Rectangle {
+ width: 100
+ height: 20
+ color: "black"
+ Text {
+ anchors.centerIn: parent
+ text: "Name: " + model.name
+ color: "white"
+ }
+ }
+
+ property int length: 0
+ property var filterAcceptsItem: function(item) { return true; }
+
+ model: ListModel {
+ id: myModel
+
+ ListElement {
+ name: "tomato"
+ classifications: [
+ ListElement { classification: "fruit" },
+ ListElement { classification: "veg" }
+ ]
+ nutritionFacts: [
+ ListElement {
+ calories: "45"
+ }
+ ]
+ }
+
+ ListElement {
+ name: "apple"
+ classifications: [
+ ListElement { classification: "fruit" }
+ ]
+ nutritionFacts: [
+ ListElement {
+ calories: "87"
+ }
+ ]
+ }
+
+ ListElement {
+ name: "broccoli"
+ classifications: [
+ ListElement { classification: "veg" }
+ ]
+ nutritionFacts: [
+ ListElement {
+ calories: "12"
+ }
+ ]
+ }
+
+ ListElement {
+ name: "squash"
+ classifications: [
+ ListElement { classification: "veg" }
+ ]
+ nutritionFacts: [
+ ListElement {
+ calories: "112"
+ }
+ ]
+ }
+ }
+
+ groups: [
+ DelegateModelGroup { id: visibleItems; name: "visible" },
+ DelegateModelGroup { name: "veg" },
+ DelegateModelGroup { name: "fruit"; includeByDefault: true }
+ ]
+
+ function update() {
+
+ // Step 1: Filter items
+ var visible = [];
+ for (var i = 0; i < items.count; ++i) {
+ var item = items.get(i);
+ if (filterAcceptsItem(item.model)) {
+ visible.push(item);
+ }
+ }
+
+ // Step 2: Add all items to the visible group:
+ for (i = 0; i < visible.length; ++i) {
+ items.insert(visible[i], delegateModel.filterOnGroup)
+ }
+ delegateModel.length = visible.length
+ }
+
+ items.onChanged: update()
+ onFilterAcceptsItemChanged: update()
+
+ filterOnGroup: "visible"
+ Component.onCompleted: {
+ for(var i = 0; i < myModel.count; i++) {
+ var temp = 0;
+ var entry = myModel.get(i);
+
+ for (var j = 0; j < entry.classifications.count; j++) {
+ temp = entry.classifications.get(j)
+ items.insert(entry, temp.classification)
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml b/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml
new file mode 100644
index 0000000000..b01b14f7b8
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/deleteRace.qml
@@ -0,0 +1,50 @@
+import QtQuick
+import QtQml.Models
+
+Item {
+ DelegateModel {
+ id: delegateModel
+ model: ListModel {
+ id: sourceModel
+
+ ListElement { title: "foo" }
+ ListElement { title: "bar" }
+
+ function clear() {
+ if (count > 0)
+ remove(0, count);
+ }
+ }
+
+ groups: [
+ DelegateModelGroup { name: "selectedItems" }
+ ]
+
+ delegate: Text {
+ height: DelegateModel.inSelectedItems ? implicitHeight * 2 : implicitHeight
+ Component.onCompleted: {
+ if (index === 0)
+ DelegateModel.inSelectedItems = true;
+ }
+ }
+
+ Component.onCompleted: {
+ items.create(0)
+ items.create(1)
+ }
+ }
+
+ ListView {
+ anchors.fill: parent
+ model: delegateModel
+ }
+
+ Timer {
+ running: true
+ interval: 10
+ onTriggered: sourceModel.clear()
+ }
+
+ property int count: delegateModel.items.count
+}
+
diff --git a/tests/auto/qml/qqmldelegatemodel/data/integerModel.qml b/tests/auto/qml/qqmldelegatemodel/data/integerModel.qml
index 42bf882fda..bcc7a2786a 100644
--- a/tests/auto/qml/qqmldelegatemodel/data/integerModel.qml
+++ b/tests/auto/qml/qqmldelegatemodel/data/integerModel.qml
@@ -1,5 +1,5 @@
// 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
import QtQml.Models 2.15
import QtQuick 2.15
diff --git a/tests/auto/qml/qqmldelegatemodel/data/listModel.qml b/tests/auto/qml/qqmldelegatemodel/data/listModel.qml
index f74abb2e9a..b4ea7a283f 100644
--- a/tests/auto/qml/qqmldelegatemodel/data/listModel.qml
+++ b/tests/auto/qml/qqmldelegatemodel/data/listModel.qml
@@ -1,5 +1,5 @@
// 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
import QtQml.Models 2.15
import QtQuick 2.15
diff --git a/tests/auto/qml/qqmldelegatemodel/data/modifyObjectUnderConstruction.qml b/tests/auto/qml/qqmldelegatemodel/data/modifyObjectUnderConstruction.qml
new file mode 100644
index 0000000000..f21577e395
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/modifyObjectUnderConstruction.qml
@@ -0,0 +1,18 @@
+import QtQuick
+
+Window {
+ id: window
+ width: 640
+ height: 480
+ visible: true
+ property alias testModel: repeater.model
+
+ Repeater {
+ id: repeater
+ model: 1
+ delegate: Item {
+ Component.onCompleted: repeater.model = 0
+ }
+ }
+}
+
diff --git a/tests/auto/qml/qqmldelegatemodel/data/overriddenModelData.qml b/tests/auto/qml/qqmldelegatemodel/data/overriddenModelData.qml
new file mode 100644
index 0000000000..e392b2e5c9
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/overriddenModelData.qml
@@ -0,0 +1,55 @@
+import QtQml
+
+DelegateModel {
+ id: root
+
+ property ListModel listModel: ListModel {
+ ListElement {
+ modelData: "a"
+ row: "b"
+ column: "c"
+ model: "d"
+ hasModelChildren: "e"
+ index: "f"
+ }
+ }
+
+ property var array: [{
+ modelData: "a",
+ row: "b",
+ column: "c",
+ model: "d",
+ hasModelChildren: "e",
+ index: "f"
+ }]
+
+ property QtObject object: QtObject {
+ property string modelData: "a"
+ property string row: "b"
+ property string column: "c"
+ property string model: "d"
+ property string hasModelChildren: "e"
+ property string index: "f"
+ }
+
+ property int n: -1
+
+ model: {
+ switch (n) {
+ case 0: return listModel
+ case 1: return array
+ case 2: return object
+ }
+ return undefined;
+ }
+
+ delegate: QtObject {
+ required property string modelData
+ required property string row
+ required property string column
+ required property string model
+ required property string hasModelChildren
+ required property string index
+ objectName: [modelData, row, column, model, hasModelChildren, index].join(" ")
+ }
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/persistedItemsCache.qml b/tests/auto/qml/qqmldelegatemodel/data/persistedItemsCache.qml
new file mode 100644
index 0000000000..5ae2038e1f
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/persistedItemsCache.qml
@@ -0,0 +1,62 @@
+import QtQuick
+import QtQuick.Window
+import QtQml.Models
+
+Window {
+ id: win
+ visible: true
+ width: 640
+ height: 480
+ property int destroyCount : 0;
+ property int createCount : 0;
+ property alias testListModel: mdl
+
+ DelegateModel {
+ id: visualModel
+ model: ListModel {
+ id: mdl
+ ListElement {
+ name: "a"
+ hidden: false
+ }
+ ListElement {
+ name: "b"
+ hidden: true
+ }
+ ListElement {
+ name: "c"
+ hidden: false
+ }
+ }
+
+ filterOnGroup: "selected"
+
+ groups: [
+ DelegateModelGroup {
+ name: "selected"
+ includeByDefault: true
+ }
+ ]
+
+ delegate: Text {
+ visible: DelegateModel.inSelected
+ property var idx
+ Component.onCompleted: {
+ ++createCount
+ idx = index
+ DelegateModel.inPersistedItems = true
+ DelegateModel.inSelected = !model.hidden
+ }
+ Component.onDestruction: ++destroyCount
+ text: model.name
+ }
+ }
+
+ ListView {
+ id: listView
+ model: visualModel
+ anchors.fill: parent
+ focus: true
+ }
+
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/requiredModelData.qml b/tests/auto/qml/qqmldelegatemodel/data/requiredModelData.qml
new file mode 100644
index 0000000000..467d60dff8
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/requiredModelData.qml
@@ -0,0 +1,50 @@
+import QtQml
+
+DelegateModel {
+ id: root
+
+ property ListModel singularModel: ListModel {
+ ListElement {
+ a: "a"
+ }
+ ListElement {
+ a: "a"
+ }
+ }
+
+ property ListModel listModel: ListModel {
+ ListElement {
+ a: "a"
+ b: "b"
+ }
+ ListElement {
+ a: "a"
+ b: "b"
+ }
+ }
+
+ property var array: [
+ {a: "a", b: "b"}, {a: "b", b: "b"}
+ ]
+
+ property QtObject object: QtObject {
+ property string a: "a"
+ property string b: "b"
+ }
+
+ property int n: -1
+
+ model: {
+ switch (n) {
+ case 0: return singularModel
+ case 1: return listModel
+ case 2: return array
+ case 3: return object
+ }
+ return undefined;
+ }
+
+ delegate: QtObject {
+ required property string a
+ }
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/resetModelData.qml b/tests/auto/qml/qqmldelegatemodel/data/resetModelData.qml
new file mode 100644
index 0000000000..1fe6168c79
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/resetModelData.qml
@@ -0,0 +1,16 @@
+import QtQuick
+
+ListView {
+ id: root
+ anchors.fill: parent
+ property bool success: (currentItem?.mydata ?? 0) === 42
+ height: 300
+ width: 200
+
+ delegate: Rectangle {
+ required property var model
+ implicitWidth: 100
+ implicitHeight: 50
+ property var mydata: model?.foo ?? model.bar
+ }
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/typedModelData.qml b/tests/auto/qml/qqmldelegatemodel/data/typedModelData.qml
new file mode 100644
index 0000000000..08f1c7d68e
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/typedModelData.qml
@@ -0,0 +1,64 @@
+import QtQml
+
+DelegateModel {
+ id: root
+
+ // useful object as model, int as modelData
+ property ListModel singularModel: ListModel {
+ ListElement {
+ x: 11
+ }
+ ListElement {
+ x: 12
+ }
+ }
+
+ // same, useful, object as model and modelData
+ property ListModel listModel: ListModel {
+ ListElement {
+ x: 13
+ y: 14
+ }
+ ListElement {
+ x: 15
+ y: 16
+ }
+ }
+
+ // useful but different objects as modelData and model
+ // This is how the array accessor works. We can live with it.
+ property var array: [
+ {x: 17, y: 18}, {x: 19, y: 20}
+ ]
+
+ // useful but different objects as modelData and model
+ // This is how the object accessor works. We can live with it.
+ property QtObject object: QtObject {
+ property int x: 21
+ property int y: 22
+ }
+
+ property int n: -1
+
+ model: {
+ switch (n) {
+ case 0: return singularModel
+ case 1: return listModel
+ case 2: return array
+ case 3: return object
+ }
+ return undefined;
+ }
+
+ delegate: QtObject {
+ required property point modelData
+ required property QtObject model
+
+ property real modelX: model.x
+ property real modelDataX: modelData.x
+ property point modelSelf: model
+ property point modelDataSelf: modelData
+ property point modelModelData: model.modelData
+ property point modelAnonymous: model[""]
+ }
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/universalModelData.qml b/tests/auto/qml/qqmldelegatemodel/data/universalModelData.qml
new file mode 100644
index 0000000000..f24009f873
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/universalModelData.qml
@@ -0,0 +1,72 @@
+import QtQml
+
+DelegateModel {
+ id: root
+
+ // useful object as model, string as modelData
+ property ListModel singularModel: ListModel {
+ ListElement {
+ a: "a"
+ }
+ ListElement {
+ a: "b"
+ }
+ }
+
+ // same, useful, object as model and modelData
+ property ListModel listModel: ListModel {
+ ListElement {
+ a: "a"
+ b: "a"
+ }
+ ListElement {
+ a: "b"
+ b: "b"
+ }
+ }
+
+ // useful but different objects as modelData and model
+ // This is how the array accessor works. We can live with it.
+ property var array: [
+ {a: "a", b: "a"}, {a: "b", b: "a"}
+ ]
+
+ // string as modelData
+ // object with anonymous string property as model.
+ property var stringList: ["a", "b"]
+
+ // useful but different objects as modelData and model
+ // This is how the object accessor works. We can live with it.
+ property QtObject object: QtObject {
+ property string a: "a"
+ property string b: "a"
+ }
+
+ // number as modelData
+ // object with anonymous number property as model
+ property int n: -1
+
+ model: {
+ switch (n) {
+ case 0: return singularModel
+ case 1: return listModel
+ case 2: return array
+ case 3: return stringList
+ case 4: return object
+ case 5: return n
+ }
+ return undefined;
+ }
+
+ delegate: QtObject {
+ required property var modelData
+ required property var model
+
+ property var modelA: model.a
+ property var modelDataA: modelData.a
+ property var modelSelf: model
+ property var modelDataSelf: modelData
+ property var modelModelData: model.modelData
+ property var modelAnonymous: model[""]
+ }
+}
diff --git a/tests/auto/qml/qqmldelegatemodel/data/viewUpdatedOnDelegateChoiceAffectingRoleChange.qml b/tests/auto/qml/qqmldelegatemodel/data/viewUpdatedOnDelegateChoiceAffectingRoleChange.qml
new file mode 100644
index 0000000000..b2367b3a6e
--- /dev/null
+++ b/tests/auto/qml/qqmldelegatemodel/data/viewUpdatedOnDelegateChoiceAffectingRoleChange.qml
@@ -0,0 +1,93 @@
+import QtQuick
+import Qt.labs.qmlmodels
+import QtQml.Models
+
+Item {
+ id: root
+ property bool triggered: false
+ onTriggeredChanged: {
+ rootLM.setProperty(1, "currentRole", "first");
+ }
+ width: 800
+ height: 680
+
+ function verify(): bool {
+ rootLV.currentIndex = 1; // needed for itemAtIndex to work
+ if (root.triggered)
+ return rootLV.itemAtIndex(0).isFirst && rootLV.itemAtIndex(1).isFirst;
+ else
+ return rootLV.itemAtIndex(0).isFirst && !rootLV.itemAtIndex(1).isFirst;
+ }
+
+ ListModel {
+ id: rootLM
+ ListElement {
+ currentRole: "first"
+ firstText: "TEXT_FIRST_1"
+ secondText: "TEXT_SECOND_1"
+ }
+ ListElement {
+ currentRole: "second"
+ firstText: "TEXT_FIRST_2"
+ secondText: "TEXT_SECOND_2"
+ }
+ }
+
+ DelegateModel {
+ id: delModel
+ model: rootLM
+ delegate: DelegateChooser {
+ id: delegateChooser
+ role: "currentRole"
+ DelegateChoice {
+ roleValue: "first"
+ Rectangle {
+ property bool isFirst: true
+ height: 30
+ width: rootLV.width
+ color: "yellow"
+ Text {
+ anchors.centerIn: parent
+ text: firstText + " " + currentRole
+ }
+ }
+ }
+ DelegateChoice {
+ roleValue: "second"
+ Rectangle {
+ property bool isFirst: false
+ height: 30
+ width: rootLV.width
+ color: "red"
+ Text {
+ anchors.centerIn: parent
+ text: secondText + " " + currentRole
+ }
+ }
+ }
+ }
+ }
+
+ TapHandler {
+ // for manual testing
+ onTapped: root.triggered = true
+ }
+
+ Rectangle {
+ width: 200
+ height: 300
+ anchors.centerIn: parent
+ border.color: "black"
+ border.width: 1
+ color: "blue"
+
+ ListView {
+ id: rootLV
+ objectName: "listview"
+ anchors.margins: 30
+ anchors.fill: parent
+ cacheBuffer: 0
+ model: delModel
+ }
+ }
+}
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"