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