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