1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
7 ** This file is part of the test suite of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include <QtQuick1/private/qdeclarativeitem_p.h>
43 #include <QtQuick1/private/qdeclarativetext_p.h>
44 #include <QtQuick1/private/qdeclarativelistmodel_p.h>
45 #include <QtDeclarative/private/qdeclarativeengine_p.h>
46 #include <QtDeclarative/private/qdeclarativeexpression_p.h>
47 #include <QDeclarativeComponent>
49 #include <QtCore/qtimer.h>
50 #include <QtCore/qdebug.h>
51 #include <QtCore/qtranslator.h>
54 Q_DECLARE_METATYPE(QList<int>)
55 Q_DECLARE_METATYPE(QList<QVariantHash>)
57 class tst_qdeclarativelistmodel : public QObject
61 tst_qdeclarativelistmodel() {}
64 int roleFromName(const QDeclarative1ListModel *model, const QString &roleName);
65 QDeclarativeItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarative1ListModel *model);
66 void waitForWorker(QDeclarativeItem *item);
70 void static_types_data();
72 void static_nestedElements();
73 void static_nestedElements_data();
76 void dynamic_worker_data();
77 void dynamic_worker();
78 void dynamic_worker_sync_data();
79 void dynamic_worker_sync();
80 void convertNestedToFlat_fail();
81 void convertNestedToFlat_fail_data();
82 void convertNestedToFlat_ok();
83 void convertNestedToFlat_ok_data();
92 void get_worker_data();
94 void get_nested_data();
95 void crash_model_with_multiple_roles();
96 void set_model_cache();
97 void property_changes();
98 void property_changes_data();
99 void property_changes_worker();
100 void property_changes_worker_data();
104 int tst_qdeclarativelistmodel::roleFromName(const QDeclarative1ListModel *model, const QString &roleName)
106 QList<int> roles = model->roles();
107 for (int i=0; i<roles.count(); i++) {
108 if (model->toString(roles[i]) == roleName)
114 QDeclarativeItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarative1ListModel *model)
116 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(component->create());
117 QDeclarativeEngine::setContextForObject(model, eng->rootContext());
119 item->setProperty("model", qVariantFromValue(model));
123 void tst_qdeclarativelistmodel::waitForWorker(QDeclarativeItem *item)
127 timer.setSingleShot(true);
128 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
130 QDeclarativeProperty prop(item, "done");
131 QVERIFY(prop.isValid());
132 QVERIFY(prop.connectNotifySignal(&loop, SLOT(quit())));
135 QVERIFY(timer.isActive());
138 void tst_qdeclarativelistmodel::static_types_data()
140 QTest::addColumn<QString>("qml");
141 QTest::addColumn<QVariant>("value");
143 QTest::newRow("string")
144 << "ListElement { foo: \"bar\" }"
145 << QVariant(QString("bar"));
147 QTest::newRow("real")
148 << "ListElement { foo: 10.5 }"
151 QTest::newRow("real0")
152 << "ListElement { foo: 0 }"
153 << QVariant(double(0));
155 QTest::newRow("bool")
156 << "ListElement { foo: false }"
159 QTest::newRow("bool")
160 << "ListElement { foo: true }"
163 QTest::newRow("enum")
164 << "ListElement { foo: Text.AlignHCenter }"
165 << QVariant(double(QDeclarativeText::AlignHCenter));
168 void tst_qdeclarativelistmodel::static_types()
170 QFETCH(QString, qml);
171 QFETCH(QVariant, value);
173 qml = "import QtQuick 1.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
175 QDeclarativeEngine engine;
176 QDeclarativeComponent component(&engine);
177 component.setData(qml.toUtf8(),
178 QUrl::fromLocalFile(QString("dummy.qml")));
180 QVERIFY(!component.isError());
182 QObject *obj = component.create();
185 QVariant actual = obj->property("test");
187 QCOMPARE(actual, value);
188 QCOMPARE(actual.toString(), value.toString());
193 void tst_qdeclarativelistmodel::static_i18n()
195 QString expect = QString::fromUtf8("na\303\257ve");
197 QString componentStr = "import QtQuick 1.0\nItem { property string prop1: model.get(0).prop1; property string prop2: model.get(0).prop2; ListModel { id: model; ListElement { prop1: \""+expect+"\"; prop2: QT_TR_NOOP(\""+expect+"\") } } }";
198 QDeclarativeEngine engine;
199 QDeclarativeComponent component(&engine);
200 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
201 QObject *obj = component.create();
203 QString prop1 = obj->property("prop1").toString();
204 QCOMPARE(prop1,expect);
205 QString prop2 = obj->property("prop2").toString();
206 QCOMPARE(prop2,expect); // (no, not translated, QT_TR_NOOP is a no-op)
210 void tst_qdeclarativelistmodel::static_nestedElements()
212 QFETCH(int, elementCount);
214 QStringList elements;
215 for (int i=0; i<elementCount; i++)
216 elements.append("ListElement { a: 1; b: 2 }");
217 QString elementsStr = elements.join(",\n") + "\n";
219 QString componentStr =
220 "import QtQuick 1.0\n"
222 " property variant count: model.get(0).attributes.count\n"
227 componentStr += elementsStr.toUtf8().constData();
234 QDeclarativeEngine engine;
235 QDeclarativeComponent component(&engine);
236 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
238 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
241 QVariant count = obj->property("count");
242 QCOMPARE(count.type(), QVariant::Int);
243 QCOMPARE(count.toInt(), elementCount);
248 void tst_qdeclarativelistmodel::static_nestedElements_data()
250 QTest::addColumn<int>("elementCount");
252 QTest::newRow("0 items") << 0;
253 QTest::newRow("1 item") << 1;
254 QTest::newRow("2 items") << 2;
255 QTest::newRow("many items") << 5;
258 void tst_qdeclarativelistmodel::dynamic_data()
260 QTest::addColumn<QString>("script");
261 QTest::addColumn<int>("result");
262 QTest::addColumn<QString>("warning");
266 QTest::newRow("count") << "count" << 0 << "";
268 QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
269 QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "";
270 QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "";
271 QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "";
273 QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "";
274 QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "";
276 QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "";
277 QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "";
278 QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
279 QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
280 QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
281 QTest::newRow("append4b") << "{append([1,2,3])}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
283 QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "";
284 QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "";
285 QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "";
287 QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "";
288 QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "";
289 QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "";
290 QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "";
291 QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "";
292 QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: index -1 out of range";
293 QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: index 0 out of range";
294 QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: index 0 out of range";
295 QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: index 1 out of range";
297 QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "";
298 QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range";
299 QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "";
300 QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "";
301 QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "";
302 QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "";
303 QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "";
304 QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range";
305 QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
306 QTest::newRow("insert5b") << "{insert(0,[1,2,3])}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
308 QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "";
309 QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "";
310 QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "";
311 QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "";
312 QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "";
313 QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
314 QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
315 QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
316 QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "";
318 QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "";
319 QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "";
320 QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "";
321 QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "";
322 QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range";
323 QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
324 QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range";
325 QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "";
327 QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "";
328 QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "";
329 QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "";
330 QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "";
331 QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "";
332 QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "";
333 QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "";
334 QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "";
335 QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "";
336 QTest::newRow("move3a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
337 QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
338 QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
339 QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
343 QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
344 QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";
345 QTest::newRow("nested-append3") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.append({'a':4});get(0).bars.get(3).a}" << 4 << "";
347 QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "";
348 QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "";
350 QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "";
351 QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "";
354 void tst_qdeclarativelistmodel::dynamic()
356 QFETCH(QString, script);
358 QFETCH(QString, warning);
360 QDeclarativeEngine engine;
361 QDeclarative1ListModel model;
362 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
363 engine.rootContext()->setContextObject(&model);
364 QDeclarativeExpression e(engine.rootContext(), &model, script);
365 if (!warning.isEmpty())
366 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
368 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
370 int actual = e.evaluate().toInt();
372 qDebug() << e.error(); // errors not expected
374 QCOMPARE(actual,result);
376 if (model.count() > 0)
377 QVERIFY(spyCount.count() > 0);
380 void tst_qdeclarativelistmodel::dynamic_worker_data()
385 void tst_qdeclarativelistmodel::dynamic_worker()
387 QFETCH(QString, script);
389 QFETCH(QString, warning);
391 if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
394 // This is same as dynamic() except it applies the test to a ListModel called
395 // from a WorkerScript (i.e. testing the internal FlatListModel that is created
396 // by the WorkerListModelAgent)
398 QDeclarative1ListModel model;
399 QDeclarativeEngine eng;
400 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
401 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
404 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
406 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
407 script = script.mid(1, script.length() - 2);
408 QVariantList operations;
409 foreach (const QString &s, script.split(';')) {
414 if (!warning.isEmpty())
415 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
417 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
418 Q_ARG(QVariant, operations)));
420 QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
422 if (model.count() > 0)
423 QVERIFY(spyCount.count() > 0);
426 qApp->processEvents();
429 void tst_qdeclarativelistmodel::dynamic_worker_sync_data()
434 void tst_qdeclarativelistmodel::dynamic_worker_sync()
436 QFETCH(QString, script);
438 QFETCH(QString, warning);
440 // This is the same as dynamic_worker() except that it executes a set of list operations
441 // from the worker script, calls sync(), and tests the changes are reflected in the
442 // list in the main thread
444 QDeclarative1ListModel model;
445 QDeclarativeEngine eng;
446 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
447 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
450 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
451 script = script.mid(1, script.length() - 2);
452 QVariantList operations;
453 foreach (const QString &s, script.split(';')) {
458 if (!warning.isEmpty())
459 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
461 // execute a set of commands on the worker list model, then check the
462 // changes are reflected in the list model in the main thread
463 if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
464 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
466 if (QByteArray(QTest::currentDataTag()).startsWith("nested-set"))
467 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
469 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
470 Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
473 QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
474 if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
475 QCOMPARE(e.evaluate().toInt(), result);
478 qApp->processEvents();
481 #define RUNEVAL(object, string) \
482 QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
484 inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
486 QDeclarativeExpression expr(engine->rootContext(), 0, str);
487 return expr.evaluate();
490 #define RUNEXPR(string) runexpr(&engine, QString(string))
492 void tst_qdeclarativelistmodel::convertNestedToFlat_fail()
494 // If a model has nested data, it cannot be used at all from a worker script
496 QFETCH(QString, script);
498 QDeclarative1ListModel model;
499 QDeclarativeEngine eng;
500 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
501 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
504 RUNEVAL(item, "model.append({foo: 123})");
505 RUNEVAL(item, "model.append({foo: [{}, {}]})");
507 QCOMPARE(model.count(), 2);
509 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: List contains list-type data and cannot be used from a worker script");
510 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
513 QCOMPARE(model.count(), 2);
516 qApp->processEvents();
519 void tst_qdeclarativelistmodel::convertNestedToFlat_fail_data()
521 QTest::addColumn<QString>("script");
523 QTest::newRow("clear") << "clear()";
524 QTest::newRow("remove") << "remove(0)";
525 QTest::newRow("append") << "append({'x':1})";
526 QTest::newRow("insert") << "insert(0, {'x':1})";
527 QTest::newRow("set") << "set(0, {'foo':1})";
528 QTest::newRow("setProperty") << "setProperty(0, 'foo', 1})";
529 QTest::newRow("move") << "move(0, 1, 1})";
530 QTest::newRow("get") << "get(0)";
533 void tst_qdeclarativelistmodel::convertNestedToFlat_ok()
536 // If a model only has plain data, it can be modified from a worker script. However,
537 // once the model is used from a worker script, it no longer accepts nested data
539 QFETCH(QString, script);
541 QDeclarative1ListModel model;
542 QDeclarativeEngine eng;
543 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
544 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
547 RUNEVAL(item, "model.append({foo: 123})");
549 QCOMPARE(model.count(), 1);
551 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
554 // can still add plain data
555 int count = model.count();
557 RUNEVAL(item, "model.append({foo: 123})");
559 QCOMPARE(model.count(), count+1);
561 const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
563 QTest::ignoreMessage(QtWarningMsg, warning);
564 RUNEVAL(item, "model.append({foo: [{}, {}]})");
566 QTest::ignoreMessage(QtWarningMsg, warning);
567 RUNEVAL(item, "model.insert(0, {foo: [{}, {}]})");
569 QTest::ignoreMessage(QtWarningMsg, warning);
570 RUNEVAL(item, "model.set(0, {foo: [{}, {}]})");
572 QCOMPARE(model.count(), count+1);
575 qApp->processEvents();
578 void tst_qdeclarativelistmodel::convertNestedToFlat_ok_data()
580 convertNestedToFlat_fail_data();
583 void tst_qdeclarativelistmodel::enumerate()
585 QDeclarativeEngine eng;
586 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
587 QVERIFY(!component.isError());
588 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(component.create());
590 QStringList r = item->property("result").toString().split(":");
591 QCOMPARE(r[0],QLatin1String("val1=1Y"));
592 QCOMPARE(r[1],QLatin1String("val2=2Y"));
593 QCOMPARE(r[2],QLatin1String("val3=strY"));
594 QCOMPARE(r[3],QLatin1String("val4=falseN"));
595 QCOMPARE(r[4],QLatin1String("val5=trueY"));
599 void tst_qdeclarativelistmodel::error_data()
601 QTest::addColumn<QString>("qml");
602 QTest::addColumn<QString>("error");
604 QTest::newRow("id not allowed in ListElement")
605 << "import QtQuick 1.0\nListModel { ListElement { id: fred } }"
606 << "ListElement: cannot use reserved \"id\" property";
608 QTest::newRow("id allowed in ListModel")
609 << "import QtQuick 1.0\nListModel { id:model }"
612 QTest::newRow("random properties not allowed in ListModel")
613 << "import QtQuick 1.0\nListModel { foo:123 }"
614 << "ListModel: undefined property 'foo'";
616 QTest::newRow("random properties allowed in ListElement")
617 << "import QtQuick 1.0\nListModel { ListElement { foo:123 } }"
620 QTest::newRow("bindings not allowed in ListElement")
621 << "import QtQuick 1.0\nRectangle { id: rect; ListModel { ListElement { foo: rect.color } } }"
622 << "ListElement: cannot use script for property value";
624 QTest::newRow("random object list properties allowed in ListElement")
625 << "import QtQuick 1.0\nListModel { ListElement { foo: [ ListElement { bar: 123 } ] } }"
628 QTest::newRow("default properties not allowed in ListElement")
629 << "import QtQuick 1.0\nListModel { ListElement { Item { } } }"
630 << "ListElement: cannot contain nested elements";
632 QTest::newRow("QML elements not allowed in ListElement")
633 << "import QtQuick 1.0\nListModel { ListElement { a: Item { } } }"
634 << "ListElement: cannot contain nested elements";
636 QTest::newRow("qualified ListElement supported")
637 << "import QtQuick 1.0 as Foo\nFoo.ListModel { Foo.ListElement { a: 123 } }"
640 QTest::newRow("qualified ListElement required")
641 << "import QtQuick 1.0 as Foo\nFoo.ListModel { ListElement { a: 123 } }"
642 << "ListElement is not a type";
644 QTest::newRow("unknown qualified ListElement not allowed")
645 << "import QtQuick 1.0\nListModel { Foo.ListElement { a: 123 } }"
646 << "Foo.ListElement - Foo is not a namespace";
649 void tst_qdeclarativelistmodel::error()
651 QFETCH(QString, qml);
652 QFETCH(QString, error);
654 QDeclarativeEngine engine;
655 QDeclarativeComponent component(&engine);
656 component.setData(qml.toUtf8(),
657 QUrl::fromLocalFile(QString("dummy.qml")));
658 if (error.isEmpty()) {
659 QVERIFY(!component.isError());
661 QVERIFY(component.isError());
662 QList<QDeclarativeError> errors = component.errors();
663 QCOMPARE(errors.count(),1);
664 QCOMPARE(errors.at(0).description(),error);
668 void tst_qdeclarativelistmodel::syncError()
670 QString qml = "import QtQuick 1.0\nListModel { id: lm; Component.onCompleted: lm.sync() }";
671 QString error = "file:dummy.qml:2:1: QML ListModel: List sync() can only be called from a WorkerScript";
673 QDeclarativeEngine engine;
674 QDeclarativeComponent component(&engine);
675 component.setData(qml.toUtf8(),
676 QUrl::fromLocalFile(QString("dummy.qml")));
677 QTest::ignoreMessage(QtWarningMsg,error.toUtf8());
678 QObject *obj = component.create();
684 Test model changes from set() are available to the view
686 void tst_qdeclarativelistmodel::set()
688 QDeclarativeEngine engine;
689 QDeclarative1ListModel model;
690 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
691 engine.rootContext()->setContextProperty("model", &model);
693 RUNEXPR("model.append({test:false})");
694 RUNEXPR("model.set(0, {test:true})");
696 QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache
697 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
699 RUNEXPR("model.set(0, {test:false})");
700 QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
701 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
705 Test model changes on values returned by get() are available to the view
707 void tst_qdeclarativelistmodel::get()
709 QFETCH(QString, expression);
711 QFETCH(QString, roleName);
712 QFETCH(QVariant, roleValue);
714 QDeclarativeEngine engine;
715 QDeclarativeComponent component(&engine);
717 "import QtQuick 1.0\n"
719 "ListElement { roleA: 100 }\n"
720 "ListElement { roleA: 200; roleB: 400 } \n"
721 "ListElement { roleA: 200; roleB: 400 } \n"
723 QDeclarative1ListModel *model = qobject_cast<QDeclarative1ListModel*>(component.create());
724 int role = roleFromName(model, roleName);
727 QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
728 QDeclarativeExpression expr(engine.rootContext(), model, expression);
730 QVERIFY(!expr.hasError());
732 QCOMPARE(model->data(index, role), roleValue);
733 QCOMPARE(spy.count(), 1);
735 QList<QVariant> spyResult = spy.takeFirst();
736 QCOMPARE(spyResult.at(0).toInt(), index);
737 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
738 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
743 void tst_qdeclarativelistmodel::get_data()
745 QTest::addColumn<QString>("expression");
746 QTest::addColumn<int>("index");
747 QTest::addColumn<QString>("roleName");
748 QTest::addColumn<QVariant>("roleValue");
750 QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
751 QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
755 QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
758 map.clear(); map["a"] = 50; map["b"] = 500;
760 map.clear(); map["c"] = 1000;
762 QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
765 void tst_qdeclarativelistmodel::get_worker()
767 QFETCH(QString, expression);
769 QFETCH(QString, roleName);
770 QFETCH(QVariant, roleValue);
772 QDeclarative1ListModel model;
773 QDeclarativeEngine eng;
774 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
775 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
778 // Add some values like get() test
779 RUNEVAL(item, "model.append({roleA: 100})");
780 RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
781 RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
783 int role = roleFromName(&model, roleName);
786 const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
787 if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
788 QTest::ignoreMessage(QtWarningMsg, warning);
789 QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
791 // in the worker thread, change the model data and call sync()
792 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
793 Q_ARG(QVariant, QStringList(expression))));
796 // see if we receive the model changes in the main thread's model
797 if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
798 QVERIFY(model.data(index, role) != roleValue);
799 QCOMPARE(spy.count(), 0);
801 QCOMPARE(model.data(index, role), roleValue);
802 QCOMPARE(spy.count(), 1);
804 QList<QVariant> spyResult = spy.takeFirst();
805 QCOMPARE(spyResult.at(0).toInt(), index);
806 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
807 QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
811 void tst_qdeclarativelistmodel::get_worker_data()
817 Test that the tests run in get() also work for nested list data
819 void tst_qdeclarativelistmodel::get_nested()
821 QFETCH(QString, expression);
823 QFETCH(QString, roleName);
824 QFETCH(QVariant, roleValue);
826 QDeclarativeEngine eng;
827 QDeclarativeComponent component(&eng);
829 "import QtQuick 1.0\n"
833 "ListElement { roleA: 100 },\n"
834 "ListElement { roleA: 200; roleB: 400 },\n"
835 "ListElement { roleA: 200; roleB: 400 } \n"
840 "ListElement { roleA: 100 },\n"
841 "ListElement { roleA: 200; roleB: 400 },\n"
842 "ListElement { roleA: 200; roleB: 400 } \n"
845 "ListElement { roleA: 100 },\n"
846 "ListElement { roleA: 200; roleB: 400 },\n"
847 "ListElement { roleA: 200; roleB: 400 } \n"
850 "ListElement { roleA: 100 },\n"
851 "ListElement { roleA: 200; roleB: 400 },\n"
852 "ListElement { roleA: 200; roleB: 400 } \n"
856 QDeclarative1ListModel *model = qobject_cast<QDeclarative1ListModel*>(component.create());
857 QVERIFY(component.errorString().isEmpty());
858 QDeclarative1ListModel *childModel;
860 // Test setting the inner list data for:
866 QList<QPair<int, QString> > testData;
867 testData << qMakePair(0, QString("listRoleA"));
868 testData << qMakePair(1, QString("listRoleA"));
869 testData << qMakePair(1, QString("listRoleB"));
870 testData << qMakePair(1, QString("listRoleC"));
872 for (int i=0; i<testData.count(); i++) {
873 int outerListIndex = testData[i].first;
874 QString outerListRoleName = testData[i].second;
875 int outerListRole = roleFromName(model, outerListRoleName);
876 QVERIFY(outerListRole >= 0);
878 childModel = qobject_cast<QDeclarative1ListModel*>(model->data(outerListIndex, outerListRole).value<QObject*>());
881 QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
882 QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
884 QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
886 QVERIFY(!expr.hasError());
888 int role = roleFromName(childModel, roleName);
890 QCOMPARE(childModel->data(index, role), roleValue);
891 QCOMPARE(spy.count(), 1);
893 QList<QVariant> spyResult = spy.takeFirst();
894 QCOMPARE(spyResult.at(0).toInt(), index);
895 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
896 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
902 void tst_qdeclarativelistmodel::get_nested_data()
908 void tst_qdeclarativelistmodel::crash_model_with_multiple_roles()
910 QDeclarativeEngine eng;
911 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/multipleroles.qml"));
912 QObject *rootItem = component.create();
913 QVERIFY(component.errorString().isEmpty());
914 QVERIFY(rootItem != 0);
915 QDeclarative1ListModel *model = rootItem->findChild<QDeclarative1ListModel*>("listModel");
918 // used to cause a crash in QDeclarativeVisualDataModel
919 model->setProperty(0, "black", true);
925 void tst_qdeclarativelistmodel::set_model_cache()
927 QDeclarativeEngine eng;
928 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/setmodelcachelist.qml"));
929 QObject *model = component.create();
930 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
932 QVERIFY(model->property("ok").toBool());
937 void tst_qdeclarativelistmodel::property_changes()
939 QFETCH(QString, script_setup);
940 QFETCH(QString, script_change);
941 QFETCH(QString, roleName);
942 QFETCH(int, listIndex);
943 QFETCH(bool, itemsChanged);
944 QFETCH(QString, testExpression);
946 QDeclarativeEngine engine;
947 QDeclarative1ListModel model;
948 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
949 engine.rootContext()->setContextObject(&model);
951 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
953 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
955 QString signalHandler = "on" + QString(roleName[0].toUpper()) + roleName.mid(1, roleName.length()) + "Changed:";
956 QString qml = "import QtQuick 1.0\n"
958 "property bool gotSignal: false\n"
959 "target: model.get(0)\n"
960 + signalHandler + " gotSignal = true\n"
962 QDeclarativeComponent component(&engine);
963 component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
964 engine.rootContext()->setContextProperty("model", &model);
965 QObject *connectionsObject = component.create();
966 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
968 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
970 expr.setExpression(script_change);
972 QVERIFY2(!expr.hasError(), QTest::toString(expr.error()));
974 // test the object returned by get() emits the correct signals
975 QCOMPARE(connectionsObject->property("gotSignal").toBool(), itemsChanged);
977 // test itemsChanged() is emitted correctly
979 QCOMPARE(spyItemsChanged.count(), 1);
980 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
981 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
983 QCOMPARE(spyItemsChanged.count(), 0);
986 expr.setExpression(testExpression);
987 QCOMPARE(expr.evaluate().toBool(), true);
989 delete connectionsObject;
992 void tst_qdeclarativelistmodel::property_changes_data()
994 QTest::addColumn<QString>("script_setup");
995 QTest::addColumn<QString>("script_change");
996 QTest::addColumn<QString>("roleName");
997 QTest::addColumn<int>("listIndex");
998 QTest::addColumn<bool>("itemsChanged");
999 QTest::addColumn<QString>("testExpression");
1001 QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
1002 << "b" << 0 << true << "get(0).b == 123";
1003 QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
1004 << "b" << 0 << true << "get(0).b == 123";
1006 QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
1007 << "b" << 0 << false << "get(0).b == 456";
1008 QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
1009 << "b" << 0 << false << "get(0).b == 456";
1011 // Following tests only call set() since setProperty() only allows plain
1012 // values, not lists, as the argument.
1013 // Note that when a list is changed, itemsChanged() is currently always
1014 // emitted regardless of whether it actually changed or not.
1016 QTest::newRow("nested-set: list, new size") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});"
1017 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2";
1019 QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1020 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
1022 QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
1023 << "b" << 0 << true << "get(0).b.count == 0";
1025 QTest::newRow("nested-set: list, same size, different values") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':222},{'a':3}]});"
1026 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3";
1028 QTest::newRow("nested-set: list, no changes") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1029 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
1031 QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
1032 << "b" << 0 << true << "get(0).b.count == 0";
1035 void tst_qdeclarativelistmodel::property_changes_worker()
1037 // nested models are not supported when WorkerScript is involved
1038 if (QByteArray(QTest::currentDataTag()).startsWith("nested-"))
1041 QFETCH(QString, script_setup);
1042 QFETCH(QString, script_change);
1043 QFETCH(QString, roleName);
1044 QFETCH(int, listIndex);
1045 QFETCH(bool, itemsChanged);
1047 QDeclarative1ListModel model;
1048 QDeclarativeEngine engine;
1049 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
1050 QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
1051 QDeclarativeItem *item = createWorkerTest(&engine, &component, &model);
1054 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1056 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1058 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1060 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1061 Q_ARG(QVariant, QStringList(script_change))));
1062 waitForWorker(item);
1064 // test itemsChanged() is emitted correctly
1066 QCOMPARE(spyItemsChanged.count(), 1);
1067 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1068 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1070 QCOMPARE(spyItemsChanged.count(), 0);
1074 qApp->processEvents();
1077 void tst_qdeclarativelistmodel::property_changes_worker_data()
1079 property_changes_data();
1082 void tst_qdeclarativelistmodel::clear()
1084 QDeclarativeEngine engine;
1085 QDeclarative1ListModel model;
1086 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1087 engine.rootContext()->setContextProperty("model", &model);
1090 QCOMPARE(model.count(), 0);
1092 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1093 QCOMPARE(model.count(), 1);
1096 QCOMPARE(model.count(), 0);
1098 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1099 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1100 QCOMPARE(model.count(), 2);
1103 QCOMPARE(model.count(), 0);
1105 // clearing does not remove the roles
1106 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\", propertyC: \"value c\"})");
1107 QList<int> roles = model.roles();
1109 QCOMPARE(model.count(), 0);
1110 QCOMPARE(model.roles(), roles);
1111 QCOMPARE(model.toString(roles[0]), QString("propertyA"));
1112 QCOMPARE(model.toString(roles[1]), QString("propertyB"));
1113 QCOMPARE(model.toString(roles[2]), QString("propertyC"));
1116 QTEST_MAIN(tst_qdeclarativelistmodel)
1118 #include "tst_qdeclarativelistmodel.moc"