cfae97aa4d965498c8d991178f3e80458c997e57
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qdeclarativelistmodel / tst_qdeclarativelistmodel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <qtest.h>
42 #include <QtQuick/private/qquickitem_p.h>
43 #include <QtQuick/private/qquicktext_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 Q_DECLARE_METATYPE(QList<int>)
57 Q_DECLARE_METATYPE(QList<QVariantHash>)
58
59 #define RUNEVAL(object, string) \
60     QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
61
62 inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
63 {
64     QDeclarativeExpression expr(engine->rootContext(), 0, str);
65     return expr.evaluate();
66 }
67
68 #define RUNEXPR(string) runexpr(&engine, QString(string))
69
70 static bool isValidErrorMessage(const QString &msg, bool dynamicRoleTest)
71 {
72     bool valid = true;
73
74     if (msg.isEmpty()) {
75         valid = false;
76     } else if (dynamicRoleTest) {
77         if (msg.contains("Can't assign to existing role") || msg.contains("Can't create role for unsupported data type"))
78             valid = false;
79     }
80
81     return valid;
82 }
83
84 class tst_qdeclarativelistmodel : public QDeclarativeDataTest
85 {
86     Q_OBJECT
87 public:
88     tst_qdeclarativelistmodel() {}
89
90 private:
91     int roleFromName(const QDeclarativeListModel *model, const QString &roleName);
92     QQuickItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
93     void waitForWorker(QQuickItem *item);
94
95     static bool compareVariantList(const QVariantList &testList, QVariant object);
96
97 private slots:
98     void static_types();
99     void static_types_data();
100     void static_i18n();
101     void static_i18n_data();
102     void static_nestedElements();
103     void static_nestedElements_data();
104     void dynamic_data();
105     void dynamic();
106     void dynamic_worker_data();
107     void dynamic_worker();
108     void dynamic_worker_sync_data();
109     void dynamic_worker_sync();
110     void enumerate();
111     void error_data();
112     void error();
113     void syncError();
114     void get();
115     void set_data();
116     void set();
117     void get_data();
118     void get_worker();
119     void get_worker_data();
120     void get_nested();
121     void get_nested_data();
122     void crash_model_with_multiple_roles();
123     void set_model_cache();
124     void property_changes();
125     void property_changes_data();
126     void property_changes_worker();
127     void property_changes_worker_data();
128     void clear_data();
129     void clear();
130     void signal_handlers_data();
131     void signal_handlers();
132     void worker_sync_data();
133     void worker_sync();
134     void worker_remove_element_data();
135     void worker_remove_element();
136     void worker_remove_list_data();
137     void worker_remove_list();
138     void role_mode_data();
139     void role_mode();
140     void dynamic_role();
141     void dynamic_role_data();
142 };
143
144 bool tst_qdeclarativelistmodel::compareVariantList(const QVariantList &testList, QVariant object)
145 {
146     bool allOk = true;
147
148     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel *>(object.value<QObject *>());
149     if (model == 0)
150         return false;
151
152     if (model->count() != testList.count())
153         return false;
154
155     for (int i=0 ; i < testList.count() ; ++i) {
156         const QVariant &testVariant = testList.at(i);
157         if (testVariant.type() != QVariant::Map)
158             return false;
159         const QVariantMap &map = testVariant.toMap();
160
161         const QList<int> &roles = model->roles();
162
163         QVariantMap::const_iterator it = map.begin();
164         QVariantMap::const_iterator end = map.end();
165
166         while (it != end) {
167             const QString &testKey = it.key();
168             const QVariant &testData = it.value();
169
170             int roleIndex = -1;
171             for (int j=0 ; j < roles.count() ; ++j) {
172                 if (model->toString(roles[j]).compare(testKey) == 0) {
173                     roleIndex = j;
174                     break;
175                 }
176             }
177
178             if (roleIndex == -1)
179                 return false;
180
181             const QVariant &modelData = model->data(i, roleIndex);
182
183             if (testData.type() == QVariant::List) {
184                 const QVariantList &subList = testData.toList();
185                 allOk = allOk && compareVariantList(subList, modelData);
186             } else {
187                 allOk = allOk && (testData == modelData);
188             }
189
190             ++it;
191         }
192     }
193
194     return allOk;
195 }
196
197 int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
198 {
199     QList<int> roles = model->roles();
200     for (int i=0; i<roles.count(); i++) {
201         if (model->toString(roles[i]) == roleName)
202             return roles[i];
203     }
204     return -1;
205 }
206
207 QQuickItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model)
208 {
209     QQuickItem *item = qobject_cast<QQuickItem*>(component->create());
210     QDeclarativeEngine::setContextForObject(model, eng->rootContext());
211     if (item)
212         item->setProperty("model", qVariantFromValue(model));
213     return item;
214 }
215
216 void tst_qdeclarativelistmodel::waitForWorker(QQuickItem *item)
217 {
218     QEventLoop loop;
219     QTimer timer;
220     timer.setSingleShot(true);
221     connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
222
223     QDeclarativeProperty prop(item, "done");
224     QVERIFY(prop.isValid());
225     QVERIFY(prop.connectNotifySignal(&loop, SLOT(quit())));
226     timer.start(10000);
227     loop.exec();
228     QVERIFY(timer.isActive());
229 }
230
231 void tst_qdeclarativelistmodel::static_types_data()
232 {
233     QTest::addColumn<QString>("qml");
234     QTest::addColumn<QVariant>("value");
235     QTest::addColumn<QString>("error");
236
237     QTest::newRow("string")
238         << "ListElement { foo: \"bar\" }"
239         << QVariant(QString("bar"))
240         << QString();
241
242     QTest::newRow("real")
243         << "ListElement { foo: 10.5 }"
244         << QVariant(10.5)
245         << QString();
246
247     QTest::newRow("real0")
248         << "ListElement { foo: 0 }"
249         << QVariant(double(0))
250         << QString();
251
252     QTest::newRow("bool")
253         << "ListElement { foo: false }"
254         << QVariant(false)
255         << QString();
256
257     QTest::newRow("bool")
258         << "ListElement { foo: true }"
259         << QVariant(true)
260         << QString();
261
262     QTest::newRow("enum")
263         << "ListElement { foo: Text.AlignHCenter }"
264         << QVariant(double(QQuickText::AlignHCenter))
265         << QString();
266
267     QTest::newRow("Qt enum")
268         << "ListElement { foo: Qt.AlignBottom }"
269         << QVariant(double(Qt::AlignBottom))
270         << QString();
271
272     QTest::newRow("role error")
273         << "ListElement { foo: 1 } ListElement { foo: 'string' }"
274         << QVariant()
275         << QString("<Unknown File>: Can't assign to existing role 'foo' of different type [String -> Number]");
276
277     QTest::newRow("list type error")
278         << "ListElement { foo: 1 } ListElement { foo: ListElement { bar: 1 } }"
279         << QVariant()
280         << QString("<Unknown File>: Can't assign to existing role 'foo' of different type [List -> Number]");
281 }
282
283 void tst_qdeclarativelistmodel::static_types()
284 {
285     QFETCH(QString, qml);
286     QFETCH(QVariant, value);
287     QFETCH(QString, error);
288
289     qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
290
291     if (!error.isEmpty()) {
292         QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
293     }
294
295     QDeclarativeEngine engine;
296     QDeclarativeComponent component(&engine);
297     component.setData(qml.toUtf8(),
298                       QUrl::fromLocalFile(QString("dummy.qml")));
299
300     QVERIFY(!component.isError());
301
302     QObject *obj = component.create();
303     QVERIFY(obj != 0);
304
305     if (error.isEmpty()) {
306         QVariant actual = obj->property("test");
307
308         QCOMPARE(actual, value);
309         QCOMPARE(actual.toString(), value.toString());
310     }
311
312     delete obj;
313 }
314
315 void tst_qdeclarativelistmodel::static_i18n_data()
316 {
317     QTest::addColumn<QString>("qml");
318     QTest::addColumn<QVariant>("value");
319     QTest::addColumn<QString>("error");
320
321     QTest::newRow("QT_TR_NOOP")
322         << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"na\303\257ve\") }")
323         << QVariant(QString::fromUtf8("na\303\257ve"))
324         << QString();
325
326     QTest::newRow("QT_TRANSLATE_NOOP")
327         << "ListElement { foo: QT_TRANSLATE_NOOP(\"MyListModel\", \"hello\") }"
328         << QVariant(QString("hello"))
329         << QString();
330
331     QTest::newRow("QT_TRID_NOOP")
332         << QString::fromUtf8("ListElement { foo: QT_TRID_NOOP(\"qtn_1st_text\") }")
333         << QVariant(QString("qtn_1st_text"))
334         << QString();
335
336     QTest::newRow("QT_TR_NOOP extra param")
337             << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"hello\",\"world\") }")
338             << QVariant(QString())
339             << QString("ListElement: improperly specified QT_TR_NOOP");
340
341     QTest::newRow("QT_TRANSLATE_NOOP missing params")
342         << "ListElement { foo: QT_TRANSLATE_NOOP() }"
343         << QVariant(QString())
344         << QString("ListElement: improperly specified QT_TRANSLATE_NOOP");
345
346     QTest::newRow("QT_TRID_NOOP missing param")
347         << QString::fromUtf8("ListElement { foo: QT_TRID_NOOP() }")
348         << QVariant(QString())
349         << QString("ListElement: improperly specified QT_TRID_NOOP");
350 }
351
352 void tst_qdeclarativelistmodel::static_i18n()
353 {
354     QFETCH(QString, qml);
355     QFETCH(QVariant, value);
356     QFETCH(QString, error);
357
358     qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
359
360     QDeclarativeEngine engine;
361     QDeclarativeComponent component(&engine);
362     component.setData(qml.toUtf8(),
363                       QUrl::fromLocalFile(QString("dummy.qml")));
364
365     if (!error.isEmpty()) {
366         QVERIFY(component.isError());
367         QCOMPARE(component.errors().at(0).description(), error);
368         return;
369     }
370
371     QVERIFY(!component.isError());
372
373     QObject *obj = component.create();
374     QVERIFY(obj != 0);
375
376     QVariant actual = obj->property("test");
377
378     QCOMPARE(actual, value);
379     QCOMPARE(actual.toString(), value.toString());
380
381     delete obj;
382 }
383
384 void tst_qdeclarativelistmodel::static_nestedElements()
385 {
386     QFETCH(int, elementCount);
387
388     QStringList elements;
389     for (int i=0; i<elementCount; i++)
390         elements.append("ListElement { a: 1; b: 2 }");
391     QString elementsStr = elements.join(",\n") + "\n";
392
393     QString componentStr =
394         "import QtQuick 2.0\n"
395         "Item {\n"
396         "    property variant count: model.get(0).attributes.count\n"
397         "    ListModel {\n"
398         "        id: model\n"
399         "        ListElement {\n"
400         "            attributes: [\n";
401     componentStr += elementsStr.toUtf8().constData();
402     componentStr +=
403         "            ]\n"
404         "        }\n"
405         "    }\n"
406         "}";
407
408     QDeclarativeEngine engine;
409     QDeclarativeComponent component(&engine);
410     component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
411
412     QObject *obj = component.create();
413     QVERIFY(obj != 0);
414
415     QVariant count = obj->property("count");
416     QCOMPARE(count.type(), QVariant::Int);
417     QCOMPARE(count.toInt(), elementCount);
418
419     delete obj;
420 }
421
422 void tst_qdeclarativelistmodel::static_nestedElements_data()
423 {
424     QTest::addColumn<int>("elementCount");
425
426     QTest::newRow("0 items") << 0;
427     QTest::newRow("1 item") << 1;
428     QTest::newRow("2 items") << 2;
429     QTest::newRow("many items") << 5;
430 }
431
432 void tst_qdeclarativelistmodel::dynamic_data()
433 {
434     QTest::addColumn<QString>("script");
435     QTest::addColumn<int>("result");
436     QTest::addColumn<QString>("warning");
437     QTest::addColumn<bool>("dynamicRoles");
438
439     for (int i=0 ; i < 2 ; ++i) {
440         bool dr = (i != 0);
441
442         // Simple flat model
443         QTest::newRow("count") << "count" << 0 << "" << dr;
444
445         QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "" << dr;
446         QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "" << dr;
447         QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "" << dr;
448         QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "" << dr;
449         QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "" << dr;
450         QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "" << dr;
451
452         QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "" << dr;
453         QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "" << dr;
454         QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "" << dr;
455         QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "" << dr;
456         QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
457         QTest::newRow("append4b") << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "" << dr;
458         QTest::newRow("append4c") << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "" << dr;
459
460         QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "" << dr;
461         QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "" << dr;
462         QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "" << dr;
463
464         QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "" << dr;
465         QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "" << dr;
466         QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "" << dr;
467         QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "" << dr;
468         QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "" << dr;
469         QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]" << dr;
470         QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr;
471         QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr;
472         QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [1 - 2] out of range [0 - 1]" << dr;
473         QTest::newRow("remove5a") << "{append({'foo':123});append({'foo':456});remove(0,2);count}" << 0 << "" << dr;
474         QTest::newRow("remove5b") << "{append({'foo':123});append({'foo':456});remove(0,1);count}" << 1 << "" << dr;
475         QTest::newRow("remove5c") << "{append({'foo':123});append({'foo':456});remove(1,1);count}" << 1 << "" << dr;
476         QTest::newRow("remove5d") << "{append({'foo':123});append({'foo':456});remove(0,1);get(0).foo}" << 456 << "" << dr;
477         QTest::newRow("remove5e") << "{append({'foo':123});append({'foo':456});remove(1,1);get(0).foo}" << 123 << "" << dr;
478         QTest::newRow("remove5f") << "{append({'foo':123});append({'foo':456});append({'foo':789});remove(0,1);remove(1,1);get(0).foo}" << 456 << "" << dr;
479         QTest::newRow("remove6a") << "{remove();count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr;
480         QTest::newRow("remove6b") << "{remove(1,2,3);count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr;
481         QTest::newRow("remove7a") << "{append({'foo':123});remove(0,0);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - 0] out of range [0 - 1]" << dr;
482         QTest::newRow("remove7b") << "{append({'foo':123});remove(0,-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - -1] out of range [0 - 1]" << dr;
483
484         QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "" << dr;
485         QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range" << dr;
486         QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "" << dr;
487         QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "" << dr;
488         QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "" << dr;
489         QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "" << dr;
490         QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "" << dr;
491         QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range" << dr;
492         QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
493         QTest::newRow("insert5b") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "" << dr;
494         QTest::newRow("insert5c") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "" << dr;
495
496         QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "" << dr;
497         QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "" << dr;
498         QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "" << dr;
499         QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "" << dr;
500         QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "" << dr;
501         QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr;
502         QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object" << dr;
503         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" << dr;
504         QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "" << dr;
505
506         QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "" << dr;
507         QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "" << dr;
508         QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "" << dr;
509         QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "" << dr;
510         QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range" << dr;
511         QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr;
512         QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range" << dr;
513         QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "" << dr;
514
515         QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "" << dr;
516         QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "" << dr;
517         QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "" << dr;
518         QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "" << dr;
519         QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "" << dr;
520         QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "" << dr;
521         QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "" << dr;
522         QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "" << dr;
523         QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "" << dr;
524         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" << dr;
525         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" << dr;
526         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" << dr;
527         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" << dr;
528
529         QTest::newRow("large1") << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "" << dr;
530
531         QTest::newRow("datatypes1") << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [String -> Number]" << dr;
532
533         QTest::newRow("null") << "{append({'a':null});}" << 0 << "" << dr;
534         QTest::newRow("setNull") << "{append({'a':1});set(0, {'a':null});}" << 0 << "" << dr;
535         QTest::newRow("setString") << "{append({'a':'hello'});set(0, {'a':'world'});get(0).a == 'world'}" << 1 << "" << dr;
536         QTest::newRow("setInt") << "{append({'a':5});set(0, {'a':10});get(0).a}" << 10 << "" << dr;
537         QTest::newRow("setNumber") << "{append({'a':6});set(0, {'a':5.5});get(0).a < 5.6}" << 1 << "" << dr;
538         QTest::newRow("badType0") << "{append({'a':'hello'});set(0, {'a':1});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [Number -> String]" << dr;
539         QTest::newRow("invalidInsert0") << "{insert(0);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
540         QTest::newRow("invalidAppend0") << "{append();}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
541         QTest::newRow("invalidInsert1") << "{insert(0, 34);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
542         QTest::newRow("invalidAppend1") << "{append(37);}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
543
544         // QObjects
545         QTest::newRow("qobject0") << "{append({'a':dummyItem0});}" << 0 << "" << dr;
546         QTest::newRow("qobject1") << "{append({'a':dummyItem0});set(0,{'a':dummyItem1});get(0).a == dummyItem1;}" << 1 << "" << dr;
547         QTest::newRow("qobject2") << "{append({'a':dummyItem0});get(0).a == dummyItem0;}" << 1 << "" << dr;
548         QTest::newRow("qobject3") << "{append({'a':dummyItem0});append({'b':1});}" << 0 << "" << dr;
549
550         // JS objects
551         QTest::newRow("js1") << "{append({'foo':{'prop':1}});count}" << 1 << "" << dr;
552         QTest::newRow("js2") << "{append({'foo':{'prop':27}});get(0).foo.prop}" << 27 << "" << dr;
553         QTest::newRow("js3") << "{append({'foo':{'prop':27}});append({'bar':1});count}" << 2 << "" << dr;
554         QTest::newRow("js4") << "{append({'foo':{'prop':27}});append({'bar':1});set(0, {'foo':{'prop':28}});get(0).foo.prop}" << 28 << "" << dr;
555         QTest::newRow("js5") << "{append({'foo':{'prop':27}});append({'bar':1});set(1, {'foo':{'prop':33}});get(1).foo.prop}" << 33 << "" << dr;
556         QTest::newRow("js6") << "{append({'foo':{'prop':27}});clear();count}" << 0 << "" << dr;
557         QTest::newRow("js7") << "{append({'foo':{'prop':27}});set(0, {'foo':null});count}" << 1 << "" << dr;
558         QTest::newRow("js8") << "{append({'foo':{'prop':27}});set(0, {'foo':{'prop2':31}});get(0).foo.prop2}" << 31 << "" << dr;
559
560         // Nested models
561         QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "" << dr;
562         QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "" << dr;
563         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 << "" << dr;
564
565         QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "" << dr;
566         QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "" << dr;
567
568         QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "" << dr;
569         QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "" << dr;
570     }
571 }
572
573 void tst_qdeclarativelistmodel::dynamic()
574 {
575     QFETCH(QString, script);
576     QFETCH(int, result);
577     QFETCH(QString, warning);
578     QFETCH(bool, dynamicRoles);
579
580     QQuickItem dummyItem0, dummyItem1;
581     QDeclarativeEngine engine;
582     QDeclarativeListModel model;
583     model.setDynamicRoles(dynamicRoles);
584     QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
585     engine.rootContext()->setContextObject(&model);
586     engine.rootContext()->setContextProperty("dummyItem0", QVariant::fromValue(&dummyItem0));
587     engine.rootContext()->setContextProperty("dummyItem1", QVariant::fromValue(&dummyItem1));
588     QDeclarativeExpression e(engine.rootContext(), &model, script);
589     if (isValidErrorMessage(warning, dynamicRoles))
590         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
591
592     QSignalSpy spyCount(&model, SIGNAL(countChanged()));
593
594     int actual = e.evaluate().toInt();
595     if (e.hasError())
596         qDebug() << e.error(); // errors not expected
597
598     QCOMPARE(actual,result);
599
600     if (model.count() > 0)
601         QVERIFY(spyCount.count() > 0);
602 }
603
604 void tst_qdeclarativelistmodel::dynamic_worker_data()
605 {
606     dynamic_data();
607 }
608
609 void tst_qdeclarativelistmodel::dynamic_worker()
610 {
611     QFETCH(QString, script);
612     QFETCH(int, result);
613     QFETCH(QString, warning);
614     QFETCH(bool, dynamicRoles);
615
616     if (QByteArray(QTest::currentDataTag()).startsWith("qobject"))
617         return;
618
619     // This is same as dynamic() except it applies the test to a ListModel called
620     // from a WorkerScript.
621
622     QDeclarativeListModel model;
623     model.setDynamicRoles(dynamicRoles);
624     QDeclarativeEngine eng;
625     QDeclarativeComponent component(&eng, testFileUrl("model.qml"));
626     QQuickItem *item = createWorkerTest(&eng, &component, &model);
627     QVERIFY(item != 0);
628
629     QSignalSpy spyCount(&model, SIGNAL(countChanged()));
630
631     if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
632         script = script.mid(1, script.length() - 2);
633     QVariantList operations;
634     foreach (const QString &s, script.split(';')) {
635         if (!s.isEmpty())
636             operations << s;
637     }
638
639     if (isValidErrorMessage(warning, dynamicRoles))
640         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
641
642     QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
643             Q_ARG(QVariant, operations)));
644     waitForWorker(item);
645     QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
646
647     if (model.count() > 0)
648         QVERIFY(spyCount.count() > 0);
649
650     delete item;
651     qApp->processEvents();
652 }
653
654 void tst_qdeclarativelistmodel::dynamic_worker_sync_data()
655 {
656     dynamic_data();
657 }
658
659 void tst_qdeclarativelistmodel::dynamic_worker_sync()
660 {
661     QFETCH(QString, script);
662     QFETCH(int, result);
663     QFETCH(QString, warning);
664     QFETCH(bool, dynamicRoles);
665
666     if (QByteArray(QTest::currentDataTag()).startsWith("qobject"))
667         return;
668
669     // This is the same as dynamic_worker() except that it executes a set of list operations
670     // from the worker script, calls sync(), and tests the changes are reflected in the
671     // list in the main thread
672
673     QDeclarativeListModel model;
674     model.setDynamicRoles(dynamicRoles);
675     QDeclarativeEngine eng;
676     QDeclarativeComponent component(&eng, testFileUrl("model.qml"));
677     QQuickItem *item = createWorkerTest(&eng, &component, &model);
678     QVERIFY(item != 0);
679
680     if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
681         script = script.mid(1, script.length() - 2);
682     QVariantList operations;
683     foreach (const QString &s, script.split(';')) {
684         if (!s.isEmpty())
685             operations << s;
686     }
687
688     if (isValidErrorMessage(warning, dynamicRoles))
689         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
690
691     // execute a set of commands on the worker list model, then check the
692     // changes are reflected in the list model in the main thread
693     QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
694             Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
695     waitForWorker(item);
696
697     QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
698     QCOMPARE(e.evaluate().toInt(), result);
699
700     delete item;
701     qApp->processEvents();
702 }
703
704 void tst_qdeclarativelistmodel::enumerate()
705 {
706     QDeclarativeEngine eng;
707     QDeclarativeComponent component(&eng, testFileUrl("enumerate.qml"));
708     QVERIFY(!component.isError());
709     QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
710     QVERIFY(item != 0);
711
712     QLatin1String expectedStrings[] = {
713         QLatin1String("val1=1Y"),
714         QLatin1String("val2=2Y"),
715         QLatin1String("val3=strY"),
716         QLatin1String("val4=falseN"),
717         QLatin1String("val5=trueY")
718     };
719
720     int expectedStringCount = sizeof(expectedStrings) / sizeof(expectedStrings[0]);
721
722     QStringList r = item->property("result").toString().split(":");
723
724     int matchCount = 0;
725     for (int i=0 ; i < expectedStringCount ; ++i) {
726         const QLatin1String &expectedString = expectedStrings[i];
727
728         QStringList::const_iterator it = r.begin();
729         QStringList::const_iterator end = r.end();
730
731         while (it != end) {
732             if (it->compare(expectedString) == 0) {
733                 ++matchCount;
734                 break;
735             }
736             ++it;
737         }
738     }
739
740     QVERIFY(matchCount == expectedStringCount);
741
742     delete item;
743 }
744
745 void tst_qdeclarativelistmodel::error_data()
746 {
747     QTest::addColumn<QString>("qml");
748     QTest::addColumn<QString>("error");
749
750     QTest::newRow("id not allowed in ListElement")
751         << "import QtQuick 2.0\nListModel { ListElement { id: fred } }"
752         << "ListElement: cannot use reserved \"id\" property";
753
754     QTest::newRow("id allowed in ListModel")
755         << "import QtQuick 2.0\nListModel { id:model }"
756         << "";
757
758     QTest::newRow("random properties not allowed in ListModel")
759         << "import QtQuick 2.0\nListModel { foo:123 }"
760         << "ListModel: undefined property 'foo'";
761
762     QTest::newRow("random properties allowed in ListElement")
763         << "import QtQuick 2.0\nListModel { ListElement { foo:123 } }"
764         << "";
765
766     QTest::newRow("bindings not allowed in ListElement")
767         << "import QtQuick 2.0\nRectangle { id: rect; ListModel { ListElement { foo: rect.color } } }"
768         << "ListElement: cannot use script for property value";
769
770     QTest::newRow("random object list properties allowed in ListElement")
771         << "import QtQuick 2.0\nListModel { ListElement { foo: [ ListElement { bar: 123 } ] } }"
772         << "";
773
774     QTest::newRow("default properties not allowed in ListElement")
775         << "import QtQuick 2.0\nListModel { ListElement { Item { } } }"
776         << "ListElement: cannot contain nested elements";
777
778     QTest::newRow("QML elements not allowed in ListElement")
779         << "import QtQuick 2.0\nListModel { ListElement { a: Item { } } }"
780         << "ListElement: cannot contain nested elements";
781
782     QTest::newRow("qualified ListElement supported")
783         << "import QtQuick 2.0 as Foo\nFoo.ListModel { Foo.ListElement { a: 123 } }"
784         << "";
785
786     QTest::newRow("qualified ListElement required")
787         << "import QtQuick 2.0 as Foo\nFoo.ListModel { ListElement { a: 123 } }"
788         << "ListElement is not a type";
789
790     QTest::newRow("unknown qualified ListElement not allowed")
791         << "import QtQuick 2.0\nListModel { Foo.ListElement { a: 123 } }"
792         << "Foo.ListElement - Foo is not a namespace";
793 }
794
795 void tst_qdeclarativelistmodel::error()
796 {
797     QFETCH(QString, qml);
798     QFETCH(QString, error);
799
800     QDeclarativeEngine engine;
801     QDeclarativeComponent component(&engine);
802     component.setData(qml.toUtf8(),
803                       QUrl::fromLocalFile(QString("dummy.qml")));
804     if (error.isEmpty()) {
805         QVERIFY(!component.isError());
806     } else {
807         QVERIFY(component.isError());
808         QList<QDeclarativeError> errors = component.errors();
809         QCOMPARE(errors.count(),1);
810         QCOMPARE(errors.at(0).description(),error);
811     }
812 }
813
814 void tst_qdeclarativelistmodel::syncError()
815 {
816     QString qml = "import QtQuick 2.0\nListModel { id: lm; Component.onCompleted: lm.sync() }";
817     QString error = "file:dummy.qml:2:1: QML ListModel: List sync() can only be called from a WorkerScript";
818
819     QDeclarativeEngine engine;
820     QDeclarativeComponent component(&engine);
821     component.setData(qml.toUtf8(),
822                       QUrl::fromLocalFile(QString("dummy.qml")));
823     QTest::ignoreMessage(QtWarningMsg,error.toUtf8());
824     QObject *obj = component.create();
825     QVERIFY(obj);
826     delete obj;
827 }
828
829 /*
830     Test model changes from set() are available to the view
831 */
832 void tst_qdeclarativelistmodel::set_data()
833 {
834     QTest::addColumn<bool>("dynamicRoles");
835
836     QTest::newRow("staticRoles") << false;
837     QTest::newRow("dynamicRoles") << true;
838 }
839
840 void tst_qdeclarativelistmodel::set()
841 {
842     QFETCH(bool, dynamicRoles);
843
844     QDeclarativeEngine engine;
845     QDeclarativeListModel model;
846     model.setDynamicRoles(dynamicRoles);
847     QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
848     engine.rootContext()->setContextProperty("model", &model);
849
850     RUNEXPR("model.append({test:false})");
851     RUNEXPR("model.set(0, {test:true})");
852
853     QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache
854     QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
855
856     RUNEXPR("model.set(0, {test:false})");
857     QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
858     QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
859
860     QString warning = QString::fromLatin1("<Unknown File>: Can't create role for unsupported data type");
861     if (isValidErrorMessage(warning, dynamicRoles))
862         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
863     QVariant invalidData = QColor();
864     model.setProperty(0, "test", invalidData);
865 }
866
867 /*
868     Test model changes on values returned by get() are available to the view
869 */
870 void tst_qdeclarativelistmodel::get()
871 {
872     QFETCH(QString, expression);
873     QFETCH(int, index);
874     QFETCH(QString, roleName);
875     QFETCH(QVariant, roleValue);
876     QFETCH(bool, dynamicRoles);
877
878     QDeclarativeEngine engine;
879     QDeclarativeComponent component(&engine);
880     component.setData(
881         "import QtQuick 2.0\n"
882         "ListModel {}\n", QUrl());
883     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
884     model->setDynamicRoles(dynamicRoles);
885     engine.rootContext()->setContextProperty("model", model);
886
887     RUNEXPR("model.append({roleA: 100})");
888     RUNEXPR("model.append({roleA: 200, roleB: 400})");
889     RUNEXPR("model.append({roleA: 200, roleB: 400})");
890     RUNEXPR("model.append({roleC: {} })");
891     RUNEXPR("model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
892
893     QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
894     QDeclarativeExpression expr(engine.rootContext(), model, expression);
895     expr.evaluate();
896     QVERIFY(!expr.hasError());
897
898     int role = roleFromName(model, roleName);
899     QVERIFY(role >= 0);
900
901     if (roleValue.type() == QVariant::List) {
902         const QVariantList &list = roleValue.toList();
903         QVERIFY(compareVariantList(list, model->data(index, role)));
904     } else {
905         QCOMPARE(model->data(index, role), roleValue);
906     }
907
908     QCOMPARE(spy.count(), 1);
909
910     QList<QVariant> spyResult = spy.takeFirst();
911     QCOMPARE(spyResult.at(0).toInt(), index);
912     QCOMPARE(spyResult.at(1).toInt(), 1);  // only 1 item is modified at a time
913     QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
914
915     delete model;
916 }
917
918 void tst_qdeclarativelistmodel::get_data()
919 {
920     QTest::addColumn<QString>("expression");
921     QTest::addColumn<int>("index");
922     QTest::addColumn<QString>("roleName");
923     QTest::addColumn<QVariant>("roleValue");
924     QTest::addColumn<bool>("dynamicRoles");
925
926     for (int i=0 ; i < 2 ; ++i) {
927         bool dr = (i != 0);
928
929         QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500) << dr;
930         QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500) << dr;
931
932         QVariantMap map;
933         QVariantList list;
934         map.clear(); map["a"] = 50; map["b"] = 500;
935         list << map;
936         map.clear(); map["c"] = 1000;
937         list << map;
938         QTest::newRow("list of objects") << "get(2).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list) << dr;
939     }
940 }
941
942 void tst_qdeclarativelistmodel::get_worker()
943 {
944     QFETCH(QString, expression);
945     QFETCH(int, index);
946     QFETCH(QString, roleName);
947     QFETCH(QVariant, roleValue);
948     QFETCH(bool, dynamicRoles);
949
950     QDeclarativeListModel model;
951     model.setDynamicRoles(dynamicRoles);
952     QDeclarativeEngine eng;
953     QDeclarativeComponent component(&eng, testFileUrl("model.qml"));
954     QQuickItem *item = createWorkerTest(&eng, &component, &model);
955     QVERIFY(item != 0);
956
957     // Add some values like get() test
958     RUNEVAL(item, "model.append({roleA: 100})");
959     RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
960     RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
961     RUNEVAL(item, "model.append({roleC: {} })");
962     RUNEVAL(item, "model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
963
964     int role = roleFromName(&model, roleName);
965     QVERIFY(role >= 0);
966
967     QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
968
969     // in the worker thread, change the model data and call sync()
970     QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
971             Q_ARG(QVariant, QStringList(expression))));
972     waitForWorker(item);
973
974     // see if we receive the model changes in the main thread's model
975     if (roleValue.type() == QVariant::List) {
976         const QVariantList &list = roleValue.toList();
977         QVERIFY(compareVariantList(list, model.data(index, role)));
978     } else {
979         QCOMPARE(model.data(index, role), roleValue);
980     }
981
982     QCOMPARE(spy.count(), 1);
983
984     QList<QVariant> spyResult = spy.takeFirst();
985     QCOMPARE(spyResult.at(0).toInt(), index);
986     QCOMPARE(spyResult.at(1).toInt(), 1);  // only 1 item is modified at a time
987     QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
988 }
989
990 void tst_qdeclarativelistmodel::get_worker_data()
991 {
992     get_data();
993 }
994
995 /*
996     Test that the tests run in get() also work for nested list data
997 */
998 void tst_qdeclarativelistmodel::get_nested()
999 {
1000     QFETCH(QString, expression);
1001     QFETCH(int, index);
1002     QFETCH(QString, roleName);
1003     QFETCH(QVariant, roleValue);
1004     QFETCH(bool, dynamicRoles);
1005
1006     if (roleValue.type() == QVariant::Map)
1007         return;
1008
1009     QDeclarativeEngine engine;
1010     QDeclarativeComponent component(&engine);
1011     component.setData(
1012         "import QtQuick 2.0\n"
1013         "ListModel {}", QUrl());
1014     QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
1015     model->setDynamicRoles(dynamicRoles);
1016     QVERIFY(component.errorString().isEmpty());
1017     QDeclarativeListModel *childModel;
1018     engine.rootContext()->setContextProperty("model", model);
1019
1020     RUNEXPR("model.append({ listRoleA: [\n"
1021                             "{ roleA: 100 },\n"
1022                             "{ roleA: 200, roleB: 400 },\n"
1023                             "{ roleA: 200, roleB: 400 }, \n"
1024                             "{ roleC: {} }, \n"
1025                             "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1026                             "] })\n");
1027
1028     RUNEXPR("model.append({ listRoleA: [\n"
1029                             "{ roleA: 100 },\n"
1030                             "{ roleA: 200, roleB: 400 },\n"
1031                             "{ roleA: 200, roleB: 400 }, \n"
1032                             "{ roleC: {} }, \n"
1033                             "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1034                             "],\n"
1035                             "listRoleB: [\n"
1036                             "{ roleA: 100 },\n"
1037                             "{ roleA: 200, roleB: 400 },\n"
1038                             "{ roleA: 200, roleB: 400 }, \n"
1039                             "{ roleC: {} }, \n"
1040                             "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1041                             "],\n"
1042                             "listRoleC: [\n"
1043                             "{ roleA: 100 },\n"
1044                             "{ roleA: 200, roleB: 400 },\n"
1045                             "{ roleA: 200, roleB: 400 }, \n"
1046                             "{ roleC: {} }, \n"
1047                             "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1048                             "] })\n");
1049
1050     // Test setting the inner list data for:
1051     //  get(0).listRoleA
1052     //  get(1).listRoleA
1053     //  get(1).listRoleB
1054     //  get(1).listRoleC
1055
1056     QList<QPair<int, QString> > testData;
1057     testData << qMakePair(0, QString("listRoleA"));
1058     testData << qMakePair(1, QString("listRoleA"));
1059     testData << qMakePair(1, QString("listRoleB"));
1060     testData << qMakePair(1, QString("listRoleC"));
1061
1062     for (int i=0; i<testData.count(); i++) {
1063         int outerListIndex = testData[i].first;
1064         QString outerListRoleName = testData[i].second;
1065         int outerListRole = roleFromName(model, outerListRoleName);
1066         QVERIFY(outerListRole >= 0);
1067
1068         childModel = qobject_cast<QDeclarativeListModel*>(model->data(outerListIndex, outerListRole).value<QObject*>());
1069         QVERIFY(childModel);
1070
1071         QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
1072         QDeclarativeExpression expr(engine.rootContext(), model, extendedExpression);
1073
1074         QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
1075         expr.evaluate();
1076         QVERIFY(!expr.hasError());
1077
1078         int role = roleFromName(childModel, roleName);
1079         QVERIFY(role >= 0);
1080         if (roleValue.type() == QVariant::List) {
1081             QVERIFY(compareVariantList(roleValue.toList(), childModel->data(index, role)));
1082         } else {
1083             QCOMPARE(childModel->data(index, role), roleValue);
1084         }
1085         QCOMPARE(spy.count(), 1);
1086
1087         QList<QVariant> spyResult = spy.takeFirst();
1088         QCOMPARE(spyResult.at(0).toInt(), index);
1089         QCOMPARE(spyResult.at(1).toInt(), 1);  // only 1 item is modified at a time
1090         QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
1091     }
1092
1093     delete model;
1094 }
1095
1096 void tst_qdeclarativelistmodel::get_nested_data()
1097 {
1098     get_data();
1099 }
1100
1101 //QTBUG-13754
1102 void tst_qdeclarativelistmodel::crash_model_with_multiple_roles()
1103 {
1104     QDeclarativeEngine eng;
1105     QDeclarativeComponent component(&eng, testFileUrl("multipleroles.qml"));
1106     QObject *rootItem = component.create();
1107     QVERIFY(component.errorString().isEmpty());
1108     QVERIFY(rootItem != 0);
1109     QDeclarativeListModel *model = rootItem->findChild<QDeclarativeListModel*>("listModel");
1110     QVERIFY(model != 0);
1111
1112     // used to cause a crash in QDeclarativeVisualDataModel
1113     model->setProperty(0, "black", true);
1114
1115     delete rootItem;
1116 }
1117
1118 //QTBUG-15190
1119 void tst_qdeclarativelistmodel::set_model_cache()
1120 {
1121     QDeclarativeEngine eng;
1122     QDeclarativeComponent component(&eng, testFileUrl("setmodelcachelist.qml"));
1123     QObject *model = component.create();
1124     QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
1125     QVERIFY(model != 0);
1126     QVERIFY(model->property("ok").toBool());
1127
1128     delete model;
1129 }
1130
1131 void tst_qdeclarativelistmodel::property_changes()
1132 {
1133     QFETCH(QString, script_setup);
1134     QFETCH(QString, script_change);
1135     QFETCH(QString, roleName);
1136     QFETCH(int, listIndex);
1137     QFETCH(bool, itemsChanged);
1138     QFETCH(QString, testExpression);
1139     QFETCH(bool, dynamicRoles);
1140
1141     QDeclarativeEngine engine;
1142     QDeclarativeListModel model;
1143     model.setDynamicRoles(dynamicRoles);
1144     QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1145     engine.rootContext()->setContextObject(&model);
1146
1147     QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1148     expr.evaluate();
1149     QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1150
1151     QString signalHandler = "on" + QString(roleName[0].toUpper()) + roleName.mid(1, roleName.length()) + "Changed:";
1152     QString qml = "import QtQuick 2.0\n"
1153                   "Connections {\n"
1154                         "property bool gotSignal: false\n"
1155                         "target: model.get(" + QString::number(listIndex) + ")\n"
1156                         + signalHandler + " gotSignal = true\n"
1157                   "}\n";
1158
1159     QDeclarativeComponent component(&engine);
1160     component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
1161     engine.rootContext()->setContextProperty("model", &model);
1162     QObject *connectionsObject = component.create();
1163     QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
1164
1165     QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1166
1167     expr.setExpression(script_change);
1168     expr.evaluate();
1169     QVERIFY2(!expr.hasError(), QTest::toString(expr.error()));
1170
1171     // test the object returned by get() emits the correct signals
1172     QCOMPARE(connectionsObject->property("gotSignal").toBool(), itemsChanged);
1173
1174     // test itemsChanged() is emitted correctly
1175     if (itemsChanged) {
1176         QCOMPARE(spyItemsChanged.count(), 1);
1177         QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1178         QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1179     } else {
1180         QCOMPARE(spyItemsChanged.count(), 0);
1181     }
1182
1183     expr.setExpression(testExpression);
1184     QCOMPARE(expr.evaluate().toBool(), true);
1185
1186     delete connectionsObject;
1187 }
1188
1189 void tst_qdeclarativelistmodel::property_changes_data()
1190 {
1191     QTest::addColumn<QString>("script_setup");
1192     QTest::addColumn<QString>("script_change");
1193     QTest::addColumn<QString>("roleName");
1194     QTest::addColumn<int>("listIndex");
1195     QTest::addColumn<bool>("itemsChanged");
1196     QTest::addColumn<QString>("testExpression");
1197     QTest::addColumn<bool>("dynamicRoles");
1198
1199     for (int i=0 ; i < 2 ; ++i) {
1200         bool dr = (i != 0);
1201
1202         QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
1203                 << "b" << 0 << true << "get(0).b == 123" << dr;
1204         QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
1205                 << "b" << 0 << true << "get(0).b == 123" << dr;
1206
1207         QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
1208                 << "b" << 0 << false << "get(0).b == 456" << dr;
1209         QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
1210                 << "b" << 0 << false << "get(0).b == 456" << dr;
1211
1212         QTest::newRow("set: inserted item")
1213                 << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
1214                 << "set(1, {'a':456});"
1215                 << "a" << 1 << true << "get(1).a == 456" << dr;
1216         QTest::newRow("setProperty: inserted item")
1217                 << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
1218                 << "setProperty(1, 'a', 456);"
1219                 << "a" << 1 << true << "get(1).a == 456" << dr;
1220         QTest::newRow("get: inserted item")
1221                 << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
1222                 << "get(1).a = 456;"
1223                 << "a" << 1 << true << "get(1).a == 456" << dr;
1224         QTest::newRow("set: removed item")
1225                 << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
1226                 << "set(0, {'a':456});"
1227                 << "a" << 0 << true << "get(0).a == 456" << dr;
1228         QTest::newRow("setProperty: removed item")
1229                 << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
1230                 << "setProperty(0, 'a', 456);"
1231                 << "a" << 0 << true << "get(0).a == 456" << dr;
1232         QTest::newRow("get: removed item")
1233                 << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
1234                 << "get(0).a = 456;"
1235                 << "a" << 0 << true << "get(0).a == 456" << dr;
1236
1237         // Following tests only call set() since setProperty() only allows plain
1238         // values, not lists, as the argument.
1239         // Note that when a list is changed, itemsChanged() is currently always
1240         // emitted regardless of whether it actually changed or not.
1241
1242         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}]});"
1243                 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2" << dr;
1244
1245         QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1246                 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr;
1247
1248         QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
1249                 << "b" << 0 << true << "get(0).b.count == 0" << dr;
1250
1251         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}]});"
1252                 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3" << dr;
1253
1254         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}]});"
1255                 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr;
1256
1257         QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
1258                 << "b" << 0 << true << "get(0).b.count == 0" << dr;
1259     }
1260 }
1261
1262 void tst_qdeclarativelistmodel::property_changes_worker()
1263 {
1264     QFETCH(QString, script_setup);
1265     QFETCH(QString, script_change);
1266     QFETCH(QString, roleName);
1267     QFETCH(int, listIndex);
1268     QFETCH(bool, itemsChanged);
1269     QFETCH(bool, dynamicRoles);
1270
1271     QDeclarativeListModel model;
1272     model.setDynamicRoles(dynamicRoles);
1273     QDeclarativeEngine engine;
1274     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
1275     QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
1276     QQuickItem *item = createWorkerTest(&engine, &component, &model);
1277     QVERIFY(item != 0);
1278
1279     QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1280     expr.evaluate();
1281     QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1282
1283     QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1284
1285     QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1286             Q_ARG(QVariant, QStringList(script_change))));
1287     waitForWorker(item);
1288
1289     // test itemsChanged() is emitted correctly
1290     if (itemsChanged) {
1291         QCOMPARE(spyItemsChanged.count(), 1);
1292         QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1293         QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1294     } else {
1295         QCOMPARE(spyItemsChanged.count(), 0);
1296     }
1297
1298     delete item;
1299     qApp->processEvents();
1300 }
1301
1302 void tst_qdeclarativelistmodel::property_changes_worker_data()
1303 {
1304     property_changes_data();
1305 }
1306
1307 void tst_qdeclarativelistmodel::clear_data()
1308 {
1309     QTest::addColumn<bool>("dynamicRoles");
1310
1311     QTest::newRow("staticRoles") << false;
1312     QTest::newRow("dynamicRoles") << true;
1313 }
1314
1315 void tst_qdeclarativelistmodel::clear()
1316 {
1317     QFETCH(bool, dynamicRoles);
1318
1319     QDeclarativeEngine engine;
1320     QDeclarativeListModel model;
1321     model.setDynamicRoles(dynamicRoles);
1322     QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1323     engine.rootContext()->setContextProperty("model", &model);
1324
1325     model.clear();
1326     QCOMPARE(model.count(), 0);
1327
1328     RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1329     QCOMPARE(model.count(), 1);
1330
1331     model.clear();
1332     QCOMPARE(model.count(), 0);
1333
1334     RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1335     RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1336     QCOMPARE(model.count(), 2);
1337
1338     model.clear();
1339     QCOMPARE(model.count(), 0);
1340
1341     // clearing does not remove the roles
1342     RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\", propertyC: \"value c\"})");
1343     QList<int> roles = model.roles();
1344     model.clear();
1345     QCOMPARE(model.count(), 0);
1346     QCOMPARE(model.roles(), roles);
1347     QCOMPARE(model.toString(roles[0]), QString("propertyA"));
1348     QCOMPARE(model.toString(roles[1]), QString("propertyB"));
1349     QCOMPARE(model.toString(roles[2]), QString("propertyC"));
1350 }
1351
1352 void tst_qdeclarativelistmodel::signal_handlers_data()
1353 {
1354     QTest::addColumn<bool>("dynamicRoles");
1355
1356     QTest::newRow("staticRoles") << false;
1357     QTest::newRow("dynamicRoles") << true;
1358 }
1359
1360 void tst_qdeclarativelistmodel::signal_handlers()
1361 {
1362     QFETCH(bool, dynamicRoles);
1363
1364     QDeclarativeEngine eng;
1365     QDeclarativeComponent component(&eng, testFileUrl("signalhandlers.qml"));
1366     QObject *model = component.create();
1367     QDeclarativeListModel *lm = qobject_cast<QDeclarativeListModel *>(model);
1368     QVERIFY(lm != 0);
1369     lm->setDynamicRoles(dynamicRoles);
1370     QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
1371     QVERIFY(model != 0);
1372     QVERIFY(model->property("ok").toBool());
1373
1374     delete model;
1375 }
1376
1377 void tst_qdeclarativelistmodel::worker_sync_data()
1378 {
1379     QTest::addColumn<bool>("dynamicRoles");
1380
1381     QTest::newRow("staticRoles") << false;
1382     QTest::newRow("dynamicRoles") << true;
1383 }
1384
1385 void tst_qdeclarativelistmodel::worker_sync()
1386 {
1387     QFETCH(bool, dynamicRoles);
1388
1389     QDeclarativeListModel model;
1390     model.setDynamicRoles(dynamicRoles);
1391     QDeclarativeEngine eng;
1392     QDeclarativeComponent component(&eng, testFileUrl("workersync.qml"));
1393     QQuickItem *item = createWorkerTest(&eng, &component, &model);
1394     QVERIFY(item != 0);
1395
1396     QVERIFY(model.count() == 0);
1397
1398     QVERIFY(QMetaObject::invokeMethod(item, "addItem0"));
1399
1400     QVERIFY(model.count() == 2);
1401     QVariant childData = model.data(0, 0);
1402     QDeclarativeListModel *childModel = qobject_cast<QDeclarativeListModel *>(childData.value<QObject *>());
1403     QVERIFY(childModel);
1404     QVERIFY(childModel->count() == 1);
1405
1406     QSignalSpy spyModelInserted(&model, SIGNAL(itemsInserted(int,int)));
1407     QSignalSpy spyChildInserted(childModel, SIGNAL(itemsInserted(int,int)));
1408
1409     QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
1410     waitForWorker(item);
1411
1412     QVERIFY(model.count() == 2);
1413     QVERIFY(childModel->count() == 1);
1414     QVERIFY(spyModelInserted.count() == 0);
1415     QVERIFY(spyChildInserted.count() == 0);
1416
1417     QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1418     waitForWorker(item);
1419
1420     QVERIFY(model.count() == 2);
1421     QVERIFY(childModel->count() == 2);
1422     QVERIFY(spyModelInserted.count() == 0);
1423     QVERIFY(spyChildInserted.count() == 1);
1424
1425     QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
1426     waitForWorker(item);
1427
1428     QVERIFY(model.count() == 2);
1429     QVERIFY(childModel->count() == 2);
1430     QVERIFY(spyModelInserted.count() == 0);
1431     QVERIFY(spyChildInserted.count() == 1);
1432
1433     QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1434     waitForWorker(item);
1435
1436     QVERIFY(model.count() == 2);
1437     QVERIFY(childModel->count() == 3);
1438     QVERIFY(spyModelInserted.count() == 0);
1439     QVERIFY(spyChildInserted.count() == 2);
1440
1441     delete item;
1442     qApp->processEvents();
1443 }
1444
1445 void tst_qdeclarativelistmodel::worker_remove_element_data()
1446 {
1447     worker_sync_data();
1448 }
1449
1450 void tst_qdeclarativelistmodel::worker_remove_element()
1451 {
1452     QFETCH(bool, dynamicRoles);
1453
1454     QDeclarativeListModel model;
1455     model.setDynamicRoles(dynamicRoles);
1456     QDeclarativeEngine eng;
1457     QDeclarativeComponent component(&eng, testFileUrl("workerremoveelement.qml"));
1458     QQuickItem *item = createWorkerTest(&eng, &component, &model);
1459     QVERIFY(item != 0);
1460
1461     QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
1462
1463     QVERIFY(model.count() == 0);
1464     QVERIFY(spyModelRemoved.count() == 0);
1465
1466     QVERIFY(QMetaObject::invokeMethod(item, "addItem"));
1467
1468     QVERIFY(model.count() == 1);
1469
1470     QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker"));
1471     waitForWorker(item);
1472
1473     QVERIFY(model.count() == 1);
1474     QVERIFY(spyModelRemoved.count() == 0);
1475
1476     QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1477     waitForWorker(item);
1478
1479     QVERIFY(model.count() == 0);
1480     QVERIFY(spyModelRemoved.count() == 1);
1481
1482     delete item;
1483     qApp->processEvents();
1484 }
1485
1486 void tst_qdeclarativelistmodel::worker_remove_list_data()
1487 {
1488     worker_sync_data();
1489 }
1490
1491 void tst_qdeclarativelistmodel::worker_remove_list()
1492 {
1493     QFETCH(bool, dynamicRoles);
1494
1495     QDeclarativeListModel model;
1496     model.setDynamicRoles(dynamicRoles);
1497     QDeclarativeEngine eng;
1498     QDeclarativeComponent component(&eng, testFileUrl("workerremovelist.qml"));
1499     QQuickItem *item = createWorkerTest(&eng, &component, &model);
1500     QVERIFY(item != 0);
1501
1502     QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
1503
1504     QVERIFY(model.count() == 0);
1505     QVERIFY(spyModelRemoved.count() == 0);
1506
1507     QVERIFY(QMetaObject::invokeMethod(item, "addList"));
1508
1509     QVERIFY(model.count() == 1);
1510
1511     QVERIFY(QMetaObject::invokeMethod(item, "removeListViaWorker"));
1512     waitForWorker(item);
1513
1514     QVERIFY(model.count() == 1);
1515     QVERIFY(spyModelRemoved.count() == 0);
1516
1517     QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1518     waitForWorker(item);
1519
1520     QVERIFY(model.count() == 0);
1521     QVERIFY(spyModelRemoved.count() == 1);
1522
1523     delete item;
1524     qApp->processEvents();
1525 }
1526
1527 void tst_qdeclarativelistmodel::role_mode_data()
1528 {
1529     QTest::addColumn<QString>("script");
1530     QTest::addColumn<int>("result");
1531     QTest::addColumn<QString>("warning");
1532
1533     QTest::newRow("default0") << "{dynamicRoles}" << 0 << "";
1534     QTest::newRow("default1") << "{append({'a':1});dynamicRoles}" << 0 << "";
1535
1536     QTest::newRow("enableDynamic0") << "{dynamicRoles=true;dynamicRoles}" << 1 << "";
1537     QTest::newRow("enableDynamic1") << "{append({'a':1});dynamicRoles=true;dynamicRoles}" << 0 << "<Unknown File>: QML ListModel: unable to enable dynamic roles as this model is not empty!";
1538     QTest::newRow("enableDynamic2") << "{dynamicRoles=true;append({'a':1});dynamicRoles=false;dynamicRoles}" << 1 << "<Unknown File>: QML ListModel: unable to enable static roles as this model is not empty!";
1539 }
1540
1541 void tst_qdeclarativelistmodel::role_mode()
1542 {
1543     QFETCH(QString, script);
1544     QFETCH(int, result);
1545     QFETCH(QString, warning);
1546
1547     QDeclarativeEngine engine;
1548     QDeclarativeListModel model;
1549     QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
1550     engine.rootContext()->setContextObject(&model);
1551     QDeclarativeExpression e(engine.rootContext(), &model, script);
1552     if (!warning.isEmpty())
1553         QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
1554
1555     int actual = e.evaluate().toInt();
1556     if (e.hasError())
1557         qDebug() << e.error(); // errors not expected
1558
1559     QCOMPARE(actual,result);
1560 }
1561
1562 void tst_qdeclarativelistmodel::dynamic_role_data()
1563 {
1564     QTest::addColumn<QString>("preamble");
1565     QTest::addColumn<QString>("script");
1566     QTest::addColumn<int>("result");
1567
1568     QTest::newRow("sync1") << "{append({'a':[{'b':1},{'b':2}]})}" << "{get(0).a = 'string';count}" << 1;
1569 }
1570
1571 void tst_qdeclarativelistmodel::dynamic_role()
1572 {
1573     QFETCH(QString, preamble);
1574     QFETCH(QString, script);
1575     QFETCH(int, result);
1576
1577     QDeclarativeListModel model;
1578     model.setDynamicRoles(true);
1579     QDeclarativeEngine engine;
1580     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
1581     QQuickItem *item = createWorkerTest(&engine, &component, &model);
1582     QVERIFY(item != 0);
1583
1584     QDeclarativeExpression preExp(engine.rootContext(), &model, preamble);
1585     QCOMPARE(preExp.evaluate().toInt(), 0);
1586
1587     if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
1588         script = script.mid(1, script.length() - 2);
1589     QVariantList operations;
1590     foreach (const QString &s, script.split(';')) {
1591         if (!s.isEmpty())
1592             operations << s;
1593     }
1594
1595     // execute a set of commands on the worker list model, then check the
1596     // changes are reflected in the list model in the main thread
1597     QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1598             Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
1599     waitForWorker(item);
1600
1601     QDeclarativeExpression e(engine.rootContext(), &model, operations.last().toString());
1602     QCOMPARE(e.evaluate().toInt(), result);
1603
1604     delete item;
1605     qApp->processEvents();
1606 }
1607
1608 QTEST_MAIN(tst_qdeclarativelistmodel)
1609
1610 #include "tst_qdeclarativelistmodel.moc"