aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorGlenn Watson <glenn.watson@nokia.com>2011-10-26 10:28:01 +1000
committerQt by Nokia <qt-info@nokia.com>2011-10-26 03:41:53 +0200
commitca3dfc52667a036901d640e4f9ed30f267153b2c (patch)
tree99c036580179b73e74e3e798cd91ff2b3fc5341d /tests
parent31d34d98f192035c42bd1d1c07bf6e297da76220 (diff)
Optimize listmodel and allow nested elements from worker script.
Added support for nested listmodels when used from a worker script thread. Optimized the implementation of ListModel, especially the performance of appending a large number of items. Added a batch append mode (with an array of JS objects) to reduce the overhead of calling from JS into native code for each append operation. Task-number:QTBUG-21508 Change-Id: I07b381dc3e8200d92d6e0af458df8850d78b510f Reviewed-by: Martin Jones <martin.jones@nokia.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js8
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml33
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js9
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml33
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workersync.js8
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml32
-rw-r--r--tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp560
7 files changed, 478 insertions, 205 deletions
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js
new file mode 100644
index 0000000000..cb9dfa66aa
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.js
@@ -0,0 +1,8 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.action == 'removeItem') {
+ msg.model.remove(0);
+ } else if (msg.action == 'dosync') {
+ msg.model.sync();
+ }
+ WorkerScript.sendMessage({'done': true})
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml
new file mode 100644
index 0000000000..e2361acf6b
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremoveelement.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Item {
+ id: item
+ property variant model
+ property bool done: false
+
+ WorkerScript {
+ id: worker
+ source: "workerremoveelement.js"
+ onMessage: {
+ item.done = true
+ }
+ }
+
+ function addItem() {
+ model.append({ 'data': 1 });
+
+ var element = model.get(0);
+ }
+
+ function removeItemViaWorker() {
+ done = false
+ var msg = { 'action': 'removeItem', 'model': model }
+ worker.sendMessage(msg);
+ }
+
+ function doSync() {
+ done = false
+ var msg = { 'action': 'dosync', 'model': model }
+ worker.sendMessage(msg);
+ }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js
new file mode 100644
index 0000000000..f63dd68839
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.js
@@ -0,0 +1,9 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.action == 'removeList') {
+ msg.model.remove(0);
+ } else if (msg.action == 'dosync') {
+ msg.model.sync();
+ }
+ WorkerScript.sendMessage({'done': true})
+}
+
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml
new file mode 100644
index 0000000000..bdb5e024d8
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workerremovelist.qml
@@ -0,0 +1,33 @@
+import QtQuick 2.0
+
+Item {
+ id: item
+ property variant model
+ property bool done: false
+
+ WorkerScript {
+ id: worker
+ source: "workerremovelist.js"
+ onMessage: {
+ item.done = true
+ }
+ }
+
+ function addList() {
+ model.append({ 'data': [ { 'subData': 1 } ] });
+
+ var element = model.get(0);
+ }
+
+ function removeListViaWorker() {
+ done = false
+ var msg = { 'action': 'removeList', 'model': model }
+ worker.sendMessage(msg);
+ }
+
+ function doSync() {
+ done = false
+ var msg = { 'action': 'dosync', 'model': model }
+ worker.sendMessage(msg);
+ }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js
new file mode 100644
index 0000000000..9b8d8fa7f3
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.js
@@ -0,0 +1,8 @@
+WorkerScript.onMessage = function(msg) {
+ if (msg.action == 'addItem') {
+ msg.model.get(0).level0.append({ 'level1': 33 });
+ } else if (msg.action == 'dosync') {
+ msg.model.sync();
+ }
+ WorkerScript.sendMessage({'done': true})
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml
new file mode 100644
index 0000000000..c21cd43e7e
--- /dev/null
+++ b/tests/auto/declarative/qdeclarativelistmodel/data/workersync.qml
@@ -0,0 +1,32 @@
+import QtQuick 2.0
+
+Item {
+ id: item
+ property variant model
+ property bool done: false
+
+ WorkerScript {
+ id: worker
+ source: "workersync.js"
+ onMessage: {
+ item.done = true
+ }
+ }
+
+ function addItem0() {
+ model.append({ 'level0': [ { 'level1': 29 } ] });
+ model.append({ 'level0': [ { 'level1': 37 } ] });
+ }
+
+ function addItemViaWorker() {
+ done = false
+ var msg = { 'action': 'addItem', 'model': model }
+ worker.sendMessage(msg);
+ }
+
+ function doSync() {
+ done = false
+ var msg = { 'action': 'dosync', 'model': model }
+ worker.sendMessage(msg);
+ }
+}
diff --git a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
index bc6c99102e..23e9fc4c07 100644
--- a/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
+++ b/tests/auto/declarative/qdeclarativelistmodel/tst_qdeclarativelistmodel.cpp
@@ -54,6 +54,17 @@
Q_DECLARE_METATYPE(QList<int>)
Q_DECLARE_METATYPE(QList<QVariantHash>)
+#define RUNEVAL(object, string) \
+ QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
+
+inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
+{
+ QDeclarativeExpression expr(engine->rootContext(), 0, str);
+ return expr.evaluate();
+}
+
+#define RUNEXPR(string) runexpr(&engine, QString(string))
+
class tst_qdeclarativelistmodel : public QObject
{
Q_OBJECT
@@ -65,6 +76,8 @@ private:
QQuickItem *createWorkerTest(QDeclarativeEngine *eng, QDeclarativeComponent *component, QDeclarativeListModel *model);
void waitForWorker(QQuickItem *item);
+ static bool compareVariantList(const QVariantList &testList, QVariant object);
+
private slots:
void static_types();
void static_types_data();
@@ -78,16 +91,12 @@ private slots:
void dynamic_worker();
void dynamic_worker_sync_data();
void dynamic_worker_sync();
- void convertNestedToFlat_fail();
- void convertNestedToFlat_fail_data();
- void convertNestedToFlat_ok();
- void convertNestedToFlat_ok_data();
void enumerate();
void error_data();
void error();
void syncError();
- void set();
void get();
+ void set();
void get_data();
void get_worker();
void get_worker_data();
@@ -101,8 +110,64 @@ private slots:
void property_changes_worker_data();
void clear();
void signal_handlers();
+ void worker_sync();
+ void worker_remove_element();
+ void worker_remove_list();
};
+bool tst_qdeclarativelistmodel::compareVariantList(const QVariantList &testList, QVariant object)
+{
+ bool allOk = true;
+
+ QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel *>(object.value<QObject *>());
+ if (model == 0)
+ return false;
+
+ if (model->count() != testList.count())
+ return false;
+
+ for (int i=0 ; i < testList.count() ; ++i) {
+ const QVariant &testVariant = testList.at(i);
+ if (testVariant.type() != QVariant::Map)
+ return false;
+ const QVariantMap &map = testVariant.toMap();
+
+ const QList<int> &roles = model->roles();
+
+ QVariantMap::const_iterator it = map.begin();
+ QVariantMap::const_iterator end = map.end();
+
+ while (it != end) {
+ const QString &testKey = it.key();
+ const QVariant &testData = it.value();
+
+ int roleIndex = -1;
+ for (int j=0 ; j < roles.count() ; ++j) {
+ if (model->toString(roles[j]).compare(testKey) == 0) {
+ roleIndex = j;
+ break;
+ }
+ }
+
+ if (roleIndex == -1)
+ return false;
+
+ const QVariant &modelData = model->data(i, roleIndex);
+
+ if (testData.type() == QVariant::List) {
+ const QVariantList &subList = testData.toList();
+ allOk = allOk && compareVariantList(subList, modelData);
+ } else {
+ allOk = allOk && (testData == modelData);
+ }
+
+ ++it;
+ }
+ }
+
+ return allOk;
+}
+
int tst_qdeclarativelistmodel::roleFromName(const QDeclarativeListModel *model, const QString &roleName)
{
QList<int> roles = model->roles();
@@ -118,7 +183,7 @@ QQuickItem *tst_qdeclarativelistmodel::createWorkerTest(QDeclarativeEngine *eng,
QQuickItem *item = qobject_cast<QQuickItem*>(component->create());
QDeclarativeEngine::setContextForObject(model, eng->rootContext());
if (item)
- item->setProperty("model", qVariantFromValue(model));
+ item->setProperty("model", qVariantFromValue(model));
return item;
}
@@ -141,43 +206,66 @@ void tst_qdeclarativelistmodel::static_types_data()
{
QTest::addColumn<QString>("qml");
QTest::addColumn<QVariant>("value");
+ QTest::addColumn<QString>("error");
QTest::newRow("string")
<< "ListElement { foo: \"bar\" }"
- << QVariant(QString("bar"));
+ << QVariant(QString("bar"))
+ << QString();
QTest::newRow("real")
<< "ListElement { foo: 10.5 }"
- << QVariant(10.5);
+ << QVariant(10.5)
+ << QString();
QTest::newRow("real0")
<< "ListElement { foo: 0 }"
- << QVariant(double(0));
+ << QVariant(double(0))
+ << QString();
QTest::newRow("bool")
<< "ListElement { foo: false }"
- << QVariant(false);
+ << QVariant(false)
+ << QString();
QTest::newRow("bool")
<< "ListElement { foo: true }"
- << QVariant(true);
+ << QVariant(true)
+ << QString();
QTest::newRow("enum")
<< "ListElement { foo: Text.AlignHCenter }"
- << QVariant(double(QQuickText::AlignHCenter));
+ << QVariant(double(QQuickText::AlignHCenter))
+ << QString();
QTest::newRow("Qt enum")
<< "ListElement { foo: Qt.AlignBottom }"
- << QVariant(double(Qt::AlignBottom));
+ << QVariant(double(Qt::AlignBottom))
+ << QString();
+
+ QTest::newRow("role error")
+ << "ListElement { foo: 1 } ListElement { foo: 'string' }"
+ << QVariant()
+ << QString("<Unknown File>: Can't assign to pre-existing role of different type foo");
+
+ QTest::newRow("list type error")
+ << "ListElement { foo: 1 } ListElement { foo: ListElement { bar: 1 } }"
+ << QVariant()
+ << QString("<Unknown File>: Can't assign to pre-existing role of different type foo");
}
void tst_qdeclarativelistmodel::static_types()
{
QFETCH(QString, qml);
QFETCH(QVariant, value);
+ QFETCH(QString, error);
qml = "import QtQuick 2.0\nItem { property variant test: model.get(0).foo; ListModel { id: model; " + qml + " } }";
+ if (!error.isEmpty()) {
+ QTest::ignoreMessage(QtWarningMsg, error.toLatin1());
+ }
+
QDeclarativeEngine engine;
QDeclarativeComponent component(&engine);
component.setData(qml.toUtf8(),
@@ -188,10 +276,12 @@ void tst_qdeclarativelistmodel::static_types()
QObject *obj = component.create();
QVERIFY(obj != 0);
- QVariant actual = obj->property("test");
+ if (error.isEmpty()) {
+ QVariant actual = obj->property("test");
- QCOMPARE(actual, value);
- QCOMPARE(actual.toString(), value.toString());
+ QCOMPARE(actual, value);
+ QCOMPARE(actual.toString(), value.toString());
+ }
delete obj;
}
@@ -270,11 +360,11 @@ void tst_qdeclarativelistmodel::static_nestedElements()
QFETCH(int, elementCount);
QStringList elements;
- for (int i=0; i<elementCount; i++)
+ for (int i=0; i<elementCount; i++)
elements.append("ListElement { a: 1; b: 2 }");
QString elementsStr = elements.join(",\n") + "\n";
- QString componentStr =
+ QString componentStr =
"import QtQuick 2.0\n"
"Item {\n"
" property variant count: model.get(0).attributes.count\n"
@@ -283,7 +373,7 @@ void tst_qdeclarativelistmodel::static_nestedElements()
" ListElement {\n"
" attributes: [\n";
componentStr += elementsStr.toUtf8().constData();
- componentStr +=
+ componentStr +=
" ]\n"
" }\n"
" }\n"
@@ -320,7 +410,6 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::addColumn<QString>("warning");
// Simple flat model
-
QTest::newRow("count") << "count" << 0 << "";
QTest::newRow("get1") << "{get(0) === undefined}" << 1 << "";
@@ -336,7 +425,8 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::newRow("append3a") << "{append({'foo':123});append({'foo':456});get(0).foo}" << 123 << "";
QTest::newRow("append3b") << "{append({'foo':123});append({'foo':456});get(1).foo}" << 456 << "";
QTest::newRow("append4a") << "{append(123)}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
- QTest::newRow("append4b") << "{append([1,2,3])}" << 0 << "<Unknown File>: QML ListModel: append: value is not an object";
+ QTest::newRow("append4b") << "{append([{'foo':123},{'foo':456},{'foo':789}]);count}" << 3 << "";
+ QTest::newRow("append4c") << "{append([{'foo':123},{'foo':456},{'foo':789}]);get(1).foo}" << 456 << "";
QTest::newRow("clear1") << "{append({'foo':456});clear();count}" << 0 << "";
QTest::newRow("clear2") << "{append({'foo':123});append({'foo':456});clear();count}" << 0 << "";
@@ -361,7 +451,8 @@ void tst_qdeclarativelistmodel::dynamic_data()
QTest::newRow("insert3e") << "{append({'foo':123});insert(0,{'foo':456});get(1).foo}" << 123 << "";
QTest::newRow("insert4") << "{append({'foo':123});insert(-1,{'foo':456});count}" << 1 << "<Unknown File>: QML ListModel: insert: index -1 out of range";
QTest::newRow("insert5a") << "{insert(0,123)}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
- QTest::newRow("insert5b") << "{insert(0,[1,2,3])}" << 0 << "<Unknown File>: QML ListModel: insert: value is not an object";
+ QTest::newRow("insert5b") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);count}" << 3 << "";
+ QTest::newRow("insert5c") << "{insert(0,[{'foo':11},{'foo':22},{'foo':33}]);get(2).foo}" << 33 << "";
QTest::newRow("set1") << "{append({'foo':123});set(0,{'foo':456});count}" << 1 << "";
QTest::newRow("set2") << "{append({'foo':123});set(0,{'foo':456});get(0).foo}" << 456 << "";
@@ -396,8 +487,11 @@ void tst_qdeclarativelistmodel::dynamic_data()
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";
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";
- // Nested models
+ QTest::newRow("large1") << "{append({'a':1,'b':2,'c':3,'d':4,'e':5,'f':6,'g':7,'h':8});get(0).h}" << 8 << "";
+
+ QTest::newRow("datatypes1") << "{append({'a':1});append({'a':'string'});}" << 0 << "<Unknown File>: Can't assign to pre-existing role of different type a";
+ // Nested models
QTest::newRow("nested-append1") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});count}" << 1 << "";
QTest::newRow("nested-append2") << "{append({'foo':123,'bars':[{'a':1},{'a':2},{'a':3}]});get(0).bars.get(1).a}" << 2 << "";
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 << "";
@@ -446,12 +540,8 @@ void tst_qdeclarativelistmodel::dynamic_worker()
QFETCH(int, result);
QFETCH(QString, warning);
- if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
- return;
-
- // This is same as dynamic() except it applies the test to a ListModel called
- // from a WorkerScript (i.e. testing the internal FlatListModel that is created
- // by the WorkerListModelAgent)
+ // This is same as dynamic() except it applies the test to a ListModel called
+ // from a WorkerScript.
QDeclarativeListModel model;
QDeclarativeEngine eng;
@@ -498,7 +588,7 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
// This is the same as dynamic_worker() except that it executes a set of list operations
// from the worker script, calls sync(), and tests the changes are reflected in the
// list in the main thread
-
+
QDeclarativeListModel model;
QDeclarativeEngine eng;
QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
@@ -518,140 +608,56 @@ void tst_qdeclarativelistmodel::dynamic_worker_sync()
// execute a set of commands on the worker list model, then check the
// changes are reflected in the list model in the main thread
- if (QByteArray(QTest::currentDataTag()).startsWith("nested"))
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
-
- if (QByteArray(QTest::currentDataTag()).startsWith("nested-set"))
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script");
-
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
+ QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker",
Q_ARG(QVariant, operations.mid(0, operations.length()-1))));
waitForWorker(item);
QDeclarativeExpression e(eng.rootContext(), &model, operations.last().toString());
- if (!QByteArray(QTest::currentDataTag()).startsWith("nested"))
- QCOMPARE(e.evaluate().toInt(), result);
-
- delete item;
- qApp->processEvents();
-}
-
-#define RUNEVAL(object, string) \
- QVERIFY(QMetaObject::invokeMethod(object, "runEval", Q_ARG(QVariant, QString(string))));
-
-inline QVariant runexpr(QDeclarativeEngine *engine, const QString &str)
-{
- QDeclarativeExpression expr(engine->rootContext(), 0, str);
- return expr.evaluate();
-}
-
-#define RUNEXPR(string) runexpr(&engine, QString(string))
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_fail()
-{
- // If a model has nested data, it cannot be used at all from a worker script
-
- QFETCH(QString, script);
-
- QDeclarativeListModel model;
- QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
- QQuickItem *item = createWorkerTest(&eng, &component, &model);
- QVERIFY(item != 0);
-
- RUNEVAL(item, "model.append({foo: 123})");
- RUNEVAL(item, "model.append({foo: [{}, {}]})");
-
- QCOMPARE(model.count(), 2);
-
- QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML ListModel: List contains list-type data and cannot be used from a worker script");
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
- waitForWorker(item);
-
- QCOMPARE(model.count(), 2);
+ QCOMPARE(e.evaluate().toInt(), result);
delete item;
qApp->processEvents();
}
-void tst_qdeclarativelistmodel::convertNestedToFlat_fail_data()
-{
- QTest::addColumn<QString>("script");
-
- QTest::newRow("clear") << "clear()";
- QTest::newRow("remove") << "remove(0)";
- QTest::newRow("append") << "append({'x':1})";
- QTest::newRow("insert") << "insert(0, {'x':1})";
- QTest::newRow("set") << "set(0, {'foo':1})";
- QTest::newRow("setProperty") << "setProperty(0, 'foo', 1})";
- QTest::newRow("move") << "move(0, 1, 1})";
- QTest::newRow("get") << "get(0)";
-}
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_ok()
-
+void tst_qdeclarativelistmodel::enumerate()
{
- // If a model only has plain data, it can be modified from a worker script. However,
- // once the model is used from a worker script, it no longer accepts nested data
-
- QFETCH(QString, script);
-
- QDeclarativeListModel model;
QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
- QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
+ QVERIFY(!component.isError());
+ QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
QVERIFY(item != 0);
- RUNEVAL(item, "model.append({foo: 123})");
-
- QCOMPARE(model.count(), 1);
-
- QVERIFY(QMetaObject::invokeMethod(item, "evalExpressionViaWorker", Q_ARG(QVariant, script)));
- waitForWorker(item);
-
- // can still add plain data
- int count = model.count();
-
- RUNEVAL(item, "model.append({foo: 123})");
+ QLatin1String expectedStrings[] = {
+ QLatin1String("val1=1Y"),
+ QLatin1String("val2=2Y"),
+ QLatin1String("val3=strY"),
+ QLatin1String("val4=falseN"),
+ QLatin1String("val5=trueY")
+ };
- QCOMPARE(model.count(), count+1);
+ int expectedStringCount = sizeof(expectedStrings) / sizeof(expectedStrings[0]);
- const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
+ QStringList r = item->property("result").toString().split(":");
- QTest::ignoreMessage(QtWarningMsg, warning);
- RUNEVAL(item, "model.append({foo: [{}, {}]})");
+ int matchCount = 0;
+ for (int i=0 ; i < expectedStringCount ; ++i) {
+ const QLatin1String &expectedString = expectedStrings[i];
- QTest::ignoreMessage(QtWarningMsg, warning);
- RUNEVAL(item, "model.insert(0, {foo: [{}, {}]})");
+ QStringList::const_iterator it = r.begin();
+ QStringList::const_iterator end = r.end();
- QTest::ignoreMessage(QtWarningMsg, warning);
- RUNEVAL(item, "model.set(0, {foo: [{}, {}]})");
+ while (it != end) {
+ if (it->compare(expectedString) == 0) {
+ ++matchCount;
+ break;
+ }
+ ++it;
+ }
+ }
- QCOMPARE(model.count(), count+1);
+ QVERIFY(matchCount == expectedStringCount);
delete item;
- qApp->processEvents();
-}
-
-void tst_qdeclarativelistmodel::convertNestedToFlat_ok_data()
-{
- convertNestedToFlat_fail_data();
-}
-
-void tst_qdeclarativelistmodel::enumerate()
-{
- QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/enumerate.qml"));
- QVERIFY(!component.isError());
- QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
- QVERIFY(item != 0);
- QStringList r = item->property("result").toString().split(":");
- QCOMPARE(r[0],QLatin1String("val1=1Y"));
- QCOMPARE(r[1],QLatin1String("val2=2Y"));
- QCOMPARE(r[2],QLatin1String("val3=strY"));
- QCOMPARE(r[3],QLatin1String("val4=falseN"));
- QCOMPARE(r[4],QLatin1String("val5=trueY"));
- delete item;
}
void tst_qdeclarativelistmodel::error_data()
@@ -752,11 +758,15 @@ void tst_qdeclarativelistmodel::set()
RUNEXPR("model.set(0, {test:true})");
QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache
- QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
+ QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(true));
RUNEXPR("model.set(0, {test:false})");
QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated
- QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
+ QCOMPARE(model.data(0, model.roles()[0]), qVariantFromValue(false));
+
+ QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: Can't create role for unsupported data type");
+ QVariant invalidData = QColor();
+ model.setProperty(0, "test", invalidData);
}
/*
@@ -773,21 +783,31 @@ void tst_qdeclarativelistmodel::get()
QDeclarativeComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
- "ListModel { \n"
- "ListElement { roleA: 100 }\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "}", QUrl());
+ "ListModel {}\n", QUrl());
QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
- int role = roleFromName(model, roleName);
- QVERIFY(role >= 0);
+ engine.rootContext()->setContextProperty("model", model);
+
+ RUNEXPR("model.append({roleA: 100})");
+ RUNEXPR("model.append({roleA: 200, roleB: 400})");
+ RUNEXPR("model.append({roleA: 200, roleB: 400})");
+ RUNEXPR("model.append({roleC: {} })");
+ RUNEXPR("model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
QSignalSpy spy(model, SIGNAL(itemsChanged(int, int, QList<int>)));
QDeclarativeExpression expr(engine.rootContext(), model, expression);
expr.evaluate();
QVERIFY(!expr.hasError());
- QCOMPARE(model->data(index, role), roleValue);
+ int role = roleFromName(model, roleName);
+ QVERIFY(role >= 0);
+
+ if (roleValue.type() == QVariant::List) {
+ const QVariantList &list = roleValue.toList();
+ QVERIFY(compareVariantList(list, model->data(index, role)));
+ } else {
+ QCOMPARE(model->data(index, role), roleValue);
+ }
+
QCOMPARE(spy.count(), 1);
QList<QVariant> spyResult = spy.takeFirst();
@@ -805,19 +825,16 @@ void tst_qdeclarativelistmodel::get_data()
QTest::addColumn<QString>("roleName");
QTest::addColumn<QVariant>("roleValue");
- QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
- QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
+ QTest::newRow("simple value") << "get(0).roleA = 500" << 0 << "roleA" << QVariant(500);
+ QTest::newRow("simple value 2") << "get(1).roleB = 500" << 1 << "roleB" << QVariant(500);
QVariantMap map;
- map["zzz"] = 123;
- QTest::newRow("object value") << "get(1).roleB = {'zzz':123}" << 1 << "roleB" << QVariant::fromValue(map);
-
QVariantList list;
map.clear(); map["a"] = 50; map["b"] = 500;
list << map;
map.clear(); map["c"] = 1000;
list << map;
- QTest::newRow("list of objects") << "get(2).roleB = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleB" << QVariant::fromValue(list);
+ QTest::newRow("list of objects") << "get(2).roleD = [{'a': 50, 'b': 500}, {'c': 1000}]" << 2 << "roleD" << QVariant::fromValue(list);
}
void tst_qdeclarativelistmodel::get_worker()
@@ -837,13 +854,12 @@ void tst_qdeclarativelistmodel::get_worker()
RUNEVAL(item, "model.append({roleA: 100})");
RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
RUNEVAL(item, "model.append({roleA: 200, roleB: 400})");
+ RUNEVAL(item, "model.append({roleC: {} })");
+ RUNEVAL(item, "model.append({roleD: [ { a:1, b:2 }, { c: 3 } ] })");
int role = roleFromName(&model, roleName);
QVERIFY(role >= 0);
- const char *warning = "<Unknown File>: QML ListModel: Cannot add list-type data when modifying or after modification from a worker script";
- if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map)
- QTest::ignoreMessage(QtWarningMsg, warning);
QSignalSpy spy(&model, SIGNAL(itemsChanged(int, int, QList<int>)));
// in the worker thread, change the model data and call sync()
@@ -852,18 +868,19 @@ void tst_qdeclarativelistmodel::get_worker()
waitForWorker(item);
// see if we receive the model changes in the main thread's model
- if (roleValue.type() == QVariant::List || roleValue.type() == QVariant::Map) {
- QVERIFY(model.data(index, role) != roleValue);
- QCOMPARE(spy.count(), 0);
+ if (roleValue.type() == QVariant::List) {
+ const QVariantList &list = roleValue.toList();
+ QVERIFY(compareVariantList(list, model.data(index, role)));
} else {
QCOMPARE(model.data(index, role), roleValue);
- QCOMPARE(spy.count(), 1);
-
- QList<QVariant> spyResult = spy.takeFirst();
- QCOMPARE(spyResult.at(0).toInt(), index);
- QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
- QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
}
+
+ QCOMPARE(spy.count(), 1);
+
+ QList<QVariant> spyResult = spy.takeFirst();
+ QCOMPARE(spyResult.at(0).toInt(), index);
+ QCOMPARE(spyResult.at(1).toInt(), 1); // only 1 item is modified at a time
+ QVERIFY(spyResult.at(2).value<QList<int> >().contains(role));
}
void tst_qdeclarativelistmodel::get_worker_data()
@@ -881,39 +898,48 @@ void tst_qdeclarativelistmodel::get_nested()
QFETCH(QString, roleName);
QFETCH(QVariant, roleValue);
- QDeclarativeEngine eng;
- QDeclarativeComponent component(&eng);
+ if (roleValue.type() == QVariant::Map)
+ return;
+
+ QDeclarativeEngine engine;
+ QDeclarativeComponent component(&engine);
component.setData(
"import QtQuick 2.0\n"
- "ListModel { \n"
- "ListElement {\n"
- "listRoleA: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "}\n"
- "ListElement {\n"
- "listRoleA: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "listRoleB: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "listRoleC: [\n"
- "ListElement { roleA: 100 },\n"
- "ListElement { roleA: 200; roleB: 400 },\n"
- "ListElement { roleA: 200; roleB: 400 } \n"
- "]\n"
- "}\n"
- "}", QUrl());
+ "ListModel {}", QUrl());
QDeclarativeListModel *model = qobject_cast<QDeclarativeListModel*>(component.create());
QVERIFY(component.errorString().isEmpty());
QDeclarativeListModel *childModel;
+ engine.rootContext()->setContextProperty("model", model);
+
+ RUNEXPR("model.append({ listRoleA: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "] })\n");
+
+ RUNEXPR("model.append({ listRoleA: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "],\n"
+ "listRoleB: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "],\n"
+ "listRoleC: [\n"
+ "{ roleA: 100 },\n"
+ "{ roleA: 200, roleB: 400 },\n"
+ "{ roleA: 200, roleB: 400 }, \n"
+ "{ roleC: {} }, \n"
+ "{ roleD: [ { a: 1, b:2 }, { c: 3 } ] } \n"
+ "] })\n");
// Test setting the inner list data for:
// get(0).listRoleA
@@ -937,7 +963,7 @@ void tst_qdeclarativelistmodel::get_nested()
QVERIFY(childModel);
QString extendedExpression = QString("get(%1).%2.%3").arg(outerListIndex).arg(outerListRoleName).arg(expression);
- QDeclarativeExpression expr(eng.rootContext(), model, extendedExpression);
+ QDeclarativeExpression expr(engine.rootContext(), model, extendedExpression);
QSignalSpy spy(childModel, SIGNAL(itemsChanged(int, int, QList<int>)));
expr.evaluate();
@@ -945,7 +971,11 @@ void tst_qdeclarativelistmodel::get_nested()
int role = roleFromName(childModel, roleName);
QVERIFY(role >= 0);
- QCOMPARE(childModel->data(index, role), roleValue);
+ if (roleValue.type() == QVariant::List) {
+ QVERIFY(compareVariantList(roleValue.toList(), childModel->data(index, role)));
+ } else {
+ QCOMPARE(childModel->data(index, role), roleValue);
+ }
QCOMPARE(spy.count(), 1);
QList<QVariant> spyResult = spy.takeFirst();
@@ -1017,6 +1047,7 @@ void tst_qdeclarativelistmodel::property_changes()
"target: model.get(" + QString::number(listIndex) + ")\n"
+ signalHandler + " gotSignal = true\n"
"}\n";
+
QDeclarativeComponent component(&engine);
component.setData(qml.toUtf8(), QUrl::fromLocalFile(""));
engine.rootContext()->setContextProperty("model", &model);
@@ -1117,10 +1148,6 @@ void tst_qdeclarativelistmodel::property_changes_data()
void tst_qdeclarativelistmodel::property_changes_worker()
{
- // nested models are not supported when WorkerScript is involved
- if (QByteArray(QTest::currentDataTag()).startsWith("nested-"))
- return;
-
QFETCH(QString, script_setup);
QFETCH(QString, script_change);
QFETCH(QString, roleName);
@@ -1208,6 +1235,129 @@ void tst_qdeclarativelistmodel::signal_handlers()
delete model;
}
+void tst_qdeclarativelistmodel::worker_sync()
+{
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workersync.qml"));
+ QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ QVERIFY(model.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItem0"));
+
+ QVERIFY(model.count() == 2);
+ QVariant childData = model.data(0, 0);
+ QDeclarativeListModel *childModel = qobject_cast<QDeclarativeListModel *>(childData.value<QObject *>());
+ QVERIFY(childModel);
+ QVERIFY(childModel->count() == 1);
+
+ QSignalSpy spyModelInserted(&model, SIGNAL(itemsInserted(int,int)));
+ QSignalSpy spyChildInserted(childModel, SIGNAL(itemsInserted(int,int)));
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 1);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 2);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItemViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 2);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 2);
+ QVERIFY(childModel->count() == 3);
+ QVERIFY(spyModelInserted.count() == 0);
+ QVERIFY(spyChildInserted.count() == 2);
+
+ delete item;
+ qApp->processEvents();
+}
+
+void tst_qdeclarativelistmodel::worker_remove_element()
+{
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workerremoveelement.qml"));
+ QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addItem"));
+
+ QVERIFY(model.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "removeItemViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 1);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 1);
+
+ delete item;
+ qApp->processEvents();
+}
+
+void tst_qdeclarativelistmodel::worker_remove_list()
+{
+ QDeclarativeListModel model;
+ QDeclarativeEngine eng;
+ QDeclarativeComponent component(&eng, QUrl::fromLocalFile(SRCDIR "/data/workerremovelist.qml"));
+ QQuickItem *item = createWorkerTest(&eng, &component, &model);
+ QVERIFY(item != 0);
+
+ QSignalSpy spyModelRemoved(&model, SIGNAL(itemsRemoved(int,int)));
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "addList"));
+
+ QVERIFY(model.count() == 1);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "removeListViaWorker"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 1);
+ QVERIFY(spyModelRemoved.count() == 0);
+
+ QVERIFY(QMetaObject::invokeMethod(item, "doSync"));
+ waitForWorker(item);
+
+ QVERIFY(model.count() == 0);
+ QVERIFY(spyModelRemoved.count() == 1);
+
+ delete item;
+ qApp->processEvents();
+}
+
QTEST_MAIN(tst_qdeclarativelistmodel)
#include "tst_qdeclarativelistmodel.moc"