1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | #include <qtest.h> |
29 | #include <QtQuick/private/qquickitem_p.h> |
30 | #include <QtQuick/private/qquicktext_p.h> |
31 | #include <QtQml/private/qqmlengine_p.h> |
32 | #include <QtQmlModels/private/qqmllistmodel_p.h> |
33 | #include <QtQml/private/qqmlexpression_p.h> |
34 | #include <QQmlComponent> |
35 | |
36 | #include <QtCore/qtimer.h> |
37 | #include <QtCore/qdebug.h> |
38 | #include <QtCore/qtranslator.h> |
39 | #include <QSignalSpy> |
40 | |
41 | #include "../../shared/util.h" |
42 | |
43 | Q_DECLARE_METATYPE(QList<int>) |
44 | Q_DECLARE_METATYPE(QList<QVariantHash>) |
45 | |
46 | #define RUNEVAL(object, string) \ |
47 | QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string)))); |
48 | |
49 | inline QVariant runexpr(QQmlEngine *engine, const QString &str) |
50 | { |
51 | QQmlExpression expr(engine->rootContext(), nullptr, str); |
52 | return expr.evaluate(); |
53 | } |
54 | |
55 | #define RUNEXPR(string) runexpr(&engine, QString(string)) |
56 | |
57 | static bool isValidErrorMessage(const QString &msg, bool dynamicRoleTest) |
58 | { |
59 | bool valid = true; |
60 | |
61 | if (msg.isEmpty()) { |
62 | valid = false; |
63 | } else if (dynamicRoleTest) { |
64 | if (msg.contains(s: "Can't assign to existing role" ) || msg.contains(s: "Can't create role for unsupported data type" )) |
65 | valid = false; |
66 | } |
67 | |
68 | return valid; |
69 | } |
70 | |
71 | class tst_qqmllistmodelworkerscript : public QQmlDataTest |
72 | { |
73 | Q_OBJECT |
74 | public: |
75 | tst_qqmllistmodelworkerscript() |
76 | { |
77 | qRegisterMetaType<QVector<int> >(); |
78 | } |
79 | |
80 | private: |
81 | int roleFromName(const QQmlListModel *model, const QString &roleName); |
82 | QQuickItem *createWorkerTest(QQmlEngine *eng, QQmlComponent *component, QQmlListModel *model); |
83 | void waitForWorker(QQuickItem *item); |
84 | |
85 | static bool compareVariantList(const QVariantList &testList, QVariant object); |
86 | |
87 | private slots: |
88 | void dynamic_data(); |
89 | void dynamic_worker_data(); |
90 | void dynamic_worker(); |
91 | void dynamic_worker_sync_data(); |
92 | void dynamic_worker_sync(); |
93 | void get_data(); |
94 | void get_worker(); |
95 | void get_worker_data(); |
96 | void property_changes_data(); |
97 | void property_changes_worker(); |
98 | void property_changes_worker_data(); |
99 | void worker_sync_data(); |
100 | void worker_sync(); |
101 | void worker_remove_element_data(); |
102 | void worker_remove_element(); |
103 | void worker_remove_list_data(); |
104 | void worker_remove_list(); |
105 | void dynamic_role_data(); |
106 | void dynamic_role(); |
107 | void correctMoves(); |
108 | }; |
109 | |
110 | bool tst_qqmllistmodelworkerscript::compareVariantList(const QVariantList &testList, QVariant object) |
111 | { |
112 | bool allOk = true; |
113 | |
114 | QQmlListModel *model = qobject_cast<QQmlListModel *>(object: object.value<QObject *>()); |
115 | if (model == nullptr) |
116 | return false; |
117 | |
118 | if (model->count() != testList.count()) |
119 | return false; |
120 | |
121 | for (int i=0 ; i < testList.count() ; ++i) { |
122 | const QVariant &testVariant = testList.at(i); |
123 | if (testVariant.type() != QVariant::Map) |
124 | return false; |
125 | const QVariantMap &map = testVariant.toMap(); |
126 | |
127 | const QHash<int, QByteArray> roleNames = model->roleNames(); |
128 | |
129 | QVariantMap::const_iterator it = map.begin(); |
130 | QVariantMap::const_iterator end = map.end(); |
131 | |
132 | while (it != end) { |
133 | const QString &testKey = it.key(); |
134 | const QVariant &testData = it.value(); |
135 | |
136 | int roleIndex = roleNames.key(value: testKey.toUtf8(), defaultKey: -1); |
137 | if (roleIndex == -1) |
138 | return false; |
139 | |
140 | const QVariant &modelData = model->data(index: model->index(row: i, column: 0, parent: QModelIndex()), role: roleIndex); |
141 | |
142 | if (testData.type() == QVariant::List) { |
143 | const QVariantList &subList = testData.toList(); |
144 | allOk = allOk && compareVariantList(testList: subList, object: modelData); |
145 | } else { |
146 | allOk = allOk && (testData == modelData); |
147 | } |
148 | |
149 | ++it; |
150 | } |
151 | } |
152 | |
153 | return allOk; |
154 | } |
155 | |
156 | int tst_qqmllistmodelworkerscript::roleFromName(const QQmlListModel *model, const QString &roleName) |
157 | { |
158 | return model->roleNames().key(value: roleName.toUtf8(), defaultKey: -1); |
159 | } |
160 | |
161 | QQuickItem *tst_qqmllistmodelworkerscript::createWorkerTest(QQmlEngine *eng, QQmlComponent *component, QQmlListModel *model) |
162 | { |
163 | QQuickItem *item = qobject_cast<QQuickItem*>(object: component->create()); |
164 | QQmlEngine::setContextForObject(model, eng->rootContext()); |
165 | if (item) |
166 | item->setProperty(name: "model" , value: QVariant::fromValue(value: model)); |
167 | return item; |
168 | } |
169 | |
170 | void tst_qqmllistmodelworkerscript::waitForWorker(QQuickItem *item) |
171 | { |
172 | QQmlProperty prop(item, "done" ); |
173 | QVERIFY(prop.isValid()); |
174 | if (prop.read().toBool()) |
175 | return; // already finished |
176 | |
177 | QEventLoop loop; |
178 | QTimer timer; |
179 | timer.setSingleShot(true); |
180 | connect(sender: &timer, SIGNAL(timeout()), receiver: &loop, SLOT(quit())); |
181 | |
182 | QVERIFY(prop.connectNotifySignal(&loop, SLOT(quit()))); |
183 | timer.start(msec: 10000); |
184 | loop.exec(); |
185 | QVERIFY(timer.isActive()); |
186 | QVERIFY(prop.read().toBool()); |
187 | } |
188 | |
189 | void tst_qqmllistmodelworkerscript::dynamic_data() |
190 | { |
191 | QTest::addColumn<QString>(name: "script" ); |
192 | QTest::addColumn<int>(name: "result" ); |
193 | QTest::addColumn<QString>(name: "warning" ); |
194 | QTest::addColumn<bool>(name: "dynamicRoles" ); |
195 | |
196 | for (int i = 0; i < 2; ++i) { |
197 | bool dr = (i != 0); |
198 | |
199 | // Simple flat model |
200 | QTest::newRow(dataTag: "count" ) << "count" << 0 << "" << dr; |
201 | |
202 | QTest::newRow(dataTag: "get1" ) << "{get(0) === undefined}" << 1 << "" << dr; |
203 | QTest::newRow(dataTag: "get2" ) << "{get(-1) === undefined}" << 1 << "" << dr; |
204 | QTest::newRow(dataTag: "get3" ) << "{append({'foo':123});get(0) != undefined}" << 1 << "" << dr; |
205 | QTest::newRow(dataTag: "get4" ) << "{append({'foo':123});get(0).foo}" << 123 << "" << dr; |
206 | QTest::newRow(dataTag: "get-modify1" ) << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "" << dr; |
207 | QTest::newRow(dataTag: "get-modify2" ) << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "" << dr; |
208 | QTest::newRow(dataTag: "get-set" ) << "{append({'foo':123});get(0).foo;setProperty(0, 'foo', 999);get(0).foo}" << 999 << "" << dr; |
209 | |
210 | QTest::newRow(dataTag: "append1" ) << "{append({'foo':123});count}" << 1 << "" << dr; |
211 | QTest::newRow(dataTag: "append2" ) << "{append({'foo':123,'bar':456});count}" << 1 << "" << dr; |
212 | QTest::newRow(dataTag: "append3a" ) << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "" << dr; |
213 | QTest::newRow(dataTag: "append3b" ) << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "" << dr; |
214 | QTest::newRow(dataTag: "append4a" ) << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr; |
215 | QTest::newRow(dataTag: "append4b" ) << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "" << dr; |
216 | QTest::newRow(dataTag: "append4c" ) << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "" << dr; |
217 | |
218 | QTest::newRow(dataTag: "clear1" ) << "{append({'foo':456});clear();count}" << 0 << "" << dr; |
219 | QTest::newRow(dataTag: "clear2" ) << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "" << dr; |
220 | QTest::newRow(dataTag: "clear3" ) << "{append({'foo':123});clear()}" << 0 << "" << dr; |
221 | |
222 | QTest::newRow(dataTag: "remove1" ) << "{append({'foo':123});remove(0);count}" << 0 << "" << dr; |
223 | QTest::newRow(dataTag: "remove2a" ) << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "" << dr; |
224 | QTest::newRow(dataTag: "remove2b" ) << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "" << dr; |
225 | QTest::newRow(dataTag: "remove2c" ) << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "" << dr; |
226 | QTest::newRow(dataTag: "remove3" ) << "{append({'foo':123});remove(0)}" << 0 << "" << dr; |
227 | QTest::newRow(dataTag: "remove3a" ) << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]" << dr; |
228 | QTest::newRow(dataTag: "remove4a" ) << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr; |
229 | QTest::newRow(dataTag: "remove4b" ) << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr; |
230 | QTest::newRow(dataTag: "remove4c" ) << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [1 - 2] out of range [0 - 1]" << dr; |
231 | QTest::newRow(dataTag: "remove5a" ) << "{append({'foo':123});append({'foo':456});remove(0,2);count}" << 0 << "" << dr; |
232 | QTest::newRow(dataTag: "remove5b" ) << "{append({'foo':123});append({'foo':456});remove(0,1);count}" << 1 << "" << dr; |
233 | QTest::newRow(dataTag: "remove5c" ) << "{append({'foo':123});append({'foo':456});remove(1,1);count}" << 1 << "" << dr; |
234 | QTest::newRow(dataTag: "remove5d" ) << "{append({'foo':123});append({'foo':456});remove(0,1);get(0).foo}" << 456 << "" << dr; |
235 | QTest::newRow(dataTag: "remove5e" ) << "{append({'foo':123});append({'foo':456});remove(1,1);get(0).foo}" << 123 << "" << dr; |
236 | QTest::newRow(dataTag: "remove5f" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});remove(0,1);remove(1,1);get(0).foo}" << 456 << "" << dr; |
237 | QTest::newRow(dataTag: "remove6a" ) << "{remove();count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr; |
238 | QTest::newRow(dataTag: "remove6b" ) << "{remove(1,2,3);count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr; |
239 | QTest::newRow(dataTag: "remove7a" ) << "{append({'foo':123});remove(0,0);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - 0] out of range [0 - 1]" << dr; |
240 | QTest::newRow(dataTag: "remove7b" ) << "{append({'foo':123});remove(0,-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - -1] out of range [0 - 1]" << dr; |
241 | |
242 | QTest::newRow(dataTag: "insert1" ) << "{insert(0,{'foo':123});count}" << 1 << "" << dr; |
243 | QTest::newRow(dataTag: "insert2" ) << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range" << dr; |
244 | QTest::newRow(dataTag: "insert3a" ) << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "" << dr; |
245 | QTest::newRow(dataTag: "insert3b" ) << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "" << dr; |
246 | QTest::newRow(dataTag: "insert3c" ) << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "" << dr; |
247 | QTest::newRow(dataTag: "insert3d" ) << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "" << dr; |
248 | QTest::newRow(dataTag: "insert3e" ) << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "" << dr; |
249 | QTest::newRow(dataTag: "insert4" ) << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range" << dr; |
250 | QTest::newRow(dataTag: "insert5a" ) << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr; |
251 | QTest::newRow(dataTag: "insert5b" ) << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "" << dr; |
252 | QTest::newRow(dataTag: "insert5c" ) << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "" << dr; |
253 | |
254 | QTest::newRow(dataTag: "set1" ) << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "" << dr; |
255 | QTest::newRow(dataTag: "set2" ) << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "" << dr; |
256 | QTest::newRow(dataTag: "set3a" ) << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "" << dr; |
257 | QTest::newRow(dataTag: "set3b" ) << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "" << dr; |
258 | QTest::newRow(dataTag: "set4a" ) << "{set(0,{'foo':456});count}" << 1 << "" << dr; |
259 | QTest::newRow(dataTag: "set4c" ) << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr; |
260 | QTest::newRow(dataTag: "set5a" ) << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object" << dr; |
261 | QTest::newRow(dataTag: "set5b" ) << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "" << dr; |
262 | QTest::newRow(dataTag: "set6" ) << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "" << dr; |
263 | |
264 | QTest::newRow(dataTag: "setprop1" ) << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "" << dr; |
265 | QTest::newRow(dataTag: "setprop2" ) << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "" << dr; |
266 | QTest::newRow(dataTag: "setprop3a" ) << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "" << dr; |
267 | QTest::newRow(dataTag: "setprop3b" ) << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "" << dr; |
268 | QTest::newRow(dataTag: "setprop4a" ) << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range" << dr; |
269 | QTest::newRow(dataTag: "setprop4b" ) << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr; |
270 | QTest::newRow(dataTag: "setprop4c" ) << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range" << dr; |
271 | QTest::newRow(dataTag: "setprop5" ) << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "" << dr; |
272 | |
273 | QTest::newRow(dataTag: "move1a" ) << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "" << dr; |
274 | QTest::newRow(dataTag: "move1b" ) << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "" << dr; |
275 | QTest::newRow(dataTag: "move1c" ) << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "" << dr; |
276 | QTest::newRow(dataTag: "move1d" ) << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "" << dr; |
277 | QTest::newRow(dataTag: "move1e" ) << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "" << dr; |
278 | QTest::newRow(dataTag: "move2a" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "" << dr; |
279 | QTest::newRow(dataTag: "move2b" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "" << dr; |
280 | QTest::newRow(dataTag: "move2c" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "" << dr; |
281 | QTest::newRow(dataTag: "move2d" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "" << dr; |
282 | QTest::newRow(dataTag: "move3a" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr; |
283 | QTest::newRow(dataTag: "move3b" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr; |
284 | QTest::newRow(dataTag: "move3c" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr; |
285 | QTest::newRow(dataTag: "move3d" ) << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr; |
286 | |
287 | QTest::newRow(dataTag: "large1" ) << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "" << dr; |
288 | |
289 | QTest::newRow(dataTag: "datatypes1" ) << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [String -> Number]" << dr; |
290 | |
291 | QTest::newRow(dataTag: "null" ) << "{append({'a':null});}" << 0 << "" << dr; |
292 | QTest::newRow(dataTag: "setNull" ) << "{append({'a':1});set(0, {'a':null});}" << 0 << "" << dr; |
293 | QTest::newRow(dataTag: "setString" ) << "{append({'a':'hello'});set(0, {'a':'world'});get(0).a == 'world'}" << 1 << "" << dr; |
294 | QTest::newRow(dataTag: "setInt" ) << "{append({'a':5});set(0, {'a':10});get(0).a}" << 10 << "" << dr; |
295 | QTest::newRow(dataTag: "setNumber" ) << "{append({'a':6});set(0, {'a':5.5});get(0).a < 5.6}" << 1 << "" << dr; |
296 | QTest::newRow(dataTag: "badType0" ) << "{append({'a':'hello'});set(0, {'a':1});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [Number -> String]" << dr; |
297 | QTest::newRow(dataTag: "invalidInsert0" ) << "{insert(0);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr; |
298 | QTest::newRow(dataTag: "invalidAppend0" ) << "{append();}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr; |
299 | QTest::newRow(dataTag: "invalidInsert1" ) << "{insert(0, 34);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr; |
300 | QTest::newRow(dataTag: "invalidAppend1" ) << "{append(37);}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr; |
301 | |
302 | // QObjects |
303 | QTest::newRow(dataTag: "qobject0" ) << "{append({'a':dummyItem0});}" << 0 << "" << dr; |
304 | QTest::newRow(dataTag: "qobject1" ) << "{append({'a':dummyItem0});set(0,{'a':dummyItem1});get(0).a == dummyItem1;}" << 1 << "" << dr; |
305 | QTest::newRow(dataTag: "qobject2" ) << "{append({'a':dummyItem0});get(0).a == dummyItem0;}" << 1 << "" << dr; |
306 | QTest::newRow(dataTag: "qobject3" ) << "{append({'a':dummyItem0});append({'b':1});}" << 0 << "" << dr; |
307 | |
308 | // JS objects |
309 | QTest::newRow(dataTag: "js1" ) << "{append({'foo':{'prop':1}});count}" << 1 << "" << dr; |
310 | QTest::newRow(dataTag: "js2" ) << "{append({'foo':{'prop':27}});get(0).foo.prop}" << 27 << "" << dr; |
311 | QTest::newRow(dataTag: "js3" ) << "{append({'foo':{'prop':27}});append({'bar':1});count}" << 2 << "" << dr; |
312 | QTest::newRow(dataTag: "js4" ) << "{append({'foo':{'prop':27}});append({'bar':1});set(0, {'foo':{'prop':28}});get(0).foo.prop}" << 28 << "" << dr; |
313 | QTest::newRow(dataTag: "js5" ) << "{append({'foo':{'prop':27}});append({'bar':1});set(1, {'foo':{'prop':33}});get(1).foo.prop}" << 33 << "" << dr; |
314 | QTest::newRow(dataTag: "js6" ) << "{append({'foo':{'prop':27}});clear();count}" << 0 << "" << dr; |
315 | QTest::newRow(dataTag: "js7" ) << "{append({'foo':{'prop':27}});set(0, {'foo':null});count}" << 1 << "" << dr; |
316 | QTest::newRow(dataTag: "js8" ) << "{append({'foo':{'prop':27}});set(0, {'foo':{'prop2':31}});get(0).foo.prop2}" << 31 << "" << dr; |
317 | |
318 | // Nested models |
319 | QTest::newRow(dataTag: "nested-append1" ) << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "" << dr; |
320 | QTest::newRow(dataTag: "nested-append2" ) << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "" << dr; |
321 | QTest::newRow(dataTag: "nested-append3" ) << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.append({'a':4});get(0).bars.get(3).a}" << 4 << "" << dr; |
322 | |
323 | QTest::newRow(dataTag: "nested-insert" ) << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "" << dr; |
324 | QTest::newRow(dataTag: "nested-set" ) << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "" << dr; |
325 | |
326 | QTest::newRow(dataTag: "nested-count" ) << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "" << dr; |
327 | QTest::newRow(dataTag: "nested-clear" ) << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "" << dr; |
328 | } |
329 | } |
330 | |
331 | void tst_qqmllistmodelworkerscript::dynamic_worker_data() |
332 | { |
333 | dynamic_data(); |
334 | } |
335 | |
336 | void tst_qqmllistmodelworkerscript::dynamic_worker() |
337 | { |
338 | QFETCH(QString, script); |
339 | QFETCH(int, result); |
340 | QFETCH(QString, warning); |
341 | QFETCH(bool, dynamicRoles); |
342 | |
343 | if (QByteArray(QTest::currentDataTag()).startsWith(c: "qobject" )) |
344 | return; |
345 | |
346 | // This is same as dynamic() except it applies the test to a ListModel called |
347 | // from a WorkerScript. |
348 | |
349 | QQmlListModel model; |
350 | model.setDynamicRoles(dynamicRoles); |
351 | QQmlEngine eng; |
352 | QQmlComponent component(&eng, testFileUrl(fileName: "model.qml" )); |
353 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model: &model); |
354 | QVERIFY(item != nullptr); |
355 | |
356 | QSignalSpy spyCount(&model, SIGNAL(countChanged())); |
357 | |
358 | if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}')) |
359 | script = script.mid(position: 1, n: script.length() - 2); |
360 | QVariantList operations; |
361 | foreach (const QString &s, script.split(';')) { |
362 | if (!s.isEmpty()) |
363 | operations << s; |
364 | } |
365 | |
366 | if (isValidErrorMessage(msg: warning, dynamicRoleTest: dynamicRoles)) |
367 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1()); |
368 | |
369 | QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker" , |
370 | Q_ARG(QVariant, operations))); |
371 | waitForWorker(item); |
372 | QCOMPARE(QQmlProperty(item, "result" ).read().toInt(), result); |
373 | |
374 | if (model.count() > 0) |
375 | QVERIFY(spyCount.count() > 0); |
376 | |
377 | delete item; |
378 | qApp->processEvents(); |
379 | } |
380 | |
381 | void tst_qqmllistmodelworkerscript::dynamic_worker_sync_data() |
382 | { |
383 | dynamic_data(); |
384 | } |
385 | |
386 | void tst_qqmllistmodelworkerscript::dynamic_worker_sync() |
387 | { |
388 | QFETCH(QString, script); |
389 | QFETCH(int, result); |
390 | QFETCH(QString, warning); |
391 | QFETCH(bool, dynamicRoles); |
392 | |
393 | if (QByteArray(QTest::currentDataTag()).startsWith(c: "qobject" )) |
394 | return; |
395 | |
396 | // This is the same as dynamic_worker() except that it executes a set of list operations |
397 | // from the worker script, calls sync(), and tests the changes are reflected in the |
398 | // list in the main thread |
399 | |
400 | QQmlListModel model; |
401 | model.setDynamicRoles(dynamicRoles); |
402 | QQmlEngine eng; |
403 | QQmlComponent component(&eng, testFileUrl(fileName: "model.qml" )); |
404 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model: &model); |
405 | QVERIFY(item != nullptr); |
406 | |
407 | if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}')) |
408 | script = script.mid(position: 1, n: script.length() - 2); |
409 | QVariantList operations; |
410 | foreach (const QString &s, script.split(';')) { |
411 | if (!s.isEmpty()) |
412 | operations << s; |
413 | } |
414 | |
415 | if (isValidErrorMessage(msg: warning, dynamicRoleTest: dynamicRoles)) |
416 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1()); |
417 | |
418 | // execute a set of commands on the worker list model, then check the |
419 | // changes are reflected in the list model in the main thread |
420 | QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker" , |
421 | Q_ARG(QVariant, operations.mid(0, operations.length()-1)))); |
422 | waitForWorker(item); |
423 | |
424 | QQmlExpression e(eng.rootContext(), &model, operations.last().toString()); |
425 | QCOMPARE(e.evaluate().toInt(), result); |
426 | |
427 | delete item; |
428 | qApp->processEvents(); |
429 | } |
430 | |
431 | void tst_qqmllistmodelworkerscript::get_data() |
432 | { |
433 | QTest::addColumn<QString>(name: "expression" ); |
434 | QTest::addColumn<int>(name: "index" ); |
435 | QTest::addColumn<QString>(name: "roleName" ); |
436 | QTest::addColumn<QVariant>(name: "roleValue" ); |
437 | QTest::addColumn<bool>(name: "dynamicRoles" ); |
438 | |
439 | for (int i =0; i < 2; ++i) { |
440 | bool dr = (i != 0); |
441 | |
442 | QTest::newRow(dataTag: "simple value" ) << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500) << dr; |
443 | QTest::newRow(dataTag: "simple value 2" ) << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500) << dr; |
444 | |
445 | QVariantMap map; |
446 | QVariantList list; |
447 | map.clear(); map["a" ] = 50; map["b" ] = 500; |
448 | list << map; |
449 | map.clear(); map["c" ] = 1000; |
450 | list << map; |
451 | QTest::newRow(dataTag: "list of objects" ) << "get(2).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(value: list) << dr; |
452 | } |
453 | } |
454 | |
455 | void tst_qqmllistmodelworkerscript::get_worker() |
456 | { |
457 | QFETCH(QString, expression); |
458 | QFETCH(int, index); |
459 | QFETCH(QString, roleName); |
460 | QFETCH(QVariant, roleValue); |
461 | QFETCH(bool, dynamicRoles); |
462 | |
463 | QQmlListModel model; |
464 | model.setDynamicRoles(dynamicRoles); |
465 | QQmlEngine eng; |
466 | QQmlComponent component(&eng, testFileUrl(fileName: "model.qml" )); |
467 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model: &model); |
468 | QVERIFY(item != nullptr); |
469 | |
470 | // Add some values like get() test |
471 | RUNEVAL(item, "model.append({roleA: 100})" ); |
472 | RUNEVAL(item, "model.append({roleA: 200, roleB: 400})" ); |
473 | RUNEVAL(item, "model.append({roleA: 200, roleB: 400})" ); |
474 | RUNEVAL(item, "model.append({roleC: {} })" ); |
475 | RUNEVAL(item, "model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })" ); |
476 | |
477 | int role = roleFromName(model: &model, roleName); |
478 | QVERIFY(role >= 0); |
479 | |
480 | QSignalSpy spy(&model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>))); |
481 | |
482 | // in the worker thread, change the model data and call sync() |
483 | QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker" , |
484 | Q_ARG(QVariant, QStringList(expression)))); |
485 | waitForWorker(item); |
486 | |
487 | // see if we receive the model changes in the main thread's model |
488 | if (roleValue.type() == QVariant::List) { |
489 | const QVariantList &list = roleValue.toList(); |
490 | QVERIFY(compareVariantList(list, model.data(index, role))); |
491 | } else { |
492 | QCOMPARE(model.data(index, role), roleValue); |
493 | } |
494 | |
495 | QCOMPARE(spy.count(), 1); |
496 | |
497 | QList<QVariant> spyResult = spy.takeFirst(); |
498 | QCOMPARE(spyResult.at(0).value<QModelIndex>(), model.index(index, 0, QModelIndex())); |
499 | QCOMPARE(spyResult.at(1).value<QModelIndex>(), model.index(index, 0, QModelIndex())); // only 1 item is modified at a time |
500 | QVERIFY(spyResult.at(2).value<QVector<int> >().contains(role)); |
501 | |
502 | delete item; |
503 | } |
504 | |
505 | void tst_qqmllistmodelworkerscript::get_worker_data() |
506 | { |
507 | get_data(); |
508 | } |
509 | |
510 | void tst_qqmllistmodelworkerscript::property_changes_data() |
511 | { |
512 | QTest::addColumn<QString>(name: "script_setup" ); |
513 | QTest::addColumn<QString>(name: "script_change" ); |
514 | QTest::addColumn<QString>(name: "roleName" ); |
515 | QTest::addColumn<int>(name: "listIndex" ); |
516 | QTest::addColumn<bool>(name: "itemsChanged" ); |
517 | QTest::addColumn<QString>(name: "testExpression" ); |
518 | QTest::addColumn<bool>(name: "dynamicRoles" ); |
519 | |
520 | for (int i=1 ; i < 2 ; ++i) { |
521 | bool dr = (i != 0); |
522 | |
523 | QTest::newRow(dataTag: "set: plain" ) << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});" |
524 | << "b" << 0 << true << "get(0).b == 123" << dr; |
525 | QTest::newRow(dataTag: "setProperty: plain" ) << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);" |
526 | << "b" << 0 << true << "get(0).b == 123" << dr; |
527 | |
528 | QTest::newRow(dataTag: "set: plain, no changes" ) << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});" |
529 | << "b" << 0 << false << "get(0).b == 456" << dr; |
530 | QTest::newRow(dataTag: "setProperty: plain, no changes" ) << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);" |
531 | << "b" << 0 << false << "get(0).b == 456" << dr; |
532 | |
533 | QTest::newRow(dataTag: "set: inserted item" ) |
534 | << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}" |
535 | << "set(1, {'a':456});" |
536 | << "a" << 1 << true << "get(1).a == 456" << dr; |
537 | QTest::newRow(dataTag: "setProperty: inserted item" ) |
538 | << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}" |
539 | << "setProperty(1, 'a', 456);" |
540 | << "a" << 1 << true << "get(1).a == 456" << dr; |
541 | QTest::newRow(dataTag: "get: inserted item" ) |
542 | << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}" |
543 | << "get(1).a = 456;" |
544 | << "a" << 1 << true << "get(1).a == 456" << dr; |
545 | QTest::newRow(dataTag: "set: removed item" ) |
546 | << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}" |
547 | << "set(0, {'a':456});" |
548 | << "a" << 0 << true << "get(0).a == 456" << dr; |
549 | QTest::newRow(dataTag: "setProperty: removed item" ) |
550 | << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}" |
551 | << "setProperty(0, 'a', 456);" |
552 | << "a" << 0 << true << "get(0).a == 456" << dr; |
553 | QTest::newRow(dataTag: "get: removed item" ) |
554 | << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}" |
555 | << "get(0).a = 456;" |
556 | << "a" << 0 << true << "get(0).a == 456" << dr; |
557 | |
558 | // Following tests only call set() since setProperty() only allows plain |
559 | // values, not lists, as the argument. |
560 | // Note that when a list is changed, itemsChanged() is currently always |
561 | // emitted regardless of whether it actually changed or not. |
562 | |
563 | QTest::newRow(dataTag: "nested-set: list, new size" ) << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});" |
564 | << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2" << dr; |
565 | |
566 | QTest::newRow(dataTag: "nested-set: list, empty -> non-empty" ) << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});" |
567 | << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr; |
568 | |
569 | QTest::newRow(dataTag: "nested-set: list, non-empty -> empty" ) << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});" |
570 | << "b" << 0 << true << "get(0).b.count == 0" << dr; |
571 | |
572 | QTest::newRow(dataTag: "nested-set: list, same size, different values" ) << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':222},{'a':3}]});" |
573 | << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3" << dr; |
574 | |
575 | QTest::newRow(dataTag: "nested-set: list, no changes" ) << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});" |
576 | << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr; |
577 | |
578 | QTest::newRow(dataTag: "nested-set: list, no changes, empty" ) << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});" |
579 | << "b" << 0 << false << "get(0).b.count == 0" << dr; |
580 | } |
581 | } |
582 | |
583 | void tst_qqmllistmodelworkerscript::property_changes_worker() |
584 | { |
585 | QFETCH(QString, script_setup); |
586 | QFETCH(QString, script_change); |
587 | QFETCH(QString, roleName); |
588 | QFETCH(int, listIndex); |
589 | QFETCH(bool, itemsChanged); |
590 | QFETCH(bool, dynamicRoles); |
591 | |
592 | QQmlListModel model; |
593 | model.setDynamicRoles(dynamicRoles); |
594 | QQmlEngine engine; |
595 | QQmlComponent component(&engine, testFileUrl(fileName: "model.qml" )); |
596 | QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); |
597 | QQuickItem *item = createWorkerTest(eng: &engine, component: &component, model: &model); |
598 | QVERIFY(item != nullptr); |
599 | |
600 | QQmlExpression expr(engine.rootContext(), &model, script_setup); |
601 | expr.evaluate(); |
602 | QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString())); |
603 | |
604 | QSignalSpy spyItemsChanged(&model, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>))); |
605 | |
606 | QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker" , |
607 | Q_ARG(QVariant, QStringList(script_change)))); |
608 | waitForWorker(item); |
609 | |
610 | // test itemsChanged() is emitted correctly |
611 | if (itemsChanged) { |
612 | QCOMPARE(spyItemsChanged.count(), 1); |
613 | QCOMPARE(spyItemsChanged.at(0).at(0).value<QModelIndex>(), model.index(listIndex, 0, QModelIndex())); |
614 | QCOMPARE(spyItemsChanged.at(0).at(1).value<QModelIndex>(), model.index(listIndex, 0, QModelIndex())); |
615 | } else { |
616 | QCOMPARE(spyItemsChanged.count(), 0); |
617 | } |
618 | |
619 | delete item; |
620 | qApp->processEvents(); |
621 | } |
622 | |
623 | void tst_qqmllistmodelworkerscript::property_changes_worker_data() |
624 | { |
625 | property_changes_data(); |
626 | } |
627 | |
628 | void tst_qqmllistmodelworkerscript::worker_sync_data() |
629 | { |
630 | QTest::addColumn<bool>(name: "dynamicRoles" ); |
631 | |
632 | QTest::newRow(dataTag: "staticRoles" ) << false; |
633 | QTest::newRow(dataTag: "dynamicRoles" ) << true; |
634 | } |
635 | |
636 | void tst_qqmllistmodelworkerscript::worker_sync() |
637 | { |
638 | QFETCH(bool, dynamicRoles); |
639 | |
640 | QQmlListModel model; |
641 | model.setDynamicRoles(dynamicRoles); |
642 | QQmlEngine eng; |
643 | QQmlComponent component(&eng, testFileUrl(fileName: "workersync.qml" )); |
644 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model: &model); |
645 | QVERIFY(item != nullptr); |
646 | |
647 | QCOMPARE(model.count(), 0); |
648 | |
649 | QVERIFY(QMetaObject::invokeMethod(item, "addItem0" )); |
650 | |
651 | QCOMPARE(model.count(), 2); |
652 | QVariant childData = model.data(index: 0, role: 0); |
653 | QQmlListModel *childModel = qobject_cast<QQmlListModel *>(object: childData.value<QObject *>()); |
654 | QVERIFY(childModel); |
655 | QCOMPARE(childModel->count(), 1); |
656 | |
657 | QSignalSpy spyModelInserted(&model, SIGNAL(rowsInserted(QModelIndex,int,int))); |
658 | QSignalSpy spyChildInserted(childModel, SIGNAL(rowsInserted(QModelIndex,int,int))); |
659 | |
660 | QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker" )); |
661 | waitForWorker(item); |
662 | |
663 | QCOMPARE(model.count(), 2); |
664 | QCOMPARE(childModel->count(), 1); |
665 | QCOMPARE(spyModelInserted.count(), 0); |
666 | QCOMPARE(spyChildInserted.count(), 0); |
667 | |
668 | QVERIFY(QMetaObject::invokeMethod(item, "doSync" )); |
669 | waitForWorker(item); |
670 | |
671 | QCOMPARE(model.count(), 2); |
672 | QCOMPARE(childModel->count(), 2); |
673 | QCOMPARE(spyModelInserted.count(), 0); |
674 | QCOMPARE(spyChildInserted.count(), 1); |
675 | |
676 | QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker" )); |
677 | waitForWorker(item); |
678 | |
679 | QCOMPARE(model.count(), 2); |
680 | QCOMPARE(childModel->count(), 2); |
681 | QCOMPARE(spyModelInserted.count(), 0); |
682 | QCOMPARE(spyChildInserted.count(), 1); |
683 | |
684 | QVERIFY(QMetaObject::invokeMethod(item, "doSync" )); |
685 | waitForWorker(item); |
686 | |
687 | QCOMPARE(model.count(), 2); |
688 | QCOMPARE(childModel->count(), 3); |
689 | QCOMPARE(spyModelInserted.count(), 0); |
690 | QCOMPARE(spyChildInserted.count(), 2); |
691 | |
692 | delete item; |
693 | qApp->processEvents(); |
694 | } |
695 | |
696 | void tst_qqmllistmodelworkerscript::worker_remove_element_data() |
697 | { |
698 | worker_sync_data(); |
699 | } |
700 | |
701 | void tst_qqmllistmodelworkerscript::worker_remove_element() |
702 | { |
703 | QFETCH(bool, dynamicRoles); |
704 | |
705 | QQmlListModel model; |
706 | model.setDynamicRoles(dynamicRoles); |
707 | QQmlEngine eng; |
708 | QQmlComponent component(&eng, testFileUrl(fileName: "workerremoveelement.qml" )); |
709 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model: &model); |
710 | QVERIFY(item != nullptr); |
711 | |
712 | QSignalSpy spyModelRemoved(&model, SIGNAL(rowsRemoved(QModelIndex,int,int))); |
713 | |
714 | QCOMPARE(model.count(), 0); |
715 | QCOMPARE(spyModelRemoved.count(), 0); |
716 | |
717 | QVERIFY(QMetaObject::invokeMethod(item, "addItem" )); |
718 | |
719 | QCOMPARE(model.count(), 1); |
720 | |
721 | QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker" )); |
722 | waitForWorker(item); |
723 | |
724 | QCOMPARE(model.count(), 1); |
725 | QCOMPARE(spyModelRemoved.count(), 0); |
726 | |
727 | QVERIFY(QMetaObject::invokeMethod(item, "doSync" )); |
728 | waitForWorker(item); |
729 | |
730 | QCOMPARE(model.count(), 0); |
731 | QCOMPARE(spyModelRemoved.count(), 1); |
732 | |
733 | delete item; |
734 | qApp->processEvents(); |
735 | |
736 | { |
737 | //don't crash if model was deleted earlier |
738 | QQmlListModel* model = new QQmlListModel; |
739 | model->setDynamicRoles(dynamicRoles); |
740 | QQmlEngine eng; |
741 | QQmlComponent component(&eng, testFileUrl(fileName: "workerremoveelement.qml" )); |
742 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model); |
743 | QVERIFY(item != nullptr); |
744 | |
745 | QVERIFY(QMetaObject::invokeMethod(item, "addItem" )); |
746 | |
747 | QCOMPARE(model->count(), 1); |
748 | |
749 | QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker" )); |
750 | QVERIFY(QMetaObject::invokeMethod(item, "doSync" )); |
751 | delete model; |
752 | qApp->processEvents(); //must not crash here |
753 | waitForWorker(item); |
754 | |
755 | delete item; |
756 | } |
757 | } |
758 | |
759 | void tst_qqmllistmodelworkerscript::worker_remove_list_data() |
760 | { |
761 | worker_sync_data(); |
762 | } |
763 | |
764 | void tst_qqmllistmodelworkerscript::worker_remove_list() |
765 | { |
766 | QFETCH(bool, dynamicRoles); |
767 | |
768 | QQmlListModel model; |
769 | model.setDynamicRoles(dynamicRoles); |
770 | QQmlEngine eng; |
771 | QQmlComponent component(&eng, testFileUrl(fileName: "workerremovelist.qml" )); |
772 | QQuickItem *item = createWorkerTest(eng: &eng, component: &component, model: &model); |
773 | QVERIFY(item != nullptr); |
774 | |
775 | QSignalSpy spyModelRemoved(&model, SIGNAL(rowsRemoved(QModelIndex,int,int))); |
776 | |
777 | QCOMPARE(model.count(), 0); |
778 | QCOMPARE(spyModelRemoved.count(), 0); |
779 | |
780 | QVERIFY(QMetaObject::invokeMethod(item, "addList" )); |
781 | |
782 | QCOMPARE(model.count(), 1); |
783 | |
784 | QVERIFY(QMetaObject::invokeMethod(item, "removeListViaWorker" )); |
785 | waitForWorker(item); |
786 | |
787 | QCOMPARE(model.count(), 1); |
788 | QCOMPARE(spyModelRemoved.count(), 0); |
789 | |
790 | QVERIFY(QMetaObject::invokeMethod(item, "doSync" )); |
791 | waitForWorker(item); |
792 | |
793 | QCOMPARE(model.count(), 0); |
794 | QCOMPARE(spyModelRemoved.count(), 1); |
795 | |
796 | delete item; |
797 | qApp->processEvents(); |
798 | } |
799 | |
800 | void tst_qqmllistmodelworkerscript::dynamic_role_data() |
801 | { |
802 | QTest::addColumn<QString>(name: "preamble" ); |
803 | QTest::addColumn<QString>(name: "script" ); |
804 | QTest::addColumn<int>(name: "result" ); |
805 | |
806 | QTest::newRow(dataTag: "sync1" ) << "{append({'a':[{'b':1},{'b':2}]})}" << "{get(0).a = 'string';count}" << 1; |
807 | } |
808 | |
809 | void tst_qqmllistmodelworkerscript::dynamic_role() |
810 | { |
811 | QFETCH(QString, preamble); |
812 | QFETCH(QString, script); |
813 | QFETCH(int, result); |
814 | |
815 | QQmlListModel model; |
816 | model.setDynamicRoles(true); |
817 | QQmlEngine engine; |
818 | QQmlComponent component(&engine, testFileUrl(fileName: "model.qml" )); |
819 | QQuickItem *item = createWorkerTest(eng: &engine, component: &component, model: &model); |
820 | QVERIFY(item != nullptr); |
821 | |
822 | QQmlExpression preExp(engine.rootContext(), &model, preamble); |
823 | QCOMPARE(preExp.evaluate().toInt(), 0); |
824 | |
825 | if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}')) |
826 | script = script.mid(position: 1, n: script.length() - 2); |
827 | QVariantList operations; |
828 | foreach (const QString &s, script.split(';')) { |
829 | if (!s.isEmpty()) |
830 | operations << s; |
831 | } |
832 | |
833 | // execute a set of commands on the worker list model, then check the |
834 | // changes are reflected in the list model in the main thread |
835 | QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker" , |
836 | Q_ARG(QVariant, operations.mid(0, operations.length()-1)))); |
837 | waitForWorker(item); |
838 | |
839 | QQmlExpression e(engine.rootContext(), &model, operations.last().toString()); |
840 | QCOMPARE(e.evaluate().toInt(), result); |
841 | |
842 | delete item; |
843 | qApp->processEvents(); |
844 | } |
845 | |
846 | void tst_qqmllistmodelworkerscript::correctMoves() |
847 | { |
848 | QQmlEngine engine; |
849 | QQmlComponent component(&engine, testFileUrl(fileName: "listmodel_async_sort/main.qml" )); |
850 | QScopedPointer<QObject> root {component.create()}; |
851 | QVERIFY2(root, qPrintable(component.errorString())); |
852 | bool ok =QMetaObject::invokeMethod(obj: root.get(), member: "doSort" ); |
853 | QVERIFY(ok); |
854 | auto check = [&](){ |
855 | bool success = false; |
856 | QMetaObject::invokeMethod(obj: root.get(), member: "verify" , Q_RETURN_ARG(bool, success)); |
857 | return success; |
858 | }; |
859 | QTRY_VERIFY(check()); |
860 | } |
861 | |
862 | QTEST_MAIN(tst_qqmllistmodelworkerscript) |
863 | |
864 | #include "tst_qqmllistmodelworkerscript.moc" |
865 | |