1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtQuick/private/qquickitem_p.h>
43 #include <QtQuick/private/qquicktext_p.h>
44 #include <QtDeclarative/private/qdeclarativeengine_p.h>
45 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
46 #include <QtDeclarative/private/qdeclarativeexpression_p.h>
47 #include <QDeclarativeComponent>
49 #include <QtCore/qtimer.h>
50 #include <QtCore/qdebug.h>
51 #include <QtCore/qtranslator.h>
54 #include "../../shared/util.h"
56 Q_DECLARE_METATYPE(QList<int>)
57 Q_DECLARE_METATYPE(QList<QVariantHash>)
59 #define RUNEVAL(object, string) \
60 QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
62 inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
64 QDeclarativeExpression expr(engine->rootContext(), 0, str);
65 return expr.evaluate();
68 #define RUNEXPR(string) runexpr(&engine, QString(string))
70 static bool isValidErrorMessage(const QString &msg, bool dynamicRoleTest)
76 } else if (dynamicRoleTest) {
77 if (msg.contains("Can't assign to existing role") || msg.contains("Can't create role for unsupported data type"))
84 class tst_qdeclarativelistmodel : public QDeclarativeDataTest
88 tst_qdeclarativelistmodel() {}
91 int roleFromName(const QDeclarativeListModel *model, const QString &roleName);
92 QQuickItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
93 void waitForWorker(QQuickItem *item);
95 static bool compareVariantList(const QVariantList &testList, QVariant object);
99 void static_types_data();
101 void static_i18n_data();
102 void static_nestedElements();
103 void static_nestedElements_data();
106 void dynamic_worker_data();
107 void dynamic_worker();
108 void dynamic_worker_sync_data();
109 void dynamic_worker_sync();
119 void get_worker_data();
121 void get_nested_data();
122 void crash_model_with_multiple_roles();
123 void set_model_cache();
124 void property_changes();
125 void property_changes_data();
126 void property_changes_worker();
127 void property_changes_worker_data();
130 void signal_handlers_data();
131 void signal_handlers();
132 void worker_sync_data();
134 void worker_remove_element_data();
135 void worker_remove_element();
136 void worker_remove_list_data();
137 void worker_remove_list();
138 void role_mode_data();
141 void dynamic_role_data();
144 bool tst_qdeclarativelistmodel::compareVariantList(const QVariantList &testList, QVariant object)
148 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel *>(object.value<QObject *>());
152 if (model->count() != testList.count())
155 for (int i=0 ; i < testList.count() ; ++i) {
156 const QVariant &testVariant = testList.at(i);
157 if (testVariant.type() != QVariant::Map)
159 const QVariantMap &map = testVariant.toMap();
161 const QList<int> &roles = model->roles();
163 QVariantMap::const_iterator it = map.begin();
164 QVariantMap::const_iterator end = map.end();
167 const QString &testKey = it.key();
168 const QVariant &testData = it.value();
171 for (int j=0 ; j < roles.count() ; ++j) {
172 if (model->toString(roles[j]).compare(testKey) == 0) {
181 const QVariant &modelData = model->data(i, roleIndex);
183 if (testData.type() == QVariant::List) {
184 const QVariantList &subList = testData.toList();
185 allOk = allOk && compareVariantList(subList, modelData);
187 allOk = allOk && (testData == modelData);
197 int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
199 QList<int> roles = model->roles();
200 for (int i=0; i<roles.count(); i++) {
201 if (model->toString(roles[i]) == roleName)
207 QQuickItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model)
209 QQuickItem *item = qobject_cast<QQuickItem*>(component->create());
210 QDeclarativeEngine::setContextForObject(model, eng->rootContext());
212 item->setProperty("model", qVariantFromValue(model));
216 void tst_qdeclarativelistmodel::waitForWorker(QQuickItem *item)
220 timer.setSingleShot(true);
221 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
223 QDeclarativeProperty prop(item, "done");
224 QVERIFY(prop.isValid());
225 QVERIFY(prop.connectNotifySignal(&loop, SLOT(quit())));
228 QVERIFY(timer.isActive());
231 void tst_qdeclarativelistmodel::static_types_data()
233 QTest::addColumn<QString>("qml");
234 QTest::addColumn<QVariant>("value");
235 QTest::addColumn<QString>("error");
237 QTest::newRow("string")
238 << "ListElement { foo: \"bar\" }"
239 << QVariant(QString("bar"))
242 QTest::newRow("real")
243 << "ListElement { foo: 10.5 }"
247 QTest::newRow("real0")
248 << "ListElement { foo: 0 }"
249 << QVariant(double(0))
252 QTest::newRow("bool")
253 << "ListElement { foo: false }"
257 QTest::newRow("bool")
258 << "ListElement { foo: true }"
262 QTest::newRow("enum")
263 << "ListElement { foo: Text.AlignHCenter }"
264 << QVariant(double(QQuickText::AlignHCenter))
267 QTest::newRow("Qt enum")
268 << "ListElement { foo: Qt.AlignBottom }"
269 << QVariant(double(Qt::AlignBottom))
272 QTest::newRow("role error")
273 << "ListElement { foo: 1 } ListElement { foo: 'string' }"
275 << QString("<Unknown File>: Can't assign to existing role 'foo' of different type [String -> Number]");
277 QTest::newRow("list type error")
278 << "ListElement { foo: 1 } ListElement { foo: ListElement { bar: 1 } }"
280 << QString("<Unknown File>: Can't assign to existing role 'foo' of different type [List -> Number]");
283 void tst_qdeclarativelistmodel::static_types()
285 QFETCH(QString, qml);
286 QFETCH(QVariant, value);
287 QFETCH(QString, error);
289 qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
291 if (!error.isEmpty()) {
292 QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
295 QDeclarativeEngine engine;
296 QDeclarativeComponent component(&engine);
297 component.setData(qml.toUtf8(),
298 QUrl::fromLocalFile(QString("dummy.qml")));
300 QVERIFY(!component.isError());
302 QObject *obj = component.create();
305 if (error.isEmpty()) {
306 QVariant actual = obj->property("test");
308 QCOMPARE(actual, value);
309 QCOMPARE(actual.toString(), value.toString());
315 void tst_qdeclarativelistmodel::static_i18n_data()
317 QTest::addColumn<QString>("qml");
318 QTest::addColumn<QVariant>("value");
319 QTest::addColumn<QString>("error");
321 QTest::newRow("QT_TR_NOOP")
322 << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"na\303\257ve\") }")
323 << QVariant(QString::fromUtf8("na\303\257ve"))
326 QTest::newRow("QT_TRANSLATE_NOOP")
327 << "ListElement { foo: QT_TRANSLATE_NOOP(\"MyListModel\", \"hello\") }"
328 << QVariant(QString("hello"))
331 QTest::newRow("QT_TRID_NOOP")
332 << QString::fromUtf8("ListElement { foo: QT_TRID_NOOP(\"qtn_1st_text\") }")
333 << QVariant(QString("qtn_1st_text"))
336 QTest::newRow("QT_TR_NOOP extra param")
337 << QString::fromUtf8("ListElement { foo: QT_TR_NOOP(\"hello\",\"world\") }")
338 << QVariant(QString())
339 << QString("ListElement: improperly specified QT_TR_NOOP");
341 QTest::newRow("QT_TRANSLATE_NOOP missing params")
342 << "ListElement { foo: QT_TRANSLATE_NOOP() }"
343 << QVariant(QString())
344 << QString("ListElement: improperly specified QT_TRANSLATE_NOOP");
346 QTest::newRow("QT_TRID_NOOP missing param")
347 << QString::fromUtf8("ListElement { foo: QT_TRID_NOOP() }")
348 << QVariant(QString())
349 << QString("ListElement: improperly specified QT_TRID_NOOP");
352 void tst_qdeclarativelistmodel::static_i18n()
354 QFETCH(QString, qml);
355 QFETCH(QVariant, value);
356 QFETCH(QString, error);
358 qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
360 QDeclarativeEngine engine;
361 QDeclarativeComponent component(&engine);
362 component.setData(qml.toUtf8(),
363 QUrl::fromLocalFile(QString("dummy.qml")));
365 if (!error.isEmpty()) {
366 QVERIFY(component.isError());
367 QCOMPARE(component.errors().at(0).description(), error);
371 QVERIFY(!component.isError());
373 QObject *obj = component.create();
376 QVariant actual = obj->property("test");
378 QCOMPARE(actual, value);
379 QCOMPARE(actual.toString(), value.toString());
384 void tst_qdeclarativelistmodel::static_nestedElements()
386 QFETCH(int, elementCount);
388 QStringList elements;
389 for (int i=0; i<elementCount; i++)
390 elements.append("ListElement { a: 1; b: 2 }");
391 QString elementsStr = elements.join(",\n") + "\n";
393 QString componentStr =
394 "import QtQuick 2.0\n"
396 " property variant count: model.get(0).attributes.count\n"
401 componentStr += elementsStr.toUtf8().constData();
408 QDeclarativeEngine engine;
409 QDeclarativeComponent component(&engine);
410 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
412 QObject *obj = component.create();
415 QVariant count = obj->property("count");
416 QCOMPARE(count.type(), QVariant::Int);
417 QCOMPARE(count.toInt(), elementCount);
422 void tst_qdeclarativelistmodel::static_nestedElements_data()
424 QTest::addColumn<int>("elementCount");
426 QTest::newRow("0 items") << 0;
427 QTest::newRow("1 item") << 1;
428 QTest::newRow("2 items") << 2;
429 QTest::newRow("many items") << 5;
432 void tst_qdeclarativelistmodel::dynamic_data()
434 QTest::addColumn<QString>("script");
435 QTest::addColumn<int>("result");
436 QTest::addColumn<QString>("warning");
437 QTest::addColumn<bool>("dynamicRoles");
439 for (int i=0 ; i < 2 ; ++i) {
443 QTest::newRow("count") << "count" << 0 << "" << dr;
445 QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "" << dr;
446 QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "" << dr;
447 QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "" << dr;
448 QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "" << dr;
449 QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "" << dr;
450 QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "" << dr;
452 QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "" << dr;
453 QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "" << dr;
454 QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "" << dr;
455 QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "" << dr;
456 QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
457 QTest::newRow("append4b") << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "" << dr;
458 QTest::newRow("append4c") << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "" << dr;
460 QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "" << dr;
461 QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "" << dr;
462 QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "" << dr;
464 QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "" << dr;
465 QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "" << dr;
466 QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "" << dr;
467 QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "" << dr;
468 QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "" << dr;
469 QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [-1 - 0] out of range [0 - 1]" << dr;
470 QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr;
471 QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: indices [0 - 1] out of range [0 - 0]" << dr;
472 QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [1 - 2] out of range [0 - 1]" << dr;
473 QTest::newRow("remove5a") << "{append({'foo':123});append({'foo':456});remove(0,2);count}" << 0 << "" << dr;
474 QTest::newRow("remove5b") << "{append({'foo':123});append({'foo':456});remove(0,1);count}" << 1 << "" << dr;
475 QTest::newRow("remove5c") << "{append({'foo':123});append({'foo':456});remove(1,1);count}" << 1 << "" << dr;
476 QTest::newRow("remove5d") << "{append({'foo':123});append({'foo':456});remove(0,1);get(0).foo}" << 456 << "" << dr;
477 QTest::newRow("remove5e") << "{append({'foo':123});append({'foo':456});remove(1,1);get(0).foo}" << 123 << "" << dr;
478 QTest::newRow("remove5f") << "{append({'foo':123});append({'foo':456});append({'foo':789});remove(0,1);remove(1,1);get(0).foo}" << 456 << "" << dr;
479 QTest::newRow("remove6a") << "{remove();count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr;
480 QTest::newRow("remove6b") << "{remove(1,2,3);count}" << 0 << "<Unknown File>: QML ListModel: remove: incorrect number of arguments" << dr;
481 QTest::newRow("remove7a") << "{append({'foo':123});remove(0,0);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - 0] out of range [0 - 1]" << dr;
482 QTest::newRow("remove7b") << "{append({'foo':123});remove(0,-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: indices [0 - -1] out of range [0 - 1]" << dr;
484 QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "" << dr;
485 QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range" << dr;
486 QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "" << dr;
487 QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "" << dr;
488 QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "" << dr;
489 QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "" << dr;
490 QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "" << dr;
491 QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range" << dr;
492 QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
493 QTest::newRow("insert5b") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "" << dr;
494 QTest::newRow("insert5c") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "" << dr;
496 QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "" << dr;
497 QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "" << dr;
498 QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "" << dr;
499 QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "" << dr;
500 QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "" << dr;
501 QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr;
502 QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object" << dr;
503 QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object" << dr;
504 QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "" << dr;
506 QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "" << dr;
507 QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "" << dr;
508 QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "" << dr;
509 QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "" << dr;
510 QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range" << dr;
511 QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range" << dr;
512 QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range" << dr;
513 QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "" << dr;
515 QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "" << dr;
516 QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "" << dr;
517 QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "" << dr;
518 QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "" << dr;
519 QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "" << dr;
520 QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "" << dr;
521 QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "" << dr;
522 QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "" << dr;
523 QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "" << dr;
524 QTest::newRow("move3a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr;
525 QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr;
526 QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr;
527 QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range" << dr;
529 QTest::newRow("large1") << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "" << dr;
531 QTest::newRow("datatypes1") << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [String -> Number]" << dr;
533 QTest::newRow("null") << "{append({'a':null});}" << 0 << "" << dr;
534 QTest::newRow("setNull") << "{append({'a':1});set(0, {'a':null});}" << 0 << "" << dr;
535 QTest::newRow("setString") << "{append({'a':'hello'});set(0, {'a':'world'});get(0).a == 'world'}" << 1 << "" << dr;
536 QTest::newRow("setInt") << "{append({'a':5});set(0, {'a':10});get(0).a}" << 10 << "" << dr;
537 QTest::newRow("setNumber") << "{append({'a':6});set(0, {'a':5.5});get(0).a < 5.6}" << 1 << "" << dr;
538 QTest::newRow("badType0") << "{append({'a':'hello'});set(0, {'a':1});}" << 0 << "<Unknown File>: Can't assign to existing role 'a' of different type [Number -> String]" << dr;
539 QTest::newRow("invalidInsert0") << "{insert(0);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
540 QTest::newRow("invalidAppend0") << "{append();}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
541 QTest::newRow("invalidInsert1") << "{insert(0, 34);}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object" << dr;
542 QTest::newRow("invalidAppend1") << "{append(37);}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object" << dr;
545 QTest::newRow("qobject0") << "{append({'a':dummyItem0});}" << 0 << "" << dr;
546 QTest::newRow("qobject1") << "{append({'a':dummyItem0});set(0,{'a':dummyItem1});get(0).a == dummyItem1;}" << 1 << "" << dr;
547 QTest::newRow("qobject2") << "{append({'a':dummyItem0});get(0).a == dummyItem0;}" << 1 << "" << dr;
548 QTest::newRow("qobject3") << "{append({'a':dummyItem0});append({'b':1});}" << 0 << "" << dr;
551 QTest::newRow("js1") << "{append({'foo':{'prop':1}});count}" << 1 << "" << dr;
552 QTest::newRow("js2") << "{append({'foo':{'prop':27}});get(0).foo.prop}" << 27 << "" << dr;
553 QTest::newRow("js3") << "{append({'foo':{'prop':27}});append({'bar':1});count}" << 2 << "" << dr;
554 QTest::newRow("js4") << "{append({'foo':{'prop':27}});append({'bar':1});set(0, {'foo':{'prop':28}});get(0).foo.prop}" << 28 << "" << dr;
555 QTest::newRow("js5") << "{append({'foo':{'prop':27}});append({'bar':1});set(1, {'foo':{'prop':33}});get(1).foo.prop}" << 33 << "" << dr;
556 QTest::newRow("js6") << "{append({'foo':{'prop':27}});clear();count}" << 0 << "" << dr;
557 QTest::newRow("js7") << "{append({'foo':{'prop':27}});set(0, {'foo':null});count}" << 1 << "" << dr;
558 QTest::newRow("js8") << "{append({'foo':{'prop':27}});set(0, {'foo':{'prop2':31}});get(0).foo.prop2}" << 31 << "" << dr;
561 QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "" << dr;
562 QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "" << dr;
563 QTest::newRow("nested-append3") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.append({'a':4});get(0).bars.get(3).a}" << 4 << "" << dr;
565 QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "" << dr;
566 QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "" << dr;
568 QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "" << dr;
569 QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "" << dr;
573 void tst_qdeclarativelistmodel::dynamic()
575 QFETCH(QString, script);
577 QFETCH(QString, warning);
578 QFETCH(bool, dynamicRoles);
580 QQuickItem dummyItem0, dummyItem1;
581 QDeclarativeEngine engine;
582 QDeclarativeListModel model;
583 model.setDynamicRoles(dynamicRoles);
584 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
585 engine.rootContext()->setContextObject(&model);
586 engine.rootContext()->setContextProperty("dummyItem0", QVariant::fromValue(&dummyItem0));
587 engine.rootContext()->setContextProperty("dummyItem1", QVariant::fromValue(&dummyItem1));
588 QDeclarativeExpression e(engine.rootContext(), &model, script);
589 if (isValidErrorMessage(warning, dynamicRoles))
590 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
592 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
594 int actual = e.evaluate().toInt();
596 qDebug() << e.error(); // errors not expected
598 QCOMPARE(actual,result);
600 if (model.count() > 0)
601 QVERIFY(spyCount.count() > 0);
604 void tst_qdeclarativelistmodel::dynamic_worker_data()
609 void tst_qdeclarativelistmodel::dynamic_worker()
611 QFETCH(QString, script);
613 QFETCH(QString, warning);
614 QFETCH(bool, dynamicRoles);
616 if (QByteArray(QTest::currentDataTag()).startsWith("qobject"))
619 // This is same as dynamic() except it applies the test to a ListModel called
620 // from a WorkerScript.
622 QDeclarativeListModel model;
623 model.setDynamicRoles(dynamicRoles);
624 QDeclarativeEngine eng;
625 QDeclarativeComponent component(&eng, testFileUrl("model.qml"));
626 QQuickItem *item = createWorkerTest(&eng, &component, &model);
629 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
631 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
632 script = script.mid(1, script.length() - 2);
633 QVariantList operations;
634 foreach (const QString &s, script.split(';')) {
639 if (isValidErrorMessage(warning, dynamicRoles))
640 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
642 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
643 Q_ARG(QVariant, operations)));
645 QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
647 if (model.count() > 0)
648 QVERIFY(spyCount.count() > 0);
651 qApp->processEvents();
654 void tst_qdeclarativelistmodel::dynamic_worker_sync_data()
659 void tst_qdeclarativelistmodel::dynamic_worker_sync()
661 QFETCH(QString, script);
663 QFETCH(QString, warning);
664 QFETCH(bool, dynamicRoles);
666 if (QByteArray(QTest::currentDataTag()).startsWith("qobject"))
669 // This is the same as dynamic_worker() except that it executes a set of list operations
670 // from the worker script, calls sync(), and tests the changes are reflected in the
671 // list in the main thread
673 QDeclarativeListModel model;
674 model.setDynamicRoles(dynamicRoles);
675 QDeclarativeEngine eng;
676 QDeclarativeComponent component(&eng, testFileUrl("model.qml"));
677 QQuickItem *item = createWorkerTest(&eng, &component, &model);
680 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
681 script = script.mid(1, script.length() - 2);
682 QVariantList operations;
683 foreach (const QString &s, script.split(';')) {
688 if (isValidErrorMessage(warning, dynamicRoles))
689 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
691 // execute a set of commands on the worker list model, then check the
692 // changes are reflected in the list model in the main thread
693 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
694 Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
697 QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
698 QCOMPARE(e.evaluate().toInt(), result);
701 qApp->processEvents();
704 void tst_qdeclarativelistmodel::enumerate()
706 QDeclarativeEngine eng;
707 QDeclarativeComponent component(&eng, testFileUrl("enumerate.qml"));
708 QVERIFY(!component.isError());
709 QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
712 QLatin1String expectedStrings[] = {
713 QLatin1String("val1=1Y"),
714 QLatin1String("val2=2Y"),
715 QLatin1String("val3=strY"),
716 QLatin1String("val4=falseN"),
717 QLatin1String("val5=trueY")
720 int expectedStringCount = sizeof(expectedStrings) / sizeof(expectedStrings[0]);
722 QStringList r = item->property("result").toString().split(":");
725 for (int i=0 ; i < expectedStringCount ; ++i) {
726 const QLatin1String &expectedString = expectedStrings[i];
728 QStringList::const_iterator it = r.begin();
729 QStringList::const_iterator end = r.end();
732 if (it->compare(expectedString) == 0) {
740 QVERIFY(matchCount == expectedStringCount);
745 void tst_qdeclarativelistmodel::error_data()
747 QTest::addColumn<QString>("qml");
748 QTest::addColumn<QString>("error");
750 QTest::newRow("id not allowed in ListElement")
751 << "import QtQuick 2.0\nListModel { ListElement { id: fred } }"
752 << "ListElement: cannot use reserved \"id\" property";
754 QTest::newRow("id allowed in ListModel")
755 << "import QtQuick 2.0\nListModel { id:model }"
758 QTest::newRow("random properties not allowed in ListModel")
759 << "import QtQuick 2.0\nListModel { foo:123 }"
760 << "ListModel: undefined property 'foo'";
762 QTest::newRow("random properties allowed in ListElement")
763 << "import QtQuick 2.0\nListModel { ListElement { foo:123 } }"
766 QTest::newRow("bindings not allowed in ListElement")
767 << "import QtQuick 2.0\nRectangle { id: rect; ListModel { ListElement { foo: rect.color } } }"
768 << "ListElement: cannot use script for property value";
770 QTest::newRow("random object list properties allowed in ListElement")
771 << "import QtQuick 2.0\nListModel { ListElement { foo: [ ListElement { bar: 123 } ] } }"
774 QTest::newRow("default properties not allowed in ListElement")
775 << "import QtQuick 2.0\nListModel { ListElement { Item { } } }"
776 << "ListElement: cannot contain nested elements";
778 QTest::newRow("QML elements not allowed in ListElement")
779 << "import QtQuick 2.0\nListModel { ListElement { a: Item { } } }"
780 << "ListElement: cannot contain nested elements";
782 QTest::newRow("qualified ListElement supported")
783 << "import QtQuick 2.0 as Foo\nFoo.ListModel { Foo.ListElement { a: 123 } }"
786 QTest::newRow("qualified ListElement required")
787 << "import QtQuick 2.0 as Foo\nFoo.ListModel { ListElement { a: 123 } }"
788 << "ListElement is not a type";
790 QTest::newRow("unknown qualified ListElement not allowed")
791 << "import QtQuick 2.0\nListModel { Foo.ListElement { a: 123 } }"
792 << "Foo.ListElement - Foo is not a namespace";
795 void tst_qdeclarativelistmodel::error()
797 QFETCH(QString, qml);
798 QFETCH(QString, error);
800 QDeclarativeEngine engine;
801 QDeclarativeComponent component(&engine);
802 component.setData(qml.toUtf8(),
803 QUrl::fromLocalFile(QString("dummy.qml")));
804 if (error.isEmpty()) {
805 QVERIFY(!component.isError());
807 QVERIFY(component.isError());
808 QList<QDeclarativeError> errors = component.errors();
809 QCOMPARE(errors.count(),1);
810 QCOMPARE(errors.at(0).description(),error);
814 void tst_qdeclarativelistmodel::syncError()
816 QString qml = "import QtQuick 2.0\nListModel { id: lm; Component.onCompleted: lm.sync() }";
817 QString error = "file:dummy.qml:2:1: QML ListModel: List sync() can only be called from a WorkerScript";
819 QDeclarativeEngine engine;
820 QDeclarativeComponent component(&engine);
821 component.setData(qml.toUtf8(),
822 QUrl::fromLocalFile(QString("dummy.qml")));
823 QTest::ignoreMessage(QtWarningMsg,error.toUtf8());
824 QObject *obj = component.create();
830 Test model changes from set() are available to the view
832 void tst_qdeclarativelistmodel::set_data()
834 QTest::addColumn<bool>("dynamicRoles");
836 QTest::newRow("staticRoles") << false;
837 QTest::newRow("dynamicRoles") << true;
840 void tst_qdeclarativelistmodel::set()
842 QFETCH(bool, dynamicRoles);
844 QDeclarativeEngine engine;
845 QDeclarativeListModel model;
846 model.setDynamicRoles(dynamicRoles);
847 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
848 engine.rootContext()->setContextProperty("model", &model);
850 RUNEXPR("model.append({test:false})");
851 RUNEXPR("model.set(0, {test:true})");
853 QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache
854 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
856 RUNEXPR("model.set(0, {test:false})");
857 QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
858 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
860 QString warning = QString::fromLatin1("<Unknown File>: Can't create role for unsupported data type");
861 if (isValidErrorMessage(warning, dynamicRoles))
862 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
863 QVariant invalidData = QColor();
864 model.setProperty(0, "test", invalidData);
868 Test model changes on values returned by get() are available to the view
870 void tst_qdeclarativelistmodel::get()
872 QFETCH(QString, expression);
874 QFETCH(QString, roleName);
875 QFETCH(QVariant, roleValue);
876 QFETCH(bool, dynamicRoles);
878 QDeclarativeEngine engine;
879 QDeclarativeComponent component(&engine);
881 "import QtQuick 2.0\n"
882 "ListModel {}\n", QUrl());
883 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
884 model->setDynamicRoles(dynamicRoles);
885 engine.rootContext()->setContextProperty("model", model);
887 RUNEXPR("model.append({roleA: 100})");
888 RUNEXPR("model.append({roleA: 200, roleB: 400})");
889 RUNEXPR("model.append({roleA: 200, roleB: 400})");
890 RUNEXPR("model.append({roleC: {} })");
891 RUNEXPR("model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
893 QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
894 QDeclarativeExpression expr(engine.rootContext(), model, expression);
896 QVERIFY(!expr.hasError());
898 int role = roleFromName(model, roleName);
901 if (roleValue.type() == QVariant::List) {
902 const QVariantList &list = roleValue.toList();
903 QVERIFY(compareVariantList(list, model->data(index, role)));
905 QCOMPARE(model->data(index, role), roleValue);
908 QCOMPARE(spy.count(), 1);
910 QList<QVariant> spyResult = spy.takeFirst();
911 QCOMPARE(spyResult.at(0).toInt(), index);
912 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
913 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
918 void tst_qdeclarativelistmodel::get_data()
920 QTest::addColumn<QString>("expression");
921 QTest::addColumn<int>("index");
922 QTest::addColumn<QString>("roleName");
923 QTest::addColumn<QVariant>("roleValue");
924 QTest::addColumn<bool>("dynamicRoles");
926 for (int i=0 ; i < 2 ; ++i) {
929 QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500) << dr;
930 QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500) << dr;
934 map.clear(); map["a"] = 50; map["b"] = 500;
936 map.clear(); map["c"] = 1000;
938 QTest::newRow("list of objects") << "get(2).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list) << dr;
942 void tst_qdeclarativelistmodel::get_worker()
944 QFETCH(QString, expression);
946 QFETCH(QString, roleName);
947 QFETCH(QVariant, roleValue);
948 QFETCH(bool, dynamicRoles);
950 QDeclarativeListModel model;
951 model.setDynamicRoles(dynamicRoles);
952 QDeclarativeEngine eng;
953 QDeclarativeComponent component(&eng, testFileUrl("model.qml"));
954 QQuickItem *item = createWorkerTest(&eng, &component, &model);
957 // Add some values like get() test
958 RUNEVAL(item, "model.append({roleA: 100})");
959 RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
960 RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
961 RUNEVAL(item, "model.append({roleC: {} })");
962 RUNEVAL(item, "model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
964 int role = roleFromName(&model, roleName);
967 QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
969 // in the worker thread, change the model data and call sync()
970 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
971 Q_ARG(QVariant, QStringList(expression))));
974 // see if we receive the model changes in the main thread's model
975 if (roleValue.type() == QVariant::List) {
976 const QVariantList &list = roleValue.toList();
977 QVERIFY(compareVariantList(list, model.data(index, role)));
979 QCOMPARE(model.data(index, role), roleValue);
982 QCOMPARE(spy.count(), 1);
984 QList<QVariant> spyResult = spy.takeFirst();
985 QCOMPARE(spyResult.at(0).toInt(), index);
986 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
987 QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
990 void tst_qdeclarativelistmodel::get_worker_data()
996 Test that the tests run in get() also work for nested list data
998 void tst_qdeclarativelistmodel::get_nested()
1000 QFETCH(QString, expression);
1002 QFETCH(QString, roleName);
1003 QFETCH(QVariant, roleValue);
1004 QFETCH(bool, dynamicRoles);
1006 if (roleValue.type() == QVariant::Map)
1009 QDeclarativeEngine engine;
1010 QDeclarativeComponent component(&engine);
1012 "import QtQuick 2.0\n"
1013 "ListModel {}", QUrl());
1014 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
1015 model->setDynamicRoles(dynamicRoles);
1016 QVERIFY(component.errorString().isEmpty());
1017 QDeclarativeListModel *childModel;
1018 engine.rootContext()->setContextProperty("model", model);
1020 RUNEXPR("model.append({ listRoleA: [\n"
1022 "{ roleA: 200, roleB: 400 },\n"
1023 "{ roleA: 200, roleB: 400 }, \n"
1025 "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1028 RUNEXPR("model.append({ listRoleA: [\n"
1030 "{ roleA: 200, roleB: 400 },\n"
1031 "{ roleA: 200, roleB: 400 }, \n"
1033 "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1037 "{ roleA: 200, roleB: 400 },\n"
1038 "{ roleA: 200, roleB: 400 }, \n"
1040 "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1044 "{ roleA: 200, roleB: 400 },\n"
1045 "{ roleA: 200, roleB: 400 }, \n"
1047 "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
1050 // Test setting the inner list data for:
1056 QList<QPair<int, QString> > testData;
1057 testData << qMakePair(0, QString("listRoleA"));
1058 testData << qMakePair(1, QString("listRoleA"));
1059 testData << qMakePair(1, QString("listRoleB"));
1060 testData << qMakePair(1, QString("listRoleC"));
1062 for (int i=0; i<testData.count(); i++) {
1063 int outerListIndex = testData[i].first;
1064 QString outerListRoleName = testData[i].second;
1065 int outerListRole = roleFromName(model, outerListRoleName);
1066 QVERIFY(outerListRole >= 0);
1068 childModel = qobject_cast<QDeclarativeListModel*>(model->data(outerListIndex, outerListRole).value<QObject*>());
1069 QVERIFY(childModel);
1071 QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
1072 QDeclarativeExpression expr(engine.rootContext(), model, extendedExpression);
1074 QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
1076 QVERIFY(!expr.hasError());
1078 int role = roleFromName(childModel, roleName);
1080 if (roleValue.type() == QVariant::List) {
1081 QVERIFY(compareVariantList(roleValue.toList(), childModel->data(index, role)));
1083 QCOMPARE(childModel->data(index, role), roleValue);
1085 QCOMPARE(spy.count(), 1);
1087 QList<QVariant> spyResult = spy.takeFirst();
1088 QCOMPARE(spyResult.at(0).toInt(), index);
1089 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
1090 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
1096 void tst_qdeclarativelistmodel::get_nested_data()
1102 void tst_qdeclarativelistmodel::crash_model_with_multiple_roles()
1104 QDeclarativeEngine eng;
1105 QDeclarativeComponent component(&eng, testFileUrl("multipleroles.qml"));
1106 QObject *rootItem = component.create();
1107 QVERIFY(component.errorString().isEmpty());
1108 QVERIFY(rootItem != 0);
1109 QDeclarativeListModel *model = rootItem->findChild<QDeclarativeListModel*>("listModel");
1110 QVERIFY(model != 0);
1112 // used to cause a crash in QDeclarativeVisualDataModel
1113 model->setProperty(0, "black", true);
1119 void tst_qdeclarativelistmodel::set_model_cache()
1121 QDeclarativeEngine eng;
1122 QDeclarativeComponent component(&eng, testFileUrl("setmodelcachelist.qml"));
1123 QObject *model = component.create();
1124 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
1125 QVERIFY(model != 0);
1126 QVERIFY(model->property("ok").toBool());
1131 void tst_qdeclarativelistmodel::property_changes()
1133 QFETCH(QString, script_setup);
1134 QFETCH(QString, script_change);
1135 QFETCH(QString, roleName);
1136 QFETCH(int, listIndex);
1137 QFETCH(bool, itemsChanged);
1138 QFETCH(QString, testExpression);
1139 QFETCH(bool, dynamicRoles);
1141 QDeclarativeEngine engine;
1142 QDeclarativeListModel model;
1143 model.setDynamicRoles(dynamicRoles);
1144 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1145 engine.rootContext()->setContextObject(&model);
1147 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1149 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1151 QString signalHandler = "on" + QString(roleName[0].toUpper()) + roleName.mid(1, roleName.length()) + "Changed:";
1152 QString qml = "import QtQuick 2.0\n"
1154 "property bool gotSignal: false\n"
1155 "target: model.get(" + QString::number(listIndex) + ")\n"
1156 + signalHandler + " gotSignal = true\n"
1159 QDeclarativeComponent component(&engine);
1160 component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
1161 engine.rootContext()->setContextProperty("model", &model);
1162 QObject *connectionsObject = component.create();
1163 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
1165 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1167 expr.setExpression(script_change);
1169 QVERIFY2(!expr.hasError(), QTest::toString(expr.error()));
1171 // test the object returned by get() emits the correct signals
1172 QCOMPARE(connectionsObject->property("gotSignal").toBool(), itemsChanged);
1174 // test itemsChanged() is emitted correctly
1176 QCOMPARE(spyItemsChanged.count(), 1);
1177 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1178 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1180 QCOMPARE(spyItemsChanged.count(), 0);
1183 expr.setExpression(testExpression);
1184 QCOMPARE(expr.evaluate().toBool(), true);
1186 delete connectionsObject;
1189 void tst_qdeclarativelistmodel::property_changes_data()
1191 QTest::addColumn<QString>("script_setup");
1192 QTest::addColumn<QString>("script_change");
1193 QTest::addColumn<QString>("roleName");
1194 QTest::addColumn<int>("listIndex");
1195 QTest::addColumn<bool>("itemsChanged");
1196 QTest::addColumn<QString>("testExpression");
1197 QTest::addColumn<bool>("dynamicRoles");
1199 for (int i=0 ; i < 2 ; ++i) {
1202 QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
1203 << "b" << 0 << true << "get(0).b == 123" << dr;
1204 QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
1205 << "b" << 0 << true << "get(0).b == 123" << dr;
1207 QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
1208 << "b" << 0 << false << "get(0).b == 456" << dr;
1209 QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
1210 << "b" << 0 << false << "get(0).b == 456" << dr;
1212 QTest::newRow("set: inserted item")
1213 << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
1214 << "set(1, {'a':456});"
1215 << "a" << 1 << true << "get(1).a == 456" << dr;
1216 QTest::newRow("setProperty: inserted item")
1217 << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
1218 << "setProperty(1, 'a', 456);"
1219 << "a" << 1 << true << "get(1).a == 456" << dr;
1220 QTest::newRow("get: inserted item")
1221 << "{append({'a':123, 'b':456, 'c':789}); get(0); insert(0, {'a':0, 'b':0, 'c':0});}"
1222 << "get(1).a = 456;"
1223 << "a" << 1 << true << "get(1).a == 456" << dr;
1224 QTest::newRow("set: removed item")
1225 << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
1226 << "set(0, {'a':456});"
1227 << "a" << 0 << true << "get(0).a == 456" << dr;
1228 QTest::newRow("setProperty: removed item")
1229 << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
1230 << "setProperty(0, 'a', 456);"
1231 << "a" << 0 << true << "get(0).a == 456" << dr;
1232 QTest::newRow("get: removed item")
1233 << "{append({'a':0, 'b':0, 'c':0}); append({'a':123, 'b':456, 'c':789}); get(1); remove(0);}"
1234 << "get(0).a = 456;"
1235 << "a" << 0 << true << "get(0).a == 456" << dr;
1237 // Following tests only call set() since setProperty() only allows plain
1238 // values, not lists, as the argument.
1239 // Note that when a list is changed, itemsChanged() is currently always
1240 // emitted regardless of whether it actually changed or not.
1242 QTest::newRow("nested-set: list, new size") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});"
1243 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2" << dr;
1245 QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1246 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr;
1248 QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
1249 << "b" << 0 << true << "get(0).b.count == 0" << dr;
1251 QTest::newRow("nested-set: list, same size, different values") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':222},{'a':3}]});"
1252 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3" << dr;
1254 QTest::newRow("nested-set: list, no changes") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1255 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3" << dr;
1257 QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
1258 << "b" << 0 << true << "get(0).b.count == 0" << dr;
1262 void tst_qdeclarativelistmodel::property_changes_worker()
1264 QFETCH(QString, script_setup);
1265 QFETCH(QString, script_change);
1266 QFETCH(QString, roleName);
1267 QFETCH(int, listIndex);
1268 QFETCH(bool, itemsChanged);
1269 QFETCH(bool, dynamicRoles);
1271 QDeclarativeListModel model;
1272 model.setDynamicRoles(dynamicRoles);
1273 QDeclarativeEngine engine;
1274 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
1275 QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
1276 QQuickItem *item = createWorkerTest(&engine, &component, &model);
1279 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1281 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1283 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1285 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1286 Q_ARG(QVariant, QStringList(script_change))));
1287 waitForWorker(item);
1289 // test itemsChanged() is emitted correctly
1291 QCOMPARE(spyItemsChanged.count(), 1);
1292 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1293 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1295 QCOMPARE(spyItemsChanged.count(), 0);
1299 qApp->processEvents();
1302 void tst_qdeclarativelistmodel::property_changes_worker_data()
1304 property_changes_data();
1307 void tst_qdeclarativelistmodel::clear_data()
1309 QTest::addColumn<bool>("dynamicRoles");
1311 QTest::newRow("staticRoles") << false;
1312 QTest::newRow("dynamicRoles") << true;
1315 void tst_qdeclarativelistmodel::clear()
1317 QFETCH(bool, dynamicRoles);
1319 QDeclarativeEngine engine;
1320 QDeclarativeListModel model;
1321 model.setDynamicRoles(dynamicRoles);
1322 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1323 engine.rootContext()->setContextProperty("model", &model);
1326 QCOMPARE(model.count(), 0);
1328 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1329 QCOMPARE(model.count(), 1);
1332 QCOMPARE(model.count(), 0);
1334 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1335 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\"})");
1336 QCOMPARE(model.count(), 2);
1339 QCOMPARE(model.count(), 0);
1341 // clearing does not remove the roles
1342 RUNEXPR("model.append({propertyA: \"value a\", propertyB: \"value b\", propertyC: \"value c\"})");
1343 QList<int> roles = model.roles();
1345 QCOMPARE(model.count(), 0);
1346 QCOMPARE(model.roles(), roles);
1347 QCOMPARE(model.toString(roles[0]), QString("propertyA"));
1348 QCOMPARE(model.toString(roles[1]), QString("propertyB"));
1349 QCOMPARE(model.toString(roles[2]), QString("propertyC"));
1352 void tst_qdeclarativelistmodel::signal_handlers_data()
1354 QTest::addColumn<bool>("dynamicRoles");
1356 QTest::newRow("staticRoles") << false;
1357 QTest::newRow("dynamicRoles") << true;
1360 void tst_qdeclarativelistmodel::signal_handlers()
1362 QFETCH(bool, dynamicRoles);
1364 QDeclarativeEngine eng;
1365 QDeclarativeComponent component(&eng, testFileUrl("signalhandlers.qml"));
1366 QObject *model = component.create();
1367 QDeclarativeListModel *lm = qobject_cast<QDeclarativeListModel *>(model);
1369 lm->setDynamicRoles(dynamicRoles);
1370 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
1371 QVERIFY(model != 0);
1372 QVERIFY(model->property("ok").toBool());
1377 void tst_qdeclarativelistmodel::worker_sync_data()
1379 QTest::addColumn<bool>("dynamicRoles");
1381 QTest::newRow("staticRoles") << false;
1382 QTest::newRow("dynamicRoles") << true;
1385 void tst_qdeclarativelistmodel::worker_sync()
1387 QFETCH(bool, dynamicRoles);
1389 QDeclarativeListModel model;
1390 model.setDynamicRoles(dynamicRoles);
1391 QDeclarativeEngine eng;
1392 QDeclarativeComponent component(&eng, testFileUrl("workersync.qml"));
1393 QQuickItem *item = createWorkerTest(&eng, &component, &model);
1396 QVERIFY(model.count() == 0);
1398 QVERIFY(QMetaObject::invokeMethod(item, "addItem0"));
1400 QVERIFY(model.count() == 2);
1401 QVariant childData = model.data(0, 0);
1402 QDeclarativeListModel *childModel = qobject_cast<QDeclarativeListModel *>(childData.value<QObject *>());
1403 QVERIFY(childModel);
1404 QVERIFY(childModel->count() == 1);
1406 QSignalSpy spyModelInserted(&model, SIGNAL(itemsInserted(int,int)));
1407 QSignalSpy spyChildInserted(childModel, SIGNAL(itemsInserted(int,int)));
1409 QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
1410 waitForWorker(item);
1412 QVERIFY(model.count() == 2);
1413 QVERIFY(childModel->count() == 1);
1414 QVERIFY(spyModelInserted.count() == 0);
1415 QVERIFY(spyChildInserted.count() == 0);
1417 QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1418 waitForWorker(item);
1420 QVERIFY(model.count() == 2);
1421 QVERIFY(childModel->count() == 2);
1422 QVERIFY(spyModelInserted.count() == 0);
1423 QVERIFY(spyChildInserted.count() == 1);
1425 QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
1426 waitForWorker(item);
1428 QVERIFY(model.count() == 2);
1429 QVERIFY(childModel->count() == 2);
1430 QVERIFY(spyModelInserted.count() == 0);
1431 QVERIFY(spyChildInserted.count() == 1);
1433 QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1434 waitForWorker(item);
1436 QVERIFY(model.count() == 2);
1437 QVERIFY(childModel->count() == 3);
1438 QVERIFY(spyModelInserted.count() == 0);
1439 QVERIFY(spyChildInserted.count() == 2);
1442 qApp->processEvents();
1445 void tst_qdeclarativelistmodel::worker_remove_element_data()
1450 void tst_qdeclarativelistmodel::worker_remove_element()
1452 QFETCH(bool, dynamicRoles);
1454 QDeclarativeListModel model;
1455 model.setDynamicRoles(dynamicRoles);
1456 QDeclarativeEngine eng;
1457 QDeclarativeComponent component(&eng, testFileUrl("workerremoveelement.qml"));
1458 QQuickItem *item = createWorkerTest(&eng, &component, &model);
1461 QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
1463 QVERIFY(model.count() == 0);
1464 QVERIFY(spyModelRemoved.count() == 0);
1466 QVERIFY(QMetaObject::invokeMethod(item, "addItem"));
1468 QVERIFY(model.count() == 1);
1470 QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker"));
1471 waitForWorker(item);
1473 QVERIFY(model.count() == 1);
1474 QVERIFY(spyModelRemoved.count() == 0);
1476 QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1477 waitForWorker(item);
1479 QVERIFY(model.count() == 0);
1480 QVERIFY(spyModelRemoved.count() == 1);
1483 qApp->processEvents();
1486 //don't crash if model was deleted earlier
1487 QDeclarativeListModel* model = new QDeclarativeListModel;
1488 model->setDynamicRoles(dynamicRoles);
1489 QDeclarativeEngine eng;
1490 QDeclarativeComponent component(&eng, testFileUrl("workerremoveelement.qml"));
1491 QQuickItem *item = createWorkerTest(&eng, &component, model);
1494 QVERIFY(QMetaObject::invokeMethod(item, "addItem"));
1496 QVERIFY(model->count() == 1);
1498 QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker"));
1499 QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1501 qApp->processEvents(); //must not crash here
1502 waitForWorker(item);
1508 void tst_qdeclarativelistmodel::worker_remove_list_data()
1513 void tst_qdeclarativelistmodel::worker_remove_list()
1515 QFETCH(bool, dynamicRoles);
1517 QDeclarativeListModel model;
1518 model.setDynamicRoles(dynamicRoles);
1519 QDeclarativeEngine eng;
1520 QDeclarativeComponent component(&eng, testFileUrl("workerremovelist.qml"));
1521 QQuickItem *item = createWorkerTest(&eng, &component, &model);
1524 QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
1526 QVERIFY(model.count() == 0);
1527 QVERIFY(spyModelRemoved.count() == 0);
1529 QVERIFY(QMetaObject::invokeMethod(item, "addList"));
1531 QVERIFY(model.count() == 1);
1533 QVERIFY(QMetaObject::invokeMethod(item, "removeListViaWorker"));
1534 waitForWorker(item);
1536 QVERIFY(model.count() == 1);
1537 QVERIFY(spyModelRemoved.count() == 0);
1539 QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
1540 waitForWorker(item);
1542 QVERIFY(model.count() == 0);
1543 QVERIFY(spyModelRemoved.count() == 1);
1546 qApp->processEvents();
1549 void tst_qdeclarativelistmodel::role_mode_data()
1551 QTest::addColumn<QString>("script");
1552 QTest::addColumn<int>("result");
1553 QTest::addColumn<QString>("warning");
1555 QTest::newRow("default0") << "{dynamicRoles}" << 0 << "";
1556 QTest::newRow("default1") << "{append({'a':1});dynamicRoles}" << 0 << "";
1558 QTest::newRow("enableDynamic0") << "{dynamicRoles=true;dynamicRoles}" << 1 << "";
1559 QTest::newRow("enableDynamic1") << "{append({'a':1});dynamicRoles=true;dynamicRoles}" << 0 << "<Unknown File>: QML ListModel: unable to enable dynamic roles as this model is not empty!";
1560 QTest::newRow("enableDynamic2") << "{dynamicRoles=true;append({'a':1});dynamicRoles=false;dynamicRoles}" << 1 << "<Unknown File>: QML ListModel: unable to enable static roles as this model is not empty!";
1563 void tst_qdeclarativelistmodel::role_mode()
1565 QFETCH(QString, script);
1566 QFETCH(int, result);
1567 QFETCH(QString, warning);
1569 QDeclarativeEngine engine;
1570 QDeclarativeListModel model;
1571 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
1572 engine.rootContext()->setContextObject(&model);
1573 QDeclarativeExpression e(engine.rootContext(), &model, script);
1574 if (!warning.isEmpty())
1575 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
1577 int actual = e.evaluate().toInt();
1579 qDebug() << e.error(); // errors not expected
1581 QCOMPARE(actual,result);
1584 void tst_qdeclarativelistmodel::dynamic_role_data()
1586 QTest::addColumn<QString>("preamble");
1587 QTest::addColumn<QString>("script");
1588 QTest::addColumn<int>("result");
1590 QTest::newRow("sync1") << "{append({'a':[{'b':1},{'b':2}]})}" << "{get(0).a = 'string';count}" << 1;
1593 void tst_qdeclarativelistmodel::dynamic_role()
1595 QFETCH(QString, preamble);
1596 QFETCH(QString, script);
1597 QFETCH(int, result);
1599 QDeclarativeListModel model;
1600 model.setDynamicRoles(true);
1601 QDeclarativeEngine engine;
1602 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
1603 QQuickItem *item = createWorkerTest(&engine, &component, &model);
1606 QDeclarativeExpression preExp(engine.rootContext(), &model, preamble);
1607 QCOMPARE(preExp.evaluate().toInt(), 0);
1609 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
1610 script = script.mid(1, script.length() - 2);
1611 QVariantList operations;
1612 foreach (const QString &s, script.split(';')) {
1617 // execute a set of commands on the worker list model, then check the
1618 // changes are reflected in the list model in the main thread
1619 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1620 Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
1621 waitForWorker(item);
1623 QDeclarativeExpression e(engine.rootContext(), &model, operations.last().toString());
1624 QCOMPARE(e.evaluate().toInt(), result);
1627 qApp->processEvents();
1630 QTEST_MAIN(tst_qdeclarativelistmodel)
1632 #include "tst_qdeclarativelistmodel.moc"