1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
42 #include <QtDeclarative/private/qdeclarativeitem_p.h>
43 #include <QtDeclarative/private/qdeclarativetext_p.h>
44 #include <QtDeclarative/private/qdeclarativeengine_p.h>
45 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
46 #include <QtDeclarative/private/qdeclarativeexpression_p.h>
47 #include <QDeclarativeComponent>
49 #include <QtCore/qtimer.h>
50 #include <QtCore/qdebug.h>
51 #include <QtCore/qtranslator.h>
54 #include "../../../shared/util.h"
57 // In Symbian OS test data is located in applications private dir
61 Q_DECLARE_METATYPE(QList<int>)
62 Q_DECLARE_METATYPE(QList<QVariantHash>)
64 class tst_qdeclarativelistmodel : public QObject
68 tst_qdeclarativelistmodel() {}
71 int roleFromName(const QDeclarativeListModel *model, const QString &roleName);
72 QScriptValue nestedListValue(QScriptEngine *eng) const;
73 QDeclarativeItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
74 void waitForWorker(QDeclarativeItem *item);
78 void static_types_data();
80 void static_nestedElements();
81 void static_nestedElements_data();
84 void dynamic_worker_data();
85 void dynamic_worker();
86 void dynamic_worker_sync_data();
87 void dynamic_worker_sync();
88 void convertNestedToFlat_fail();
89 void convertNestedToFlat_fail_data();
90 void convertNestedToFlat_ok();
91 void convertNestedToFlat_ok_data();
100 void get_worker_data();
102 void get_nested_data();
103 void crash_model_with_multiple_roles();
104 void set_model_cache();
105 void property_changes();
106 void property_changes_data();
107 void property_changes_worker();
108 void property_changes_worker_data();
111 int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
113 QList<int> roles = model->roles();
114 for (int i=0; i<roles.count(); i++) {
115 if (model->toString(roles[i]) == roleName)
122 QScriptValue tst_qdeclarativelistmodel::nestedListValue(QScriptEngine *eng) const
124 QScriptValue list = eng->newArray();
125 list.setProperty(0, eng->newObject());
126 list.setProperty(1, eng->newObject());
127 QScriptValue sv = eng->newObject();
128 sv.setProperty("foo", list);
132 QDeclarativeItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model)
134 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(component->create());
135 QDeclarativeEngine::setContextForObject(model, eng->rootContext());
137 item->setProperty("model", qVariantFromValue(model));
141 void tst_qdeclarativelistmodel::waitForWorker(QDeclarativeItem *item)
145 timer.setSingleShot(true);
146 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
148 QDeclarativeProperty prop(item, "done");
149 QVERIFY(prop.isValid());
150 QVERIFY(prop.connectNotifySignal(&loop, SLOT(quit())));
153 QVERIFY(timer.isActive());
156 void tst_qdeclarativelistmodel::static_i18n()
158 QString expect = QString::fromUtf8("na\303\257ve");
160 QString componentStr = "import QtQuick 1.0\nListModel { ListElement { prop1: \""+expect+"\"; prop2: QT_TR_NOOP(\""+expect+"\") } }";
161 QDeclarativeEngine engine;
162 QDeclarativeComponent component(&engine);
163 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
164 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
166 QString prop1 = obj->get(0).property(QLatin1String("prop1")).toString();
167 QCOMPARE(prop1,expect);
168 QString prop2 = obj->get(0).property(QLatin1String("prop2")).toString();
169 QCOMPARE(prop2,expect); // (no, not translated, QT_TR_NOOP is a no-op)
173 void tst_qdeclarativelistmodel::static_nestedElements()
175 QFETCH(int, elementCount);
177 QStringList elements;
178 for (int i=0; i<elementCount; i++)
179 elements.append("ListElement { a: 1; b: 2 }");
180 QString elementsStr = elements.join(",\n") + "\n";
182 QString componentStr =
183 "import QtQuick 1.0\n"
187 componentStr += elementsStr.toUtf8().constData();
193 QDeclarativeEngine engine;
194 QDeclarativeComponent component(&engine);
195 component.setData(componentStr.toUtf8(), QUrl::fromLocalFile(""));
197 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
200 QScriptValue prop = obj->get(0).property(QLatin1String("attributes")).property(QLatin1String("count"));
201 QVERIFY(prop.isNumber());
202 QCOMPARE(prop.toInt32(), qint32(elementCount));
207 void tst_qdeclarativelistmodel::static_nestedElements_data()
209 QTest::addColumn<int>("elementCount");
211 QTest::newRow("0 items") << 0;
212 QTest::newRow("1 item") << 1;
213 QTest::newRow("2 items") << 2;
214 QTest::newRow("many items") << 5;
217 void tst_qdeclarativelistmodel::dynamic_data()
219 QTest::addColumn<QString>("script");
220 QTest::addColumn<int>("result");
221 QTest::addColumn<QString>("warning");
225 QTest::newRow("count") << "count" << 0 << "";
227 QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
228 QTest::newRow("get2") << "{get(-1) === undefined}" << 1 << "";
229 QTest::newRow("get3") << "{append({'foo':123});get(0) != undefined}" << 1 << "";
230 QTest::newRow("get4") << "{append({'foo':123});get(0).foo}" << 123 << "";
232 QTest::newRow("get-modify1") << "{append({'foo':123,'bar':456});get(0).foo = 333;get(0).foo}" << 333 << "";
233 QTest::newRow("get-modify2") << "{append({'z':1});append({'foo':123,'bar':456});get(1).bar = 999;get(1).bar}" << 999 << "";
235 QTest::newRow("append1") << "{append({'foo':123});count}" << 1 << "";
236 QTest::newRow("append2") << "{append({'foo':123,'bar':456});count}" << 1 << "";
237 QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
238 QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
239 QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
240 QTest::newRow("append4b") << "{append([1,2,3])}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
242 QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "";
243 QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "";
244 QTest::newRow("clear3") << "{append({'foo':123});clear()}" << 0 << "";
246 QTest::newRow("remove1") << "{append({'foo':123});remove(0);count}" << 0 << "";
247 QTest::newRow("remove2a") << "{append({'foo':123});append({'foo':456});remove(0);count}" << 1 << "";
248 QTest::newRow("remove2b") << "{append({'foo':123});append({'foo':456});remove(0);get(0).foo}" << 456 << "";
249 QTest::newRow("remove2c") << "{append({'foo':123});append({'foo':456});remove(1);get(0).foo}" << 123 << "";
250 QTest::newRow("remove3") << "{append({'foo':123});remove(0)}" << 0 << "";
251 QTest::newRow("remove3a") << "{append({'foo':123});remove(-1);count}" << 1 << "<Unknown File>: QML ListModel: remove: index -1 out of range";
252 QTest::newRow("remove4a") << "{remove(0)}" << 0 << "<Unknown File>: QML ListModel: remove: index 0 out of range";
253 QTest::newRow("remove4b") << "{append({'foo':123});remove(0);remove(0);count}" << 0 << "<Unknown File>: QML ListModel: remove: index 0 out of range";
254 QTest::newRow("remove4c") << "{append({'foo':123});remove(1);count}" << 1 << "<Unknown File>: QML ListModel: remove: index 1 out of range";
256 QTest::newRow("insert1") << "{insert(0,{'foo':123});count}" << 1 << "";
257 QTest::newRow("insert2") << "{insert(1,{'foo':123});count}" << 0 << "<Unknown File>: QML ListModel: insert: index 1 out of range";
258 QTest::newRow("insert3a") << "{append({'foo':123});insert(1,{'foo':456});count}" << 2 << "";
259 QTest::newRow("insert3b") << "{append({'foo':123});insert(1,{'foo':456});get(0).foo}" << 123 << "";
260 QTest::newRow("insert3c") << "{append({'foo':123});insert(1,{'foo':456});get(1).foo}" << 456 << "";
261 QTest::newRow("insert3d") << "{append({'foo':123});insert(0,{'foo':456});get(0).foo}" << 456 << "";
262 QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "";
263 QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range";
264 QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
265 QTest::newRow("insert5b") << "{insert(0,[1,2,3])}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
267 QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "";
268 QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "";
269 QTest::newRow("set3a") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).foo}" << 999 << "";
270 QTest::newRow("set3b") << "{append({'foo':123,'bar':456});set(0,{'foo':999});get(0).bar}" << 456 << "";
271 QTest::newRow("set4a") << "{set(0,{'foo':456});count}" << 1 << "";
272 QTest::newRow("set4c") << "{set(-1,{'foo':456})}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
273 QTest::newRow("set5a") << "{append({'foo':123,'bar':456});set(0,123);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
274 QTest::newRow("set5b") << "{append({'foo':123,'bar':456});set(0,[1,2,3]);count}" << 1 << "<Unknown File>: QML ListModel: set: value is not an object";
275 QTest::newRow("set6") << "{append({'foo':123});set(1,{'foo':456});count}" << 2 << "";
277 QTest::newRow("setprop1") << "{append({'foo':123});setProperty(0,'foo',456);count}" << 1 << "";
278 QTest::newRow("setprop2") << "{append({'foo':123});setProperty(0,'foo',456);get(0).foo}" << 456 << "";
279 QTest::newRow("setprop3a") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).foo}" << 999 << "";
280 QTest::newRow("setprop3b") << "{append({'foo':123,'bar':456});setProperty(0,'foo',999);get(0).bar}" << 456 << "";
281 QTest::newRow("setprop4a") << "{setProperty(0,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index 0 out of range";
282 QTest::newRow("setprop4b") << "{setProperty(-1,'foo',456)}" << 0 << "<Unknown File>: QML ListModel: set: index -1 out of range";
283 QTest::newRow("setprop4c") << "{append({'foo':123,'bar':456});setProperty(1,'foo',456);count}" << 1 << "<Unknown File>: QML ListModel: set: index 1 out of range";
284 QTest::newRow("setprop5") << "{append({'foo':123,'bar':456});append({'foo':111});setProperty(1,'bar',222);get(1).bar}" << 222 << "";
286 QTest::newRow("move1a") << "{append({'foo':123});append({'foo':456});move(0,1,1);count}" << 2 << "";
287 QTest::newRow("move1b") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(0).foo}" << 456 << "";
288 QTest::newRow("move1c") << "{append({'foo':123});append({'foo':456});move(0,1,1);get(1).foo}" << 123 << "";
289 QTest::newRow("move1d") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(0).foo}" << 456 << "";
290 QTest::newRow("move1e") << "{append({'foo':123});append({'foo':456});move(1,0,1);get(1).foo}" << 123 << "";
291 QTest::newRow("move2a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);count}" << 3 << "";
292 QTest::newRow("move2b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(0).foo}" << 789 << "";
293 QTest::newRow("move2c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(1).foo}" << 123 << "";
294 QTest::newRow("move2d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,1,2);get(2).foo}" << 456 << "";
295 QTest::newRow("move3a") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,3);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
296 QTest::newRow("move3b") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,-1,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
297 QTest::newRow("move3c") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(1,0,-1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
298 QTest::newRow("move3d") << "{append({'foo':123});append({'foo':456});append({'foo':789});move(0,3,1);count}" << 3 << "<Unknown File>: QML ListModel: move: out of range";
302 QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
303 QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";
304 QTest::newRow("nested-append3") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.append({'a':4});get(0).bars.get(3).a}" << 4 << "";
306 QTest::newRow("nested-insert") << "{append({'foo':123});insert(0,{'bars':[{'a':1},{'b':2},{'c':3}]});get(0).bars.get(0).a}" << 1 << "";
307 QTest::newRow("nested-set") << "{append({'foo':[{'x':1}]});set(0,{'foo':[{'x':123}]});get(0).foo.get(0).x}" << 123 << "";
309 QTest::newRow("nested-count") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.count}" << 3 << "";
310 QTest::newRow("nested-clear") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]}); get(0).bars.clear(); get(0).bars.count}" << 0 << "";
313 void tst_qdeclarativelistmodel::dynamic()
315 QFETCH(QString, script);
317 QFETCH(QString, warning);
319 QDeclarativeEngine engine;
320 QDeclarativeListModel model;
321 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
322 engine.rootContext()->setContextObject(&model);
323 QDeclarativeExpression e(engine.rootContext(), &model, script);
324 if (!warning.isEmpty())
325 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
327 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
329 int actual = e.evaluate().toInt();
331 qDebug() << e.error(); // errors not expected
333 QCOMPARE(actual,result);
335 if (model.count() > 0)
336 QVERIFY(spyCount.count() > 0);
339 void tst_qdeclarativelistmodel::dynamic_worker_data()
344 void tst_qdeclarativelistmodel::dynamic_worker()
346 QFETCH(QString, script);
348 QFETCH(QString, warning);
350 if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
353 // This is same as dynamic() except it applies the test to a ListModel called
354 // from a WorkerScript (i.e. testing the internal FlatListModel that is created
355 // by the WorkerListModelAgent)
357 QDeclarativeListModel model;
358 QDeclarativeEngine eng;
359 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
360 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
363 QSignalSpy spyCount(&model, SIGNAL(countChanged()));
365 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
366 script = script.mid(1, script.length() - 2);
367 QVariantList operations;
368 foreach (const QString &s, script.split(';')) {
373 if (!warning.isEmpty())
374 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
376 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
377 Q_ARG(QVariant, operations)));
379 QCOMPARE(QDeclarativeProperty(item, "result").read().toInt(), result);
381 if (model.count() > 0)
382 QVERIFY(spyCount.count() > 0);
385 qApp->processEvents();
390 void tst_qdeclarativelistmodel::dynamic_worker_sync_data()
395 void tst_qdeclarativelistmodel::dynamic_worker_sync()
397 QFETCH(QString, script);
399 QFETCH(QString, warning);
401 // This is the same as dynamic_worker() except that it executes a set of list operations
402 // from the worker script, calls sync(), and tests the changes are reflected in the
403 // list in the main thread
405 QDeclarativeListModel model;
406 QDeclarativeEngine eng;
407 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
408 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
411 if (script[0] == QLatin1Char('{') && script[script.length()-1] == QLatin1Char('}'))
412 script = script.mid(1, script.length() - 2);
413 QVariantList operations;
414 foreach (const QString &s, script.split(';')) {
419 if (!warning.isEmpty())
420 QTest::ignoreMessage(QtWarningMsg, warning.toLatin1());
422 // execute a set of commands on the worker list model, then check the
423 // changes are reflected in the list model in the main thread
424 if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
425 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
427 if (QByteArray(QTest::currentDataTag()).startsWith("nested-set"))
428 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
430 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
431 Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
434 QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
435 if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
436 QCOMPARE(e.evaluate().toInt(), result);
439 qApp->processEvents();
442 void tst_qdeclarativelistmodel::convertNestedToFlat_fail()
444 // If a model has nested data, it cannot be used at all from a worker script
446 QFETCH(QString, script);
448 QDeclarativeListModel model;
449 QDeclarativeEngine eng;
450 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
451 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
455 QScriptValue plainData = s_eng.newObject();
456 plainData.setProperty("foo", QScriptValue(123));
457 model.append(plainData);
458 model.append(nestedListValue(&s_eng));
459 QCOMPARE(model.count(), 2);
461 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: List contains list-type data and cannot be used from a worker script");
462 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
465 QCOMPARE(model.count(), 2);
468 qApp->processEvents();
471 void tst_qdeclarativelistmodel::convertNestedToFlat_fail_data()
473 QTest::addColumn<QString>("script");
475 QTest::newRow("clear") << "clear()";
476 QTest::newRow("remove") << "remove(0)";
477 QTest::newRow("append") << "append({'x':1})";
478 QTest::newRow("insert") << "insert(0, {'x':1})";
479 QTest::newRow("set") << "set(0, {'foo':1})";
480 QTest::newRow("setProperty") << "setProperty(0, 'foo', 1})";
481 QTest::newRow("move") << "move(0, 1, 1})";
482 QTest::newRow("get") << "get(0)";
485 void tst_qdeclarativelistmodel::convertNestedToFlat_ok()
487 // If a model only has plain data, it can be modified from a worker script. However,
488 // once the model is used from a worker script, it no longer accepts nested data
490 QFETCH(QString, script);
492 QDeclarativeListModel model;
493 QDeclarativeEngine eng;
494 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
495 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
499 QScriptValue plainData = s_eng.newObject();
500 plainData.setProperty("foo", QScriptValue(123));
501 model.append(plainData);
502 QCOMPARE(model.count(), 1);
504 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
507 // can still add plain data
508 int count = model.count();
509 model.append(plainData);
510 QCOMPARE(model.count(), count+1);
512 QScriptValue nested = nestedListValue(&s_eng);
513 const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
515 QTest::ignoreMessage(QtWarningMsg, warning);
516 model.append(nested);
518 QTest::ignoreMessage(QtWarningMsg, warning);
519 model.insert(0, nested);
521 QTest::ignoreMessage(QtWarningMsg, warning);
522 model.set(0, nested);
524 QCOMPARE(model.count(), count+1);
527 qApp->processEvents();
530 void tst_qdeclarativelistmodel::convertNestedToFlat_ok_data()
532 convertNestedToFlat_fail_data();
535 void tst_qdeclarativelistmodel::static_types_data()
537 QTest::addColumn<QString>("qml");
538 QTest::addColumn<QVariant>("value");
540 QTest::newRow("string")
541 << "ListElement { foo: \"bar\" }"
542 << QVariant(QString("bar"));
544 QTest::newRow("real")
545 << "ListElement { foo: 10.5 }"
548 QTest::newRow("real0")
549 << "ListElement { foo: 0 }"
550 << QVariant(double(0));
552 QTest::newRow("bool")
553 << "ListElement { foo: false }"
556 QTest::newRow("bool")
557 << "ListElement { foo: true }"
560 QTest::newRow("enum")
561 << "ListElement { foo: Text.AlignHCenter }"
562 << QVariant(double(QDeclarativeText::AlignHCenter));
565 void tst_qdeclarativelistmodel::static_types()
567 QFETCH(QString, qml);
568 QFETCH(QVariant, value);
570 qml = "import QtQuick 1.0\nListModel { " + qml + " }";
572 QDeclarativeEngine engine;
573 QDeclarativeComponent component(&engine);
574 component.setData(qml.toUtf8(),
575 QUrl::fromLocalFile(QString("dummy.qml")));
577 if (value.toString().startsWith("QTBUG-"))
578 QEXPECT_FAIL("",value.toString().toLatin1(),Abort);
580 QVERIFY(!component.isError());
582 QDeclarativeListModel *obj = qobject_cast<QDeclarativeListModel*>(component.create());
585 QScriptValue actual = obj->get(0).property(QLatin1String("foo"));
587 QCOMPARE(actual.isString(), value.type() == QVariant::String);
588 QCOMPARE(actual.isBoolean(), value.type() == QVariant::Bool);
589 QCOMPARE(actual.isNumber(), value.type() == QVariant::Double);
591 QCOMPARE(actual.toString(), value.toString());
596 void tst_qdeclarativelistmodel::enumerate()
598 QDeclarativeEngine eng;
599 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
600 QVERIFY(!component.isError());
601 QDeclarativeItem *item = qobject_cast<QDeclarativeItem*>(component.create());
603 QStringList r = item->property("result").toString().split(":");
604 QCOMPARE(r[0],QLatin1String("val1=1Y"));
605 QCOMPARE(r[1],QLatin1String("val2=2Y"));
606 QCOMPARE(r[2],QLatin1String("val3=strY"));
607 QCOMPARE(r[3],QLatin1String("val4=falseN"));
608 QCOMPARE(r[4],QLatin1String("val5=trueY"));
613 void tst_qdeclarativelistmodel::error_data()
615 QTest::addColumn<QString>("qml");
616 QTest::addColumn<QString>("error");
618 QTest::newRow("id not allowed in ListElement")
619 << "import QtQuick 1.0\nListModel { ListElement { id: fred } }"
620 << "ListElement: cannot use reserved \"id\" property";
622 QTest::newRow("id allowed in ListModel")
623 << "import QtQuick 1.0\nListModel { id:model }"
626 QTest::newRow("random properties not allowed in ListModel")
627 << "import QtQuick 1.0\nListModel { foo:123 }"
628 << "ListModel: undefined property 'foo'";
630 QTest::newRow("random properties allowed in ListElement")
631 << "import QtQuick 1.0\nListModel { ListElement { foo:123 } }"
634 QTest::newRow("bindings not allowed in ListElement")
635 << "import QtQuick 1.0\nRectangle { id: rect; ListModel { ListElement { foo: rect.color } } }"
636 << "ListElement: cannot use script for property value";
638 QTest::newRow("random object list properties allowed in ListElement")
639 << "import QtQuick 1.0\nListModel { ListElement { foo: [ ListElement { bar: 123 } ] } }"
642 QTest::newRow("default properties not allowed in ListElement")
643 << "import QtQuick 1.0\nListModel { ListElement { Item { } } }"
644 << "ListElement: cannot contain nested elements";
646 QTest::newRow("QML elements not allowed in ListElement")
647 << "import QtQuick 1.0\nListModel { ListElement { a: Item { } } }"
648 << "ListElement: cannot contain nested elements";
650 QTest::newRow("qualified ListElement supported")
651 << "import QtQuick 1.0 as Foo\nFoo.ListModel { Foo.ListElement { a: 123 } }"
654 QTest::newRow("qualified ListElement required")
655 << "import QtQuick 1.0 as Foo\nFoo.ListModel { ListElement { a: 123 } }"
656 << "ListElement is not a type";
658 QTest::newRow("unknown qualified ListElement not allowed")
659 << "import QtQuick 1.0\nListModel { Foo.ListElement { a: 123 } }"
660 << "Foo.ListElement - Foo is not a namespace";
663 void tst_qdeclarativelistmodel::error()
665 QFETCH(QString, qml);
666 QFETCH(QString, error);
668 QDeclarativeEngine engine;
669 QDeclarativeComponent component(&engine);
670 component.setData(qml.toUtf8(),
671 QUrl::fromLocalFile(QString("dummy.qml")));
672 if (error.isEmpty()) {
673 QVERIFY(!component.isError());
675 QVERIFY(component.isError());
676 QList<QDeclarativeError> errors = component.errors();
677 QCOMPARE(errors.count(),1);
678 QCOMPARE(errors.at(0).description(),error);
682 void tst_qdeclarativelistmodel::syncError()
684 QString qml = "import QtQuick 1.0\nListModel { id: lm; Component.onCompleted: lm.sync() }";
685 QString error = "file:dummy.qml:2:1: QML ListModel: List sync() can only be called from a WorkerScript";
687 QDeclarativeEngine engine;
688 QDeclarativeComponent component(&engine);
689 component.setData(qml.toUtf8(),
690 QUrl::fromLocalFile(QString("dummy.qml")));
691 QTest::ignoreMessage(QtWarningMsg,error.toUtf8());
692 QObject *obj = component.create();
698 Test model changes from set() are available to the view
700 void tst_qdeclarativelistmodel::set()
702 QDeclarativeEngine engine;
703 QDeclarativeListModel model;
704 QDeclarativeEngine::setContextForObject(&model,engine.rootContext());
705 engine.rootContext()->setContextObject(&model);
706 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&engine);
708 QScriptValue sv = seng->newObject();
709 sv.setProperty("test", QScriptValue(false));
712 sv.setProperty("test", QScriptValue(true));
714 QCOMPARE(model.get(0).property("test").toBool(), true); // triggers creation of model cache
715 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
717 sv.setProperty("test", QScriptValue(false));
719 QCOMPARE(model.get(0).property("test").toBool(), false); // tests model cache is updated
720 QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
724 Test model changes on values returned by get() are available to the view
726 void tst_qdeclarativelistmodel::get()
728 QFETCH(QString, expression);
730 QFETCH(QString, roleName);
731 QFETCH(QVariant, roleValue);
733 QDeclarativeEngine eng;
734 QDeclarativeComponent component(&eng);
736 "import QtQuick 1.0\n"
738 "ListElement { roleA: 100 }\n"
739 "ListElement { roleA: 200; roleB: 400 } \n"
740 "ListElement { roleA: 200; roleB: 400 } \n"
742 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
743 int role = roleFromName(model, roleName);
745 QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
746 QDeclarativeExpression expr(eng.rootContext(), model, expression);
748 QVERIFY(!expr.hasError());
750 QCOMPARE(model->data(index, role), roleValue);
751 QCOMPARE(spy.count(), 1);
753 QList<QVariant> spyResult = spy.takeFirst();
754 QCOMPARE(spyResult.at(0).toInt(), index);
755 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
756 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
759 void tst_qdeclarativelistmodel::get_data()
761 QTest::addColumn<QString>("expression");
762 QTest::addColumn<int>("index");
763 QTest::addColumn<QString>("roleName");
764 QTest::addColumn<QVariant>("roleValue");
766 QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
767 QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
771 QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
774 map.clear(); map["a"] = 50; map["b"] = 500;
776 map.clear(); map["c"] = 1000;
778 QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
781 void tst_qdeclarativelistmodel::get_worker()
783 QFETCH(QString, expression);
785 QFETCH(QString, roleName);
786 QFETCH(QVariant, roleValue);
788 QDeclarativeListModel model;
789 QDeclarativeEngine eng;
790 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
791 QDeclarativeItem *item = createWorkerTest(&eng, &component, &model);
793 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&eng);
795 // Add some values like get() test
796 QScriptValue sv = seng->newObject();
797 sv.setProperty(QLatin1String("roleA"), seng->newVariant(QVariant::fromValue(100)));
799 sv = seng->newObject();
800 sv.setProperty(QLatin1String("roleA"), seng->newVariant(QVariant::fromValue(200)));
801 sv.setProperty(QLatin1String("roleB"), seng->newVariant(QVariant::fromValue(400)));
804 int role = roleFromName(&model, roleName);
806 const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
807 if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
808 QTest::ignoreMessage(QtWarningMsg, warning);
809 QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
811 // in the worker thread, change the model data and call sync()
812 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
813 Q_ARG(QVariant, QStringList(expression))));
816 // see if we receive the model changes in the main thread's model
817 if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
818 QVERIFY(model.data(index, role) != roleValue);
819 QCOMPARE(spy.count(), 0);
821 QCOMPARE(model.data(index, role), roleValue);
822 QCOMPARE(spy.count(), 1);
824 QList<QVariant> spyResult = spy.takeFirst();
825 QCOMPARE(spyResult.at(0).toInt(), index);
826 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
827 QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
831 void tst_qdeclarativelistmodel::get_worker_data()
837 Test that the tests run in get() also work for nested list data
839 void tst_qdeclarativelistmodel::get_nested()
841 QFETCH(QString, expression);
843 QFETCH(QString, roleName);
844 QFETCH(QVariant, roleValue);
846 QDeclarativeEngine eng;
847 QDeclarativeComponent component(&eng);
849 "import QtQuick 1.0\n"
853 "ListElement { roleA: 100 },\n"
854 "ListElement { roleA: 200; roleB: 400 },\n"
855 "ListElement { roleA: 200; roleB: 400 } \n"
860 "ListElement { roleA: 100 },\n"
861 "ListElement { roleA: 200; roleB: 400 },\n"
862 "ListElement { roleA: 200; roleB: 400 } \n"
865 "ListElement { roleA: 100 },\n"
866 "ListElement { roleA: 200; roleB: 400 },\n"
867 "ListElement { roleA: 200; roleB: 400 } \n"
870 "ListElement { roleA: 100 },\n"
871 "ListElement { roleA: 200; roleB: 400 },\n"
872 "ListElement { roleA: 200; roleB: 400 } \n"
876 QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
877 QVERIFY(component.errorString().isEmpty());
878 QDeclarativeListModel *childModel;
880 // Test setting the inner list data for:
886 QList<QPair<int, QString> > testData;
887 testData << qMakePair(0, QString("listRoleA"));
888 testData << qMakePair(1, QString("listRoleA"));
889 testData << qMakePair(1, QString("listRoleB"));
890 testData << qMakePair(1, QString("listRoleC"));
892 for (int i=0; i<testData.count(); i++) {
893 int outerListIndex = testData[i].first;
894 QString outerListRoleName = testData[i].second;
895 int outerListRole = roleFromName(model, outerListRoleName);
897 childModel = qobject_cast<QDeclarativeListModel*>(model->data(outerListIndex, outerListRole).value<QObject*>());
900 QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
901 QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
903 QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
905 QVERIFY(!expr.hasError());
907 int role = roleFromName(childModel, roleName);
908 QCOMPARE(childModel->data(index, role), roleValue);
909 QCOMPARE(spy.count(), 1);
911 QList<QVariant> spyResult = spy.takeFirst();
912 QCOMPARE(spyResult.at(0).toInt(), index);
913 QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
914 QCOMPARE(spyResult.at(2).value<QList<int> >(), (QList<int>() << role));
918 void tst_qdeclarativelistmodel::get_nested_data()
924 void tst_qdeclarativelistmodel::crash_model_with_multiple_roles()
926 QDeclarativeEngine eng;
927 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/multipleroles.qml"));
928 QObject *rootItem = component.create();
929 QVERIFY(component.errorString().isEmpty());
930 QVERIFY(rootItem != 0);
931 QDeclarativeListModel *model = rootItem->findChild<QDeclarativeListModel*>("listModel");
934 // used to cause a crash in QDeclarativeVisualDataModel
935 model->setProperty(0, "black", true);
939 void tst_qdeclarativelistmodel::set_model_cache()
941 QDeclarativeEngine eng;
942 QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/setmodelcachelist.qml"));
943 QObject *model = component.create();
944 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
946 QVERIFY(model->property("ok").toBool());
949 void tst_qdeclarativelistmodel::property_changes()
951 QFETCH(QString, script_setup);
952 QFETCH(QString, script_change);
953 QFETCH(QString, roleName);
954 QFETCH(int, listIndex);
955 QFETCH(bool, itemsChanged);
956 QFETCH(QString, testExpression);
958 QDeclarativeEngine engine;
959 QDeclarativeListModel model;
960 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
961 engine.rootContext()->setContextObject(&model);
963 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
965 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
967 QString signalHandler = "on" + QString(roleName[0].toUpper()) + roleName.mid(1, roleName.length()) + "Changed:";
968 QString qml = "import QtQuick 1.0\n"
970 "property bool gotSignal: false\n"
971 "target: model.get(0)\n"
972 + signalHandler + " gotSignal = true\n"
974 QDeclarativeComponent component(&engine);
975 component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
976 engine.rootContext()->setContextProperty("model", &model);
977 QObject *connectionsObject = component.create();
978 QVERIFY2(component.errorString().isEmpty(), QTest::toString(component.errorString()));
980 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
982 expr.setExpression(script_change);
984 QVERIFY2(!expr.hasError(), QTest::toString(expr.error()));
986 // test the object returned by get() emits the correct signals
987 QCOMPARE(connectionsObject->property("gotSignal").toBool(), itemsChanged);
989 // test itemsChanged() is emitted correctly
991 QCOMPARE(spyItemsChanged.count(), 1);
992 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
993 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
995 QCOMPARE(spyItemsChanged.count(), 0);
998 expr.setExpression(testExpression);
999 QCOMPARE(expr.evaluate().toBool(), true);
1001 delete connectionsObject;
1004 void tst_qdeclarativelistmodel::property_changes_data()
1006 QTest::addColumn<QString>("script_setup");
1007 QTest::addColumn<QString>("script_change");
1008 QTest::addColumn<QString>("roleName");
1009 QTest::addColumn<int>("listIndex");
1010 QTest::addColumn<bool>("itemsChanged");
1011 QTest::addColumn<QString>("testExpression");
1013 QTest::newRow("set: plain") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':123});"
1014 << "b" << 0 << true << "get(0).b == 123";
1015 QTest::newRow("setProperty: plain") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 123);"
1016 << "b" << 0 << true << "get(0).b == 123";
1018 QTest::newRow("set: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "set(0,{'b':456});"
1019 << "b" << 0 << false << "get(0).b == 456";
1020 QTest::newRow("setProperty: plain, no changes") << "append({'a':123, 'b':456, 'c':789});" << "setProperty(0, 'b', 456);"
1021 << "b" << 0 << false << "get(0).b == 456";
1023 // Following tests only call set() since setProperty() only allows plain
1024 // values, not lists, as the argument.
1025 // Note that when a list is changed, itemsChanged() is currently always
1026 // emitted regardless of whether it actually changed or not.
1028 QTest::newRow("nested-set: list, new size") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2}]});"
1029 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2";
1031 QTest::newRow("nested-set: list, empty -> non-empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1032 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
1034 QTest::newRow("nested-set: list, non-empty -> empty") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[]});"
1035 << "b" << 0 << true << "get(0).b.count == 0";
1037 QTest::newRow("nested-set: list, same size, different values") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':222},{'a':3}]});"
1038 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 222 && get(0).b.get(2).a == 3";
1040 QTest::newRow("nested-set: list, no changes") << "append({'a':123, 'b':[{'a':1},{'a':2},{'a':3}], 'c':789});" << "set(0,{'b':[{'a':1},{'a':2},{'a':3}]});"
1041 << "b" << 0 << true << "get(0).b.get(0).a == 1 && get(0).b.get(1).a == 2 && get(0).b.get(2).a == 3";
1043 QTest::newRow("nested-set: list, no changes, empty") << "append({'a':123, 'b':[], 'c':789});" << "set(0,{'b':[]});"
1044 << "b" << 0 << true << "get(0).b.count == 0";
1048 void tst_qdeclarativelistmodel::property_changes_worker()
1050 // nested models are not supported when WorkerScript is involved
1051 if (QByteArray(QTest::currentDataTag()).startsWith("nested-"))
1054 QFETCH(QString, script_setup);
1055 QFETCH(QString, script_change);
1056 QFETCH(QString, roleName);
1057 QFETCH(int, listIndex);
1058 QFETCH(bool, itemsChanged);
1060 QDeclarativeListModel model;
1061 QDeclarativeEngine engine;
1062 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
1063 QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
1064 QDeclarativeItem *item = createWorkerTest(&engine, &component, &model);
1067 QDeclarativeExpression expr(engine.rootContext(), &model, script_setup);
1069 QVERIFY2(!expr.hasError(), QTest::toString(expr.error().toString()));
1071 QSignalSpy spyItemsChanged(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
1073 QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
1074 Q_ARG(QVariant, QStringList(script_change))));
1075 waitForWorker(item);
1077 // test itemsChanged() is emitted correctly
1079 QCOMPARE(spyItemsChanged.count(), 1);
1080 QCOMPARE(spyItemsChanged.at(0).at(0).toInt(), listIndex);
1081 QCOMPARE(spyItemsChanged.at(0).at(1).toInt(), 1);
1083 QCOMPARE(spyItemsChanged.count(), 0);
1087 qApp->processEvents();
1090 void tst_qdeclarativelistmodel::property_changes_worker_data()
1092 property_changes_data();
1095 void tst_qdeclarativelistmodel::clear()
1097 QDeclarativeEngine engine;
1098 QDeclarativeListModel model;
1099 QDeclarativeEngine::setContextForObject(&model, engine.rootContext());
1100 engine.rootContext()->setContextObject(&model);
1102 QScriptEngine *seng = QDeclarativeEnginePrivate::getScriptEngine(&engine);
1103 QScriptValue sv = seng->newObject();
1107 QCOMPARE(model.count(), 0);
1109 sv.setProperty("propertyA", "value a");
1110 sv.setProperty("propertyB", "value b");
1112 QCOMPARE(model.count(), 1);
1115 QCOMPARE(model.count(), 0);
1119 QCOMPARE(model.count(), 2);
1122 QCOMPARE(model.count(), 0);
1124 // clearing does not remove the roles
1125 sv.setProperty("propertyC", "value c");
1127 QList<int> roles = model.roles();
1129 QCOMPARE(model.count(), 0);
1130 QCOMPARE(model.roles(), roles);
1131 QCOMPARE(model.toString(roles[0]), QString("propertyA"));
1132 QCOMPARE(model.toString(roles[1]), QString("propertyB"));
1133 QCOMPARE(model.toString(roles[2]), QString("propertyC"));
1136 QTEST_MAIN(tst_qdeclarativelistmodel)
1138 #include "tst_qdeclarativelistmodel.moc"