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