1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <qtest.h>
42 #include <QtDeclarative/private/qdeclarativeitem_p.h>
43 #include <QtDeclarative/private/qdeclarativetext_p.h>
44 #include <QtDeclarative/private/qdeclarativeengine_p.h>
45 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
46 #include <QtDeclarative/private/qdeclarativeexpression_p.h>
47 #include <QDeclarativeComponent>
48
49 #include <QtCore/qtimer.h>
50 #include <QtCore/qdebug.h>
51 #include <QtCore/qtranslator.h>
52 #include <QSignalSpy>
53
54 #include "../../../shared/util.h"
55
56 #ifdef Q_OS_SYMBIAN
57 // In Symbian OS test data is located in applications private dir
58 #define SRCDIR "."
59 #endif
60
61 Q_DECLARE_METATYPE(QList<int>)
62 Q_DECLARE_METATYPE(QList<QVariantHash>)
63
64 class tst_qdeclarativelistmodel : public QObject
65 {
66 Q_OBJECT
67 public:
tst_qdeclarativelistmodel()68 tst_qdeclarativelistmodel() {}
69
70 private:
71 int roleFromName(const QDeclarativeListModel *model, const QString &roleName);
72 QScriptValue nestedListValue(QScriptEngine *eng) const;
73 QDeclarativeItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
74 void waitForWorker(QDeclarativeItem *item);
75
76 private slots:
77 void static_types();
78 void static_types_data();
79 void static_i18n();
80 void static_nestedElements();
81 void static_nestedElements_data();
82 void dynamic_data();
83 void dynamic();
84 void dynamic_worker_data();
85 void dynamic_worker();
86 void dynamic_worker_sync_data();
87 void dynamic_worker_sync();
88 void convertNestedToFlat_fail();
89 void convertNestedToFlat_fail_data();
90 void convertNestedToFlat_ok();
91 void convertNestedToFlat_ok_data();
92 void enumerate();
93 void error_data();
94 void error();
95 void syncError();
96 void set();
97 void get();
98 void get_data();
99 void get_worker();
100 void get_worker_data();
101 void get_nested();
102 void get_nested_data();
103 void crash_model_with_multiple_roles();
104 void set_model_cache();
105 void property_changes();
106 void property_changes_data();
107 void property_changes_worker();
108 void property_changes_worker_data();
109 void clear();
110 };
roleFromName(const QDeclarativeListModel * model,const QString & roleName)111 int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
112 {
113 QList<int> roles = model->roles();
114 for (int i=0; i<roles.count(); i++) {
115 if (model->toString(roles[i]) == roleName)
116 return roles[i];
117 }
118 return -1;
119 }
120
nestedListValue(QScriptEngine * eng) const121 QScriptValue tst_qdeclarativelistmodel::nestedListValue(QScriptEngine *eng) const
122 {
123 QScriptValue list = eng->newArray();
124 list.setProperty(0, eng->newObject());
125 list.setProperty(1, eng->newObject());
126 QScriptValue sv = eng->newObject();
127 sv.setProperty("foo", list);
128 return sv;
129 }
130
createWorkerTest(QDeclarativeEngine * eng,QDeclarativeComponent * component,QDeclarativeListModel * model)131 QDeclarativeItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model)
132 {
133 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(component->create());
134 QDeclarativeEngine::setContextForObject(model, eng->rootContext());
135 if (item)
136 item->setProperty("model", qVariantFromValue(model));
137 return item;
138 }
139
waitForWorker(QDeclarativeItem * item)140 void tst_qdeclarativelistmodel::waitForWorker(QDeclarativeItem *item)
141 {
142 QEventLoop loop;
143 QTimer timer;
144 timer.setSingleShot(true);
145 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
146
147 QDeclarativeProperty prop(item, "done");
148 QVERIFY(prop.isValid());
149 QVERIFY(prop.connectNotifySignal(&loop, SLOT(quit())));
150 timer.start(10000);
151 loop.exec();
152 QVERIFY(timer.isActive());
153 }
154
static_i18n()155 void tst_qdeclarativelistmodel::static_i18n()
156 {
157 QString expect = QString::fromUtf8("na\303\257ve");
158
159 QString componentStr = "import QtQuick 1.0\nListModel { ListElement { prop1: \""+expect+"\"; prop2: QT_TR_NOOP(\""+expect+"\") } }";
160 QDeclarativeEngine engine;
161 QDeclarativeComponent component(&engine);
162 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
163 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
164 QVERIFY(obj != 0);
165 QString prop1 = obj->get(0).property(QLatin1String("prop1")).toString();
166 QCOMPARE(prop1,expect);
167 QString prop2 = obj->get(0).property(QLatin1String("prop2")).toString();
168 QCOMPARE(prop2,expect); // (no, not translated, QT_TR_NOOP is a no-op)
169 delete obj;
170 }
171
static_nestedElements()172 void tst_qdeclarativelistmodel::static_nestedElements()
173 {
174 QFETCH(int, elementCount);
175
176 QStringList elements;
177 for (int i=0; i<elementCount; i++)
178 elements.append("ListElement { a: 1; b: 2 }");
179 QString elementsStr = elements.join(",\n") + "\n";
180
181 QString componentStr =
182 "import QtQuick 1.0\n"
183 "ListModel {\n"
184 " ListElement {\n"
185 " attributes: [\n";
186 componentStr += elementsStr.toUtf8().constData();
187 componentStr +=
188 " ]\n"
189 " }\n"
190 "}";
191
192 QDeclarativeEngine engine;
193 QDeclarativeComponent component(&engine);
194 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
195
196 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
197 QVERIFY(obj != 0);
198
199 QScriptValue prop = obj->get(0).property(QLatin1String("attributes")).property(QLatin1String("count"));
200 QVERIFY(prop.isNumber());
201 QCOMPARE(prop.toInt32(), qint32(elementCount));
202
203 delete obj;
204 }
205
static_nestedElements_data()206 void tst_qdeclarativelistmodel::static_nestedElements_data()
207 {
208 QTest::addColumn<int>("elementCount");
209
210 QTest::newRow("0 items") << 0;
211 QTest::newRow("1 item") << 1;
212 QTest::newRow("2 items") << 2;
213 QTest::newRow("many items") << 5;
214 }
215
dynamic_data()216 void tst_qdeclarativelistmodel::dynamic_data()
217 {
218 QTest::addColumn<QString>("script");
219 QTest::addColumn<int>("result");
220 QTest::addColumn<QString>("warning");
221
222 // Simple flat model
223
224 QTest::newRow("count") << "count" << 0 << "";
225
226 QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
227 QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "";
228 QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "";
229 QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "";
230
231 QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "";
232 QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "";
233
234 QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "";
235 QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "";
236 QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
237 QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
238 QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
239 QTest::newRow("append4b") << "{append([1,2,3])}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
240
241 QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "";
242 QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "";
243 QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "";
244
245 QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "";
246 QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "";
247 QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "";
248 QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "";
249 QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "";
250 QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: index -1 out of range";
251 QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: index 0 out of range";
252 QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: index 0 out of range";
253 QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: index 1 out of range";
254
255 QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "";
256 QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range";
257 QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "";
258 QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "";
259 QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "";
260 QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "";
261 QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "";
262 QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range";
263 QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
264 QTest::newRow("insert5b") << "{insert(0,[1,2,3])}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
265
266 QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "";
267 QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "";
268 QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "";
269 QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "";
270 QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "";
271 QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
272 QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
273 QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
274 QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "";
275
276 QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "";
277 QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "";
278 QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "";
279 QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "";
280 QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range";
281 QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
282 QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range";
283 QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "";
284
285 QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "";
286 QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "";
287 QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "";
288 QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "";
289 QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "";
290 QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "";
291 QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "";
292 QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "";
293 QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "";
294 QTest::newRow("move3a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
295 QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
296 QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
297 QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
298
299 // Nested models
300
301 QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
302 QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";
303 QTest::newRow("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 << "";
304
305 QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "";
306 QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "";
307
308 QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "";
309 QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "";
310 }
311
dynamic()312 void tst_qdeclarativelistmodel::dynamic()
313 {
314 QFETCH(QString, script);
315 QFETCH(int, result);
316 QFETCH(QString, warning);
317
318 QDeclarativeEngine engine;
319 QDeclarativeListModel model;
320 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
321 engine.rootContext()->setContextObject(&model);
322 QDeclarativeExpression e(engine.rootContext(), &model, script);
323 if (!warning.isEmpty())
324 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
325
326 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
327
328 int actual = e.evaluate().toInt();
329 if (e.hasError())
330 qDebug() << e.error(); // errors not expected
331
332 QCOMPARE(actual,result);
333
334 if (model.count() > 0)
335 QVERIFY(spyCount.count() > 0);
336 }
337
dynamic_worker_data()338 void tst_qdeclarativelistmodel::dynamic_worker_data()
339 {
340 dynamic_data();
341 }
342
dynamic_worker()343 void tst_qdeclarativelistmodel::dynamic_worker()
344 {
345 QFETCH(QString, script);
346 QFETCH(int, result);
347 QFETCH(QString, warning);
348
349 if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
350 return;
351
352 // This is same as dynamic() except it applies the test to a ListModel called
353 // from a WorkerScript (i.e. testing the internal FlatListModel that is created
354 // by the WorkerListModelAgent)
355
356 QDeclarativeListModel model;
357 QDeclarativeEngine eng;
358 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
359 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
360 QVERIFY(item != 0);
361
362 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
363
364 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
365 script = script.mid(1, script.length() - 2);
366 QVariantList operations;
367 foreach (const QString &s, script.split(';')) {
368 if (!s.isEmpty())
369 operations << s;
370 }
371
372 if (!warning.isEmpty())
373 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
374
375 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
376 Q_ARG(QVariant, operations)));
377 waitForWorker(item);
378 QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
379
380 if (model.count() > 0)
381 QVERIFY(spyCount.count() > 0);
382
383 delete item;
384 qApp->processEvents();
385 }
386
387
388
dynamic_worker_sync_data()389 void tst_qdeclarativelistmodel::dynamic_worker_sync_data()
390 {
391 dynamic_data();
392 }
393
dynamic_worker_sync()394 void tst_qdeclarativelistmodel::dynamic_worker_sync()
395 {
396 QFETCH(QString, script);
397 QFETCH(int, result);
398 QFETCH(QString, warning);
399
400 // This is the same as dynamic_worker() except that it executes a set of list operations
401 // from the worker script, calls sync(), and tests the changes are reflected in the
402 // list in the main thread
403
404 QDeclarativeListModel model;
405 QDeclarativeEngine eng;
406 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
407 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
408 QVERIFY(item != 0);
409
410 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
411 script = script.mid(1, script.length() - 2);
412 QVariantList operations;
413 foreach (const QString &s, script.split(';')) {
414 if (!s.isEmpty())
415 operations << s;
416 }
417
418 if (!warning.isEmpty())
419 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
420
421 // execute a set of commands on the worker list model, then check the
422 // changes are reflected in the list model in the main thread
423 if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
424 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
425
426 if (QByteArray(QTest::currentDataTag()).startsWith("nested-set"))
427 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
428
429 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
430 Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
431 waitForWorker(item);
432
433 QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
434 if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
435 QCOMPARE(e.evaluate().toInt(), result);
436
437 delete item;
438 qApp->processEvents();
439 }
440
convertNestedToFlat_fail()441 void tst_qdeclarativelistmodel::convertNestedToFlat_fail()
442 {
443 // If a model has nested data, it cannot be used at all from a worker script
444
445 QFETCH(QString, script);
446
447 QDeclarativeListModel model;
448 QDeclarativeEngine eng;
449 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
450 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
451 QVERIFY(item != 0);
452
453 QScriptEngine s_eng;
454 QScriptValue plainData = s_eng.newObject();
455 plainData.setProperty("foo", QScriptValue(123));
456 model.append(plainData);
457 model.append(nestedListValue(&s_eng));
458 QCOMPARE(model.count(), 2);
459
460 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: List contains list-type data and cannot be used from a worker script");
461 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
462 waitForWorker(item);
463
464 QCOMPARE(model.count(), 2);
465
466 delete item;
467 qApp->processEvents();
468 }
469
convertNestedToFlat_fail_data()470 void tst_qdeclarativelistmodel::convertNestedToFlat_fail_data()
471 {
472 QTest::addColumn<QString>("script");
473
474 QTest::newRow("clear") << "clear()";
475 QTest::newRow("remove") << "remove(0)";
476 QTest::newRow("append") << "append({'x':1})";
477 QTest::newRow("insert") << "insert(0, {'x':1})";
478 QTest::newRow("set") << "set(0, {'foo':1})";
479 QTest::newRow("setProperty") << "setProperty(0, 'foo', 1})";
480 QTest::newRow("move") << "move(0, 1, 1})";
481 QTest::newRow("get") << "get(0)";
482 }
483
convertNestedToFlat_ok()484 void tst_qdeclarativelistmodel::convertNestedToFlat_ok()
485 {
486 // If a model only has plain data, it can be modified from a worker script. However,
487 // once the model is used from a worker script, it no longer accepts nested data
488
489 QFETCH(QString, script);
490
491 QDeclarativeListModel model;
492 QDeclarativeEngine eng;
493 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
494 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
495 QVERIFY(item != 0);
496
497 QScriptEngine s_eng;
498 QScriptValue plainData = s_eng.newObject();
499 plainData.setProperty("foo", QScriptValue(123));
500 model.append(plainData);
501 QCOMPARE(model.count(), 1);
502
503 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
504 waitForWorker(item);
505
506 // can still add plain data
507 int count = model.count();
508 model.append(plainData);
509 QCOMPARE(model.count(), count+1);
510
511 QScriptValue nested = nestedListValue(&s_eng);
512 const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
513
514 QTest::ignoreMessage(QtWarningMsg, warning);
515 model.append(nested);
516
517 QTest::ignoreMessage(QtWarningMsg, warning);
518 model.insert(0, nested);
519
520 QTest::ignoreMessage(QtWarningMsg, warning);
521 model.set(0, nested);
522
523 QCOMPARE(model.count(), count+1);
524
525 delete item;
526 qApp->processEvents();
527 }
528
convertNestedToFlat_ok_data()529 void tst_qdeclarativelistmodel::convertNestedToFlat_ok_data()
530 {
531 convertNestedToFlat_fail_data();
532 }
533
static_types_data()534 void tst_qdeclarativelistmodel::static_types_data()
535 {
536 QTest::addColumn<QString>("qml");
537 QTest::addColumn<QVariant>("value");
538
539 QTest::newRow("string")
540 << "ListElement { foo: \"bar\" }"
541 << QVariant(QString("bar"));
542
543 QTest::newRow("real")
544 << "ListElement { foo: 10.5 }"
545 << QVariant(10.5);
546
547 QTest::newRow("real0")
548 << "ListElement { foo: 0 }"
549 << QVariant(double(0));
550
551 QTest::newRow("bool")
552 << "ListElement { foo: false }"
553 << QVariant(false);
554
555 QTest::newRow("bool")
556 << "ListElement { foo: true }"
557 << QVariant(true);
558
559 QTest::newRow("enum")
560 << "ListElement { foo: Text.AlignHCenter }"
561 << QVariant(double(QDeclarativeText::AlignHCenter));
562
563 QTest::newRow("real11")
564 << "ListElement { foo: 11 }"
565 << QVariant(11.0);
566 }
567
static_types()568 void tst_qdeclarativelistmodel::static_types()
569 {
570 QFETCH(QString, qml);
571 QFETCH(QVariant, value);
572
573 qml = "import QtQuick 1.0\nListModel { " + qml + " }";
574
575 QDeclarativeEngine engine;
576 QDeclarativeComponent component(&engine);
577 component.setData(qml.toUtf8(),
578 QUrl::fromLocalFile(QString("dummy.qml")));
579
580 if (value.toString().startsWith("QTBUG-"))
581 QEXPECT_FAIL("",value.toString().toLatin1(),Abort);
582
583 QVERIFY(!component.isError());
584
585 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
586 QVERIFY(obj != 0);
587
588 QScriptValue actual = obj->get(0).property(QLatin1String("foo"));
589
590 QCOMPARE(actual.isString(), value.type() == QVariant::String);
591 QCOMPARE(actual.isBoolean(), value.type() == QVariant::Bool);
592 QCOMPARE(actual.isNumber(), value.type() == QVariant::Double);
593
594 QCOMPARE(actual.toString(), value.toString());
595
596 delete obj;
597 }
598
enumerate()599 void tst_qdeclarativelistmodel::enumerate()
600 {
601 QDeclarativeEngine eng;
602 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
603 QVERIFY(!component.isError());
604 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(component.create());
605 QVERIFY(item != 0);
606 QStringList r = item->property("result").toString().split(":");
607 QCOMPARE(r[0],QLatin1String("val1=1Y"));
608 QCOMPARE(r[1],QLatin1String("val2=2Y"));
609 QCOMPARE(r[2],QLatin1String("val3=strY"));
610 QCOMPARE(r[3],QLatin1String("val4=falseN"));
611 QCOMPARE(r[4],QLatin1String("val5=trueY"));
612 delete item;
613 }
614
615
error_data()616 void tst_qdeclarativelistmodel::error_data()
617 {
618 QTest::addColumn<QString>("qml");
619 QTest::addColumn<QString>("error");
620
621 QTest::newRow("id not allowed in ListElement")
622 << "import QtQuick 1.0\nListModel { ListElement { id: fred } }"
623 << "ListElement: cannot use reserved \"id\" property";
624
625 QTest::newRow("id allowed in ListModel")
626 << "import QtQuick 1.0\nListModel { id:model }"
627 << "";
628
629 QTest::newRow("random properties not allowed in ListModel")
630 << "import QtQuick 1.0\nListModel { foo:123 }"
631 << "ListModel: undefined property 'foo'";
632
633 QTest::newRow("random properties allowed in ListElement")
634 << "import QtQuick 1.0\nListModel { ListElement { foo:123 } }"
635 << "";
636
637 QTest::newRow("bindings not allowed in ListElement")
638 << "import QtQuick 1.0\nRectangle { id: rect; ListModel { ListElement { foo: rect.color } } }"
639 << "ListElement: cannot use script for property value";
640
641 QTest::newRow("random object list properties allowed in ListElement")
642 << "import QtQuick 1.0\nListModel { ListElement { foo: [ ListElement { bar: 123 } ] } }"
643 << "";
644
645 QTest::newRow("default properties not allowed in ListElement")
646 << "import QtQuick 1.0\nListModel { ListElement { Item { } } }"
647 << "ListElement: cannot contain nested elements";
648
649 QTest::newRow("QML elements not allowed in ListElement")
650 << "import QtQuick 1.0\nListModel { ListElement { a: Item { } } }"
651 << "ListElement: cannot contain nested elements";
652
653 QTest::newRow("qualified ListElement supported")
654 << "import QtQuick 1.0 as Foo\nFoo.ListModel { Foo.ListElement { a: 123 } }"
655 << "";
656
657 QTest::newRow("qualified ListElement required")
658 << "import QtQuick 1.0 as Foo\nFoo.ListModel { ListElement { a: 123 } }"
659 << "ListElement is not a type";
660
661 QTest::newRow("unknown qualified ListElement not allowed")
662 << "import QtQuick 1.0\nListModel { Foo.ListElement { a: 123 } }"
663 << "Foo.ListElement - Foo is not a namespace";
664 }
665
error()666 void tst_qdeclarativelistmodel::error()
667 {
668 QFETCH(QString, qml);
669 QFETCH(QString, error);
670
671 QDeclarativeEngine engine;
672 QDeclarativeComponent component(&engine);
673 component.setData(qml.toUtf8(),
674 QUrl::fromLocalFile(QString("dummy.qml")));
675 if (error.isEmpty()) {
676 QVERIFY(!component.isError());
677 } else {
678 QVERIFY(component.isError());
679 QList<QDeclarativeError> errors = component.errors();
680 QCOMPARE(errors.count(),1);
681 QCOMPARE(errors.at(0).description(),error);
682 }
683 }
684
syncError()685 void tst_qdeclarativelistmodel::syncError()
686 {
687 QString qml = "import QtQuick 1.0\nListModel { id: lm; Component.onCompleted: lm.sync() }";
688 QString error = "file:dummy.qml:2:1: QML ListModel: List sync() can only be called from a WorkerScript";
689
690 QDeclarativeEngine engine;
691 QDeclarativeComponent component(&engine);
692 component.setData(qml.toUtf8(),
693 QUrl::fromLocalFile(QString("dummy.qml")));
694 QTest::ignoreMessage(QtWarningMsg,error.toUtf8());
695 QObject *obj = component.create();
696 QVERIFY(obj);
697 delete obj;
698 }
699
700 /*
701 Test model changes from set() are available to the view
702 */
set()703 void tst_qdeclarativelistmodel::set()
704 {
705 QDeclarativeEngine engine;
706 QDeclarativeListModel model;
707 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
708 engine.rootContext()->setContextObject(&model);
709 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&engine);
710
711 QScriptValue sv = seng->newObject();
712 sv.setProperty("test", QScriptValue(false));
713 model.append(sv);
714
715 sv.setProperty("test", QScriptValue(true));
716 model.set(0, sv);
717 QCOMPARE(model.get(0).property("test").toBool(), true); // triggers creation of model cache
718 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
719
720 sv.setProperty("test", QScriptValue(false));
721 model.set(0, sv);
722 QCOMPARE(model.get(0).property("test").toBool(), false); // tests model cache is updated
723 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
724 }
725
726 /*
727 Test model changes on values returned by get() are available to the view
728 */
get()729 void tst_qdeclarativelistmodel::get()
730 {
731 QFETCH(QString, expression);
732 QFETCH(int, index);
733 QFETCH(QString, roleName);
734 QFETCH(QVariant, roleValue);
735
736 QDeclarativeEngine eng;
737 QDeclarativeComponent component(&eng);
738 component.setData(
739 "import QtQuick 1.0\n"
740 "ListModel { \n"
741 "ListElement { roleA: 100 }\n"
742 "ListElement { roleA: 200; roleB: 400 } \n"
743 "ListElement { roleA: 200; roleB: 400 } \n"
744 "}", QUrl());
745 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
746 int role = roleFromName(model, roleName);
747 QVERIFY(role >= 0);
748
749 QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
750 QDeclarativeExpression expr(eng.rootContext(), model, expression);
751 expr.evaluate();
752 QVERIFY(!expr.hasError());
753
754 QCOMPARE(model->data(index, role), roleValue);
755 QCOMPARE(spy.count(), 1);
756
757 QList<QVariant> spyResult = spy.takeFirst();
758 QCOMPARE(spyResult.at(0).toInt(), index);
759 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
760 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
761 }
762
get_data()763 void tst_qdeclarativelistmodel::get_data()
764 {
765 QTest::addColumn<QString>("expression");
766 QTest::addColumn<int>("index");
767 QTest::addColumn<QString>("roleName");
768 QTest::addColumn<QVariant>("roleValue");
769
770 QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
771 QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
772
773 QVariantMap map;
774 map["zzz"] = 123;
775 QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
776
777 QVariantList list;
778 map.clear(); map["a"] = 50; map["b"] = 500;
779 list << map;
780 map.clear(); map["c"] = 1000;
781 list << map;
782 QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
783 }
784
get_worker()785 void tst_qdeclarativelistmodel::get_worker()
786 {
787 QFETCH(QString, expression);
788 QFETCH(int, index);
789 QFETCH(QString, roleName);
790 QFETCH(QVariant, roleValue);
791
792 QDeclarativeListModel model;
793 QDeclarativeEngine eng;
794 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
795 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
796 QVERIFY(item != 0);
797 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&eng);
798
799 // Add some values like get() test
800 QScriptValue sv = seng->newObject();
801 sv.setProperty(QLatin1String("roleA"), seng->newVariant(QVariant::fromValue(100)));
802 model.append(sv);
803 sv = seng->newObject();
804 sv.setProperty(QLatin1String("roleA"), seng->newVariant(QVariant::fromValue(200)));
805 sv.setProperty(QLatin1String("roleB"), seng->newVariant(QVariant::fromValue(400)));
806 model.append(sv);
807 model.append(sv);
808 int role = roleFromName(&model, roleName);
809 QVERIFY(role >= 0);
810
811 const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
812 if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
813 QTest::ignoreMessage(QtWarningMsg, warning);
814 QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
815
816 // in the worker thread, change the model data and call sync()
817 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
818 Q_ARG(QVariant, QStringList(expression))));
819 waitForWorker(item);
820
821 // see if we receive the model changes in the main thread's model
822 if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
823 QVERIFY(model.data(index, role) != roleValue);
824 QCOMPARE(spy.count(), 0);
825 } else {
826 QCOMPARE(model.data(index, role), roleValue);
827 QCOMPARE(spy.count(), 1);
828
829 QList<QVariant> spyResult = spy.takeFirst();
830 QCOMPARE(spyResult.at(0).toInt(), index);
831 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
832 QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
833 }
834 }
835
get_worker_data()836 void tst_qdeclarativelistmodel::get_worker_data()
837 {
838 get_data();
839 }
840
841 /*
842 Test that the tests run in get() also work for nested list data
843 */
get_nested()844 void tst_qdeclarativelistmodel::get_nested()
845 {
846 QFETCH(QString, expression);
847 QFETCH(int, index);
848 QFETCH(QString, roleName);
849 QFETCH(QVariant, roleValue);
850
851 QDeclarativeEngine eng;
852 QDeclarativeComponent component(&eng);
853 component.setData(
854 "import QtQuick 1.0\n"
855 "ListModel { \n"
856 "ListElement {\n"
857 "listRoleA: [\n"
858 "ListElement { roleA: 100 },\n"
859 "ListElement { roleA: 200; roleB: 400 },\n"
860 "ListElement { roleA: 200; roleB: 400 } \n"
861 "]\n"
862 "}\n"
863 "ListElement {\n"
864 "listRoleA: [\n"
865 "ListElement { roleA: 100 },\n"
866 "ListElement { roleA: 200; roleB: 400 },\n"
867 "ListElement { roleA: 200; roleB: 400 } \n"
868 "]\n"
869 "listRoleB: [\n"
870 "ListElement { roleA: 100 },\n"
871 "ListElement { roleA: 200; roleB: 400 },\n"
872 "ListElement { roleA: 200; roleB: 400 } \n"
873 "]\n"
874 "listRoleC: [\n"
875 "ListElement { roleA: 100 },\n"
876 "ListElement { roleA: 200; roleB: 400 },\n"
877 "ListElement { roleA: 200; roleB: 400 } \n"
878 "]\n"
879 "}\n"
880 "}", QUrl());
881 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
882 QVERIFY(component.errorString().isEmpty());
883 QDeclarativeListModel *childModel;
884
885 // Test setting the inner list data for:
886 // get(0).listRoleA
887 // get(1).listRoleA
888 // get(1).listRoleB
889 // get(1).listRoleC
890
891 QList<QPair<int, QString> > testData;
892 testData << qMakePair(0, QString("listRoleA"));
893 testData << qMakePair(1, QString("listRoleA"));
894 testData << qMakePair(1, QString("listRoleB"));
895 testData << qMakePair(1, QString("listRoleC"));
896
897 for (int i=0; i<testData.count(); i++) {
898 int outerListIndex = testData[i].first;
899 QString outerListRoleName = testData[i].second;
900 int outerListRole = roleFromName(model, outerListRoleName);
901 QVERIFY(outerListRole >= 0);
902
903 childModel = qobject_cast<QDeclarativeListModel*>(model->data(outerListIndex, outerListRole).value<QObject*>());
904 QVERIFY(childModel);
905
906 QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
907 QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
908
909 QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
910 expr.evaluate();
911 QVERIFY(!expr.hasError());
912
913 int role = roleFromName(childModel, roleName);
914 QVERIFY(role >= 0);
915 QCOMPARE(childModel->data(index, role), roleValue);
916 QCOMPARE(spy.count(), 1);
917
918 QList<QVariant> spyResult = spy.takeFirst();
919 QCOMPARE(spyResult.at(0).toInt(), index);
920 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
921 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
922 }
923 }
924
get_nested_data()925 void tst_qdeclarativelistmodel::get_nested_data()
926 {
927 get_data();
928 }
929
930 //QTBUG-13754
crash_model_with_multiple_roles()931 void tst_qdeclarativelistmodel::crash_model_with_multiple_roles()
932 {
933 QDeclarativeEngine eng;
934 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/multipleroles.qml"));
935 QObject *rootItem = component.create();
936 QVERIFY(component.errorString().isEmpty());
937 QVERIFY(rootItem != 0);
938 QDeclarativeListModel *model = rootItem->findChild<QDeclarativeListModel*>("listModel");
939 QVERIFY(model != 0);
940
941 // used to cause a crash in QDeclarativeVisualDataModel
942 model->setProperty(0, "black", true);
943 }
944
945 //QTBUG-15190
set_model_cache()946 void tst_qdeclarativelistmodel::set_model_cache()
947 {
948 QDeclarativeEngine eng;
949 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/setmodelcachelist.qml"));
950 QObject *model = component.create();
951 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
952 QVERIFY(model != 0);
953 QVERIFY(model->property("ok").toBool());
954 }
955
property_changes()956 void tst_qdeclarativelistmodel::property_changes()
957 {
958 QFETCH(QString, script_setup);
959 QFETCH(QString, script_change);
960 QFETCH(QString, roleName);
961 QFETCH(int, listIndex);
962 QFETCH(bool, itemsChanged);
963 QFETCH(QString, testExpression);
964
965 QDeclarativeEngine engine;
966 QDeclarativeListModel model;
967 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
968 engine.rootContext()->setContextObject(&model);
969
970 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
971 expr.evaluate();
972 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
973
974 QString signalHandler = "on" + QString(roleName[0].toUpper()) + roleName.mid(1, roleName.length()) + "Changed:";
975 QString qml = "import QtQuick 1.0\n"
976 "Connections {\n"
977 "property bool gotSignal: false\n"
978 "target: model.get(0)\n"
979 + signalHandler + " gotSignal = true\n"
980 "}\n";
981 QDeclarativeComponent component(&engine);
982 component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
983 engine.rootContext()->setContextProperty("model", &model);
984 QObject *connectionsObject = component.create();
985 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
986
987 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
988
989 expr.setExpression(script_change);
990 expr.evaluate();
991 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
992
993 // test the object returned by get() emits the correct signals
994 QCOMPARE(connectionsObject->property("gotSignal").toBool(), itemsChanged);
995
996 // test itemsChanged() is emitted correctly
997 if (itemsChanged) {
998 QCOMPARE(spyItemsChanged.count(), 1);
999 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1000 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1001 } else {
1002 QCOMPARE(spyItemsChanged.count(), 0);
1003 }
1004
1005 expr.setExpression(testExpression);
1006 QCOMPARE(expr.evaluate().toBool(), true);
1007
1008 delete connectionsObject;
1009 }
1010
property_changes_data()1011 void tst_qdeclarativelistmodel::property_changes_data()
1012 {
1013 QTest::addColumn<QString>("script_setup");
1014 QTest::addColumn<QString>("script_change");
1015 QTest::addColumn<QString>("roleName");
1016 QTest::addColumn<int>("listIndex");
1017 QTest::addColumn<bool>("itemsChanged");
1018 QTest::addColumn<QString>("testExpression");
1019
1020 QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
1021 << "b" << 0 << true << "get(0).b == 123";
1022 QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
1023 << "b" << 0 << true << "get(0).b == 123";
1024
1025 QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
1026 << "b" << 0 << false << "get(0).b == 456";
1027 QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
1028 << "b" << 0 << false << "get(0).b == 456";
1029
1030 // Following tests only call set() since setProperty() only allows plain
1031 // values, not lists, as the argument.
1032 // Note that when a list is changed, itemsChanged() is currently always
1033 // emitted regardless of whether it actually changed or not.
1034
1035 QTest::newRow("nested-set: list, new size") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});"
1036 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2";
1037
1038 QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1039 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
1040
1041 QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
1042 << "b" << 0 << true << "get(0).b.count == 0";
1043
1044 QTest::newRow("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}]});"
1045 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3";
1046
1047 QTest::newRow("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}]});"
1048 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
1049
1050 QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
1051 << "b" << 0 << true << "get(0).b.count == 0";
1052 }
1053
1054
property_changes_worker()1055 void tst_qdeclarativelistmodel::property_changes_worker()
1056 {
1057 // nested models are not supported when WorkerScript is involved
1058 if (QByteArray(QTest::currentDataTag()).startsWith("nested-"))
1059 return;
1060
1061 QFETCH(QString, script_setup);
1062 QFETCH(QString, script_change);
1063 QFETCH(QString, roleName);
1064 QFETCH(int, listIndex);
1065 QFETCH(bool, itemsChanged);
1066
1067 QDeclarativeListModel model;
1068 QDeclarativeEngine engine;
1069 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
1070 QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
1071 QDeclarativeItem *item = createWorkerTest(&engine, &component, &model);
1072 QVERIFY(item != 0);
1073
1074 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1075 expr.evaluate();
1076 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1077
1078 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1079
1080 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1081 Q_ARG(QVariant, QStringList(script_change))));
1082 waitForWorker(item);
1083
1084 // test itemsChanged() is emitted correctly
1085 if (itemsChanged) {
1086 QCOMPARE(spyItemsChanged.count(), 1);
1087 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1088 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1089 } else {
1090 QCOMPARE(spyItemsChanged.count(), 0);
1091 }
1092
1093 delete item;
1094 qApp->processEvents();
1095 }
1096
property_changes_worker_data()1097 void tst_qdeclarativelistmodel::property_changes_worker_data()
1098 {
1099 property_changes_data();
1100 }
1101
clear()1102 void tst_qdeclarativelistmodel::clear()
1103 {
1104 QDeclarativeEngine engine;
1105 QDeclarativeListModel model;
1106 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1107 engine.rootContext()->setContextObject(&model);
1108
1109 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&engine);
1110 QScriptValue sv = seng->newObject();
1111 QVariant result;
1112
1113 model.clear();
1114 QCOMPARE(model.count(), 0);
1115
1116 sv.setProperty("propertyA", "value a");
1117 sv.setProperty("propertyB", "value b");
1118 model.append(sv);
1119 QCOMPARE(model.count(), 1);
1120
1121 model.clear();
1122 QCOMPARE(model.count(), 0);
1123
1124 model.append(sv);
1125 model.append(sv);
1126 QCOMPARE(model.count(), 2);
1127
1128 model.clear();
1129 QCOMPARE(model.count(), 0);
1130
1131 // clearing does not remove the roles
1132 sv.setProperty("propertyC", "value c");
1133 model.append(sv);
1134 QList<int> roles = model.roles();
1135 model.clear();
1136 QCOMPARE(model.count(), 0);
1137 QCOMPARE(model.roles(), roles);
1138 QCOMPARE(model.toString(roles[0]), QString("propertyA"));
1139 QCOMPARE(model.toString(roles[1]), QString("propertyB"));
1140 QCOMPARE(model.toString(roles[2]), QString("propertyC"));
1141 }
1142
1143 QTEST_MAIN(tst_qdeclarativelistmodel)
1144
1145 #include "tst_qdeclarativelistmodel.moc"
1146