diff options
author | Matthew Vogt <matthew.vogt@nokia.com> | 2012-02-16 14:43:03 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-02-24 04:51:31 +0100 |
commit | b855240b782395f94315f43ea3e7e182299fac48 (patch) | |
tree | bc594c04449be8cd14cd0ab0bb72dafc2be0ffb2 /tests/auto/quick/qquickxmllistmodel | |
parent | 6a42a6e0a9a1abdda0d07a5a20b4ac7e45348684 (diff) |
Rename QDeclarative symbols to QQuick and QQml
Symbols beginning with QDeclarative are already exported
by the quick1 module.
Users can apply the bin/rename-qtdeclarative-symbols.sh
script to modify client code using the previous names of the
renamed symbols.
Task-number: QTBUG-23737
Change-Id: Ifaa482663767634931e8711a8e9bf6e404859e66
Reviewed-by: Martin Jones <martin.jones@nokia.com>
Diffstat (limited to 'tests/auto/quick/qquickxmllistmodel')
15 files changed, 1278 insertions, 0 deletions
diff --git a/tests/auto/quick/qquickxmllistmodel/data/empty.xml b/tests/auto/quick/qquickxmllistmodel/data/empty.xml new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/empty.xml diff --git a/tests/auto/quick/qquickxmllistmodel/data/get.qml b/tests/auto/quick/qquickxmllistmodel/data/get.qml new file mode 100644 index 0000000000..509da7174b --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/get.qml @@ -0,0 +1,61 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + source: "model.xml" + query: "/Pets/Pet" + XmlRole { name: "name"; query: "name/string()" } + XmlRole { name: "type"; query: "type/string()" } + XmlRole { name: "age"; query: "age/number()" } + XmlRole { name: "size"; query: "size/string()" } + + id: root + + property bool preTest: false + property bool postTest: false + + function runPreTest() { + if (root.get(0) != undefined) + return; + + preTest = true; + } + + function runPostTest() { + if (root.get(-1) != undefined) + return; + + var row = root.get(0); + if (row.name != "Polly" || + row.type != "Parrot" || + row.age != 12 || + row.size != "Small") + return; + + row = root.get(1); + if (row.name != "Penny" || + row.type != "Turtle" || + row.age != 4 || + row.size != "Small") + return; + + row = root.get(7); + if (row.name != "Rover" || + row.type != "Dog" || + row.age != 0 || + row.size != "Large") + return; + + row = root.get(8); + if (row.name != "Tiny" || + row.type != "Elephant" || + row.age != 15 || + row.size != "Large") + return; + + if (root.get(9) != undefined) + return; + + postTest = true; + } +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/model.qml b/tests/auto/quick/qquickxmllistmodel/data/model.qml new file mode 100644 index 0000000000..2df3927479 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/model.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + source: "model.xml" + query: "/Pets/Pet" + XmlRole { name: "name"; query: "name/string()" } + XmlRole { name: "type"; query: "type/string()" } + XmlRole { name: "age"; query: "age/number()" } + XmlRole { name: "size"; query: "size/string()" } +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/model.xml b/tests/auto/quick/qquickxmllistmodel/data/model.xml new file mode 100644 index 0000000000..40cd6d0432 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/model.xml @@ -0,0 +1,54 @@ +<Pets> + <Pet> + <name>Polly</name> + <type>Parrot</type> + <age>12</age> + <size>Small</size> + </Pet> + <Pet> + <name>Penny</name> + <type>Turtle</type> + <age>4</age> + <size>Small</size> + </Pet> + <Pet> + <name>Warren</name> + <type>Rabbit</type> + <age>2</age> + <size>Small</size> + </Pet> + <Pet> + <name>Spot</name> + <type>Dog</type> + <age>9</age> + <size>Medium</size> + </Pet> + <Pet> + <name>Whiskers</name> + <type>Cat</type> + <age>2</age> + <size>Medium</size> + </Pet> + <Pet> + <name>Joey</name> + <type>Kangaroo</type> + <age>1</age> + </Pet> + <Pet> + <name>Kimba</name> + <type>Bunny</type> + <age>65</age> + <size>Large</size> + </Pet> + <Pet> + <name>Rover</name> + <type>Dog</type> + <size>Large</size> + </Pet> + <Pet> + <name>Tiny</name> + <type>Elephant</type> + <age>15</age> + <size>Large</size> + </Pet> +</Pets> diff --git a/tests/auto/quick/qquickxmllistmodel/data/model2.xml b/tests/auto/quick/qquickxmllistmodel/data/model2.xml new file mode 100644 index 0000000000..dab2ec6dc0 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/model2.xml @@ -0,0 +1,14 @@ +<Pets> + <Pet> + <name>Polly</name> + <type>Parrot</type> + <age>12</age> + <size>Small</size> + </Pet> + <Pet> + <name>Penny</name> + <type>Turtle</type> + <age>4</age> + <size>Small</size> + </Pet> +</Pets> diff --git a/tests/auto/quick/qquickxmllistmodel/data/propertychanges.qml b/tests/auto/quick/qquickxmllistmodel/data/propertychanges.qml new file mode 100644 index 0000000000..f8a97bffc3 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/propertychanges.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + source: "model.xml" + query: "/Pets/Pet" + XmlRole { objectName: "role"; name: "name"; query: "name/string()" } + XmlRole { name: "type"; query: "type/string()" } + XmlRole { name: "age"; query: "age/number()" } + XmlRole { name: "size"; query: "size/string()" } +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/recipes.qml b/tests/auto/quick/qquickxmllistmodel/data/recipes.qml new file mode 100644 index 0000000000..dc609e95e3 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/recipes.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + source: "recipes.xml" + query: "/recipes/recipe" + XmlRole { name: "title"; query: "@title/string()" } + XmlRole { name: "picture"; query: "picture/string()" } + XmlRole { name: "ingredients"; query: "ingredients/string()" } + XmlRole { name: "preparation"; query: "method/string()" } +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/recipes.xml b/tests/auto/quick/qquickxmllistmodel/data/recipes.xml new file mode 100644 index 0000000000..d71de60710 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/recipes.xml @@ -0,0 +1,90 @@ +<recipes> + <recipe title="Pancakes"> + <picture>content/pics/pancakes.jpg</picture> + <ingredients><![CDATA[<html> + <ul> + <li> 1 cup (150g) self-raising flour + <li> 1 tbs caster sugar + <li> 3/4 cup (185ml) milk + <li> 1 egg + </ul> + </html> + ]]></ingredients> + <method><![CDATA[<html> + <ol> + <li> Sift flour and sugar together into a bowl. Add a pinch of salt. + <li> Beat milk and egg together, then add to dry ingredients. Beat until smooth. + <li> Pour mixture into a pan on medium heat and cook until bubbles appear on the surface. + <li> Turn over and cook other side until golden. + </ol> + </html> + ]]></method> + </recipe> + <recipe title="Fruit Salad"> + <picture>content/pics/fruit-salad.jpg</picture> + <ingredients><![CDATA[* Seasonal Fruit]]></ingredients> + <method><![CDATA[* Chop fruit and place in a bowl.]]></method> + </recipe> + <recipe title="Vegetable Soup"> + <picture>content/pics/vegetable-soup.jpg</picture> + <ingredients><![CDATA[<html> + <ul> + <li> 1 onion + <li> 1 turnip + <li> 1 potato + <li> 1 carrot + <li> 1 head of celery + <li> 1 1/2 litres of water + </ul> + </html> + ]]></ingredients> + <method><![CDATA[<html> + <ol> + <li> Chop vegetables. + <li> Boil in water until vegetables soften. + <li> Season with salt and pepper to taste. + </ol> + </html> + ]]></method> + </recipe> + <recipe title="Hamburger"> + <picture>content/pics/hamburger.jpg</picture> + <ingredients><![CDATA[<html> + <ul> + <li> 500g minced beef + <li> Seasoning + <li> lettuce, tomato, onion, cheese + <li> 1 hamburger bun for each burger + </ul> + </html> + ]]></ingredients> + <method><![CDATA[<html> + <ol> + <li> Mix the beef, together with seasoning, in a food processor. + <li> Shape the beef into burgers. + <li> Grill the burgers for about 5 mins on each side (until cooked through) + <li> Serve each burger on a bun with ketchup, cheese, lettuce, tomato and onion. + </ol> + </html> + ]]></method> + </recipe> + <recipe title="Lemonade"> + <picture>content/pics/lemonade.jpg</picture> + <ingredients><![CDATA[<html> + <ul> + <li> 1 cup Lemon Juice + <li> 1 cup Sugar + <li> 6 Cups of Water (2 cups warm water, 4 cups cold water) + </ul> + </html> + ]]></ingredients> + <method><![CDATA[<html> + <ol> + <li> Pour 2 cups of warm water into a pitcher and stir in sugar until it dissolves. + <li> Pour in lemon juice, stir again, and add 4 cups of cold water. + <li> Chill or serve over ice cubes. + </ol> + </html> + ]]></method> + </recipe> +</recipes> diff --git a/tests/auto/quick/qquickxmllistmodel/data/roleCrash.qml b/tests/auto/quick/qquickxmllistmodel/data/roleCrash.qml new file mode 100644 index 0000000000..6a7059bb45 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/roleCrash.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + id: model + XmlRole {} + Component.onCompleted: model.roles = 0 +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/roleErrors.qml b/tests/auto/quick/qquickxmllistmodel/data/roleErrors.qml new file mode 100644 index 0000000000..91664b6d4a --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/roleErrors.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + source: "model.xml" + query: "/Pets/Pet" + XmlRole { name: "name"; query: "/name/string()" } //starts with '/' + XmlRole { name: "type"; query: "type" } //no type + XmlRole { name: "age"; query: "age/" } //ends with '/' + XmlRole { name: "size"; query: "size/number()" } //wrong type +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/roleKeys.qml b/tests/auto/quick/qquickxmllistmodel/data/roleKeys.qml new file mode 100644 index 0000000000..9f667d86e5 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/roleKeys.qml @@ -0,0 +1,13 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + query: "/data/item" + XmlRole { id: nameRole; name: "name"; query: "name/string()"; isKey: true } + XmlRole { name: "age"; query: "age/number()"; isKey: true } + XmlRole { name: "sport"; query: "sport/string()" } + + function disableNameKey() { + nameRole.isKey = false; + } +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/testtypes.qml b/tests/auto/quick/qquickxmllistmodel/data/testtypes.qml new file mode 100644 index 0000000000..5ec1ffa35f --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/testtypes.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + query: "/data" + XmlRole { name: "stringValue"; query: "a-string/string()" } + XmlRole { name: "numberValue"; query: "a-number/number()" } +} diff --git a/tests/auto/quick/qquickxmllistmodel/data/unique.qml b/tests/auto/quick/qquickxmllistmodel/data/unique.qml new file mode 100644 index 0000000000..322a2e4e5c --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/data/unique.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import QtQuick.XmlListModel 2.0 + +XmlListModel { + source: "model.xml" + query: "/Pets/Pet" + XmlRole { name: "name"; query: "name/string()" } + XmlRole { name: "name"; query: "type/string()" } +} diff --git a/tests/auto/quick/qquickxmllistmodel/qquickxmllistmodel.pro b/tests/auto/quick/qquickxmllistmodel/qquickxmllistmodel.pro new file mode 100644 index 0000000000..b18af50c9e --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/qquickxmllistmodel.pro @@ -0,0 +1,15 @@ +CONFIG += testcase +TARGET = tst_qquickxmllistmodel +macx:CONFIG -= app_bundle + +SOURCES += tst_qquickxmllistmodel.cpp + +include (../../shared/util.pri) + +testDataFiles.files = data +testDataFiles.path = . +DEPLOYMENT += testDataFiles + +CONFIG += parallel_test + +QT += core-private gui-private v8-private qml-private network testlib xmlpatterns diff --git a/tests/auto/quick/qquickxmllistmodel/tst_qquickxmllistmodel.cpp b/tests/auto/quick/qquickxmllistmodel/tst_qquickxmllistmodel.cpp new file mode 100644 index 0000000000..4d337564c0 --- /dev/null +++ b/tests/auto/quick/qquickxmllistmodel/tst_qquickxmllistmodel.cpp @@ -0,0 +1,962 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtGlobal> +#include <math.h> +#include <QMetaObject> +#include <qtest.h> +#include <QtTest/qsignalspy.h> +#include <QtQml/qqmlnetworkaccessmanagerfactory.h> +#include <QtNetwork/qnetworkaccessmanager.h> +#include <QtNetwork/qnetworkrequest.h> +#include <QtCore/qtimer.h> +#include <QtCore/qfile.h> +#include <QtCore/qtemporaryfile.h> +#include "../../shared/util.h" +#include <private/qqmlengine_p.h> + +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <private/qlistmodelinterface_p.h> +#include "../../../../src/imports/xmllistmodel/qqmlxmllistmodel_p.h" + +typedef QPair<int, int> QQuickXmlListRange; +typedef QList<QVariantList> QQmlXmlModelData; + +Q_DECLARE_METATYPE(QList<QQuickXmlListRange>) +Q_DECLARE_METATYPE(QQmlXmlModelData) +Q_DECLARE_METATYPE(QQuickXmlListModel::Status) + +class tst_qquickxmllistmodel : public QQmlDataTest + +{ + Q_OBJECT +public: + tst_qquickxmllistmodel() {} + +private slots: + void initTestCase() { + QQmlDataTest::initTestCase(); + qRegisterMetaType<QQuickXmlListModel::Status>(); + } + + void buildModel(); + void testTypes(); + void testTypes_data(); + void cdata(); + void attributes(); + void roles(); + void roleErrors(); + void uniqueRoleNames(); + void headers(); + void xml(); + void xml_data(); + void source(); + void source_data(); + void data(); + void get(); + void reload(); + void useKeys(); + void useKeys_data(); + void noKeysValueChanges(); + void keysChanged(); + void threading(); + void threading_data(); + void propertyChanges(); + + void roleCrash(); + +private: + QString errorString(QListModelInterface* model) { + QString ret; + QMetaObject::invokeMethod(model, "errorString", Q_RETURN_ARG(QString, ret)); + return ret; + } + + QString makeItemXmlAndData(const QString &data, QQmlXmlModelData *modelData = 0) const + { + if (modelData) + modelData->clear(); + QString xml; + + if (!data.isEmpty()) { + QStringList items = data.split(";"); + foreach(const QString &item, items) { + if (item.isEmpty()) + continue; + QVariantList variants; + xml += QLatin1String("<item>"); + QStringList fields = item.split(","); + foreach(const QString &field, fields) { + QStringList values = field.split("="); + if (values.count() != 2) { + qWarning() << "makeItemXmlAndData: invalid field:" << field; + continue; + } + xml += QString("<%1>%2</%1>").arg(values[0], values[1]); + if (!modelData) + continue; + bool isNum = false; + int number = values[1].toInt(&isNum); + if (isNum) + variants << number; + else + variants << values[1]; + } + xml += QLatin1String("</item>"); + if (modelData) + modelData->append(variants); + } + } + + QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>"; + return decl + QLatin1String("<data>") + xml + QLatin1String("</data>"); + } + + QQmlEngine engine; +}; + +class CustomNetworkAccessManagerFactory : public QObject, public QQmlNetworkAccessManagerFactory +{ + Q_OBJECT +public: + QVariantMap lastSentHeaders; + +protected: + QNetworkAccessManager *create(QObject *parent); +}; + +class CustomNetworkAccessManager : public QNetworkAccessManager +{ + Q_OBJECT +public: + CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent) + : QNetworkAccessManager(parent), m_factory(factory) {} + +protected: + QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0) + { + if (m_factory) { + QVariantMap map; + foreach (const QString &header, req.rawHeaderList()) + map[header] = req.rawHeader(header.toUtf8()); + m_factory->lastSentHeaders = map; + } + return QNetworkAccessManager::createRequest(op, req, outgoingData); + } + + QPointer<CustomNetworkAccessManagerFactory> m_factory; +}; + +QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent) +{ + return new CustomNetworkAccessManager(this, parent); +} + + +void tst_qquickxmllistmodel::buildModel() +{ + QQmlComponent component(&engine, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 9); + + QCOMPARE(model->data(3, Qt::UserRole).toString(), QLatin1String("Spot")); + QCOMPARE(model->data(3, Qt::UserRole+1).toString(), QLatin1String("Dog")); + QCOMPARE(model->data(3, Qt::UserRole+2).toInt(), 9); + QCOMPARE(model->data(3, Qt::UserRole+3).toString(), QLatin1String("Medium")); + + delete model; +} + +void tst_qquickxmllistmodel::testTypes() +{ + QFETCH(QString, xml); + QFETCH(QString, roleName); + QFETCH(QVariant, expectedValue); + + QQmlComponent component(&engine, testFileUrl("testtypes.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + model->setProperty("xml",xml.toUtf8()); + QMetaObject::invokeMethod(model, "reload"); + QTRY_COMPARE(model->count(), 1); + + int role = -1; + foreach (int i, model->roles()) { + if (model->toString(i) == roleName) { + role = i; + break; + } + } + QVERIFY(role >= 0); + + if (expectedValue.toString() == "nan") + QVERIFY(qIsNaN(model->data(0, role).toDouble())); + else + QCOMPARE(model->data(0, role), expectedValue); + + delete model; +} + +void tst_qquickxmllistmodel::testTypes_data() +{ + QTest::addColumn<QString>("xml"); + QTest::addColumn<QString>("roleName"); + QTest::addColumn<QVariant>("expectedValue"); + + QTest::newRow("missing string field") << "<data></data>" + << "stringValue" << QVariant(""); + QTest::newRow("empty string") << "<data><a-string></a-string></data>" + << "stringValue" << QVariant(""); + QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>" + << "stringValue" << QVariant("5"); + QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>" + << "stringValue" << QVariant("abc def g"); + + QTest::newRow("missing number field") << "<data></data>" + << "numberValue" << QVariant(""); + double nan = qQNaN(); + QTest::newRow("empty number field") << "<data><a-number></a-number></data>" + << "numberValue" << QVariant(nan); + QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>" + << "numberValue" << QVariant(nan); + QTest::newRow("-1") << "<data><a-number>-1</a-number></data>" + << "numberValue" << QVariant("-1"); + QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>" + << "numberValue" << QVariant("-1.5"); + QTest::newRow("0") << "<data><a-number>0</a-number></data>" + << "numberValue" << QVariant("0"); + QTest::newRow("+1") << "<data><a-number>1</a-number></data>" + << "numberValue" << QVariant("1"); + QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>" + << "numberValue" << QVariant("1.5"); +} + +void tst_qquickxmllistmodel::cdata() +{ + QQmlComponent component(&engine, testFileUrl("recipes.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 5); + + QVERIFY(model->data(2, Qt::UserRole+2).toString().startsWith(QLatin1String("<html>"))); + + delete model; +} + +void tst_qquickxmllistmodel::attributes() +{ + QQmlComponent component(&engine, testFileUrl("recipes.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 5); + QCOMPARE(model->data(2, Qt::UserRole).toString(), QLatin1String("Vegetable Soup")); + + delete model; +} + +void tst_qquickxmllistmodel::roles() +{ + QQmlComponent component(&engine, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 9); + + QList<int> roles = model->roles(); + QCOMPARE(roles.count(), 4); + QCOMPARE(model->toString(roles.at(0)), QLatin1String("name")); + QCOMPARE(model->toString(roles.at(1)), QLatin1String("type")); + QCOMPARE(model->toString(roles.at(2)), QLatin1String("age")); + QCOMPARE(model->toString(roles.at(3)), QLatin1String("size")); + + delete model; +} + +void tst_qquickxmllistmodel::roleErrors() +{ + QQmlComponent component(&engine, testFileUrl("roleErrors.qml")); + QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":7:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData()); + QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":10:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData()); + + //### make sure we receive all expected warning messages. + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 9); + + + //### should any of these return valid values? + QCOMPARE(model->data(3, Qt::UserRole), QVariant()); + QCOMPARE(model->data(3, Qt::UserRole+1), QVariant()); + QCOMPARE(model->data(3, Qt::UserRole+2), QVariant()); + + QEXPECT_FAIL("", "QTBUG-10797", Continue); + QCOMPARE(model->data(3, Qt::UserRole+3), QVariant()); + + delete model; +} + +void tst_qquickxmllistmodel::uniqueRoleNames() +{ + QQmlComponent component(&engine, testFileUrl("unique.qml")); + QTest::ignoreMessage(QtWarningMsg, (testFileUrl("unique.qml").toString() + ":8:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData()); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 9); + + QList<int> roles = model->roles(); + QCOMPARE(roles.count(), 1); + + delete model; +} + + +void tst_qquickxmllistmodel::xml() +{ + QFETCH(QString, xml); + QFETCH(int, count); + + QQmlComponent component(&engine, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + + QSignalSpy spy(model, SIGNAL(statusChanged(QQuickXmlListModel::Status))); + QVERIFY(errorString(model).isEmpty()); + QCOMPARE(model->property("progress").toDouble(), qreal(0.0)); + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Loading); + QTRY_COMPARE(spy.count(), 1); spy.clear(); + QTest::qWait(50); + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Ready); + QVERIFY(errorString(model).isEmpty()); + QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); + QCOMPARE(model->count(), 9); + + // if xml is empty (i.e. clearing) it won't have any effect if a source is set + if (xml.isEmpty()) + model->setProperty("source",QUrl()); + model->setProperty("xml",xml); + QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); // immediately goes to 1.0 if using setXml() + QTRY_COMPARE(spy.count(), 1); spy.clear(); + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Loading); + QTRY_COMPARE(spy.count(), 1); spy.clear(); + if (xml.isEmpty()) + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Null); + else + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Ready); + QVERIFY(errorString(model).isEmpty()); + QCOMPARE(model->count(), count); + + delete model; +} + +void tst_qquickxmllistmodel::xml_data() +{ + QTest::addColumn<QString>("xml"); + QTest::addColumn<int>("count"); + + QTest::newRow("xml with no items") << "<Pets></Pets>" << 0; + QTest::newRow("empty xml") << "" << 0; + QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1; +} + +void tst_qquickxmllistmodel::headers() +{ + // ensure the QNetworkAccessManagers created for this test are immediately deleted + QQmlEngine qmlEng; + + CustomNetworkAccessManagerFactory factory; + qmlEng.setNetworkAccessManagerFactory(&factory); + + QQmlComponent component(&qmlEng, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Ready); + + QVariantMap expectedHeaders; + expectedHeaders["Accept"] = "application/xml,*/*"; + + QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count()); + foreach (const QString &header, expectedHeaders.keys()) { + QVERIFY(factory.lastSentHeaders.contains(header)); + QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString()); + } + + delete model; +} + +void tst_qquickxmllistmodel::source() +{ + QFETCH(QUrl, source); + QFETCH(int, count); + QFETCH(QQuickXmlListModel::Status, status); + + QQmlComponent component(&engine, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QSignalSpy spy(model, SIGNAL(statusChanged(QQuickXmlListModel::Status))); + + QVERIFY(errorString(model).isEmpty()); + QCOMPARE(model->property("progress").toDouble(), qreal(0.0)); + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Loading); + QTRY_COMPARE(spy.count(), 1); spy.clear(); + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Ready); + QVERIFY(errorString(model).isEmpty()); + QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); + QCOMPARE(model->count(), 9); + + model->setProperty("source",source); + if (model->property("source").toString().isEmpty()) + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Null); + QCOMPARE(model->property("progress").toDouble(), qreal(0.0)); + QTRY_COMPARE(spy.count(), 1); spy.clear(); + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), + QQuickXmlListModel::Loading); + QVERIFY(errorString(model).isEmpty()); + + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + connect(model, SIGNAL(statusChanged(QQuickXmlListModel::Status)), &loop, SLOT(quit())); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + timer.start(20000); + loop.exec(); + + if (spy.count() == 0 && status != QQuickXmlListModel::Ready) { + qWarning("QQuickXmlListModel invalid source test timed out"); + } else { + QCOMPARE(spy.count(), 1); spy.clear(); + } + + QCOMPARE(qvariant_cast<QQuickXmlListModel::Status>(model->property("status")), status); + QCOMPARE(model->count(), count); + + if (status == QQuickXmlListModel::Ready) + QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); + + QCOMPARE(errorString(model).isEmpty(), status == QQuickXmlListModel::Ready); + + delete model; +} + +void tst_qquickxmllistmodel::source_data() +{ + QTest::addColumn<QUrl>("source"); + QTest::addColumn<int>("count"); + QTest::addColumn<QQuickXmlListModel::Status>("status"); + + QTest::newRow("valid") << testFileUrl("model2.xml") << 2 + << QQuickXmlListModel::Ready; + QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 + << QQuickXmlListModel::Error; + + // empty file + QTemporaryFile *temp = new QTemporaryFile(this); + if (temp->open()) + QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 + << QQuickXmlListModel::Ready; + temp->close(); +} + +void tst_qquickxmllistmodel::data() +{ + QQmlComponent component(&engine, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + + for (int i=0; i<9; i++) { + for (int j=0; j<model->roles().count(); j++) { + QCOMPARE(model->data(i, j), QVariant()); + } + } + QTRY_COMPARE(model->count(), 9); + + delete model; +} + +void tst_qquickxmllistmodel::get() +{ + QQmlComponent component(&engine, testFileUrl("get.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + + QVERIFY(model != 0); + + QVERIFY(QMetaObject::invokeMethod(model, "runPreTest")); + QCOMPARE(model->property("preTest").toBool(), true); + + QTRY_COMPARE(model->count(), 9); + + QVERIFY(QMetaObject::invokeMethod(model, "runPostTest")); + QCOMPARE(model->property("postTest").toBool(), true); + + delete model; +} + +void tst_qquickxmllistmodel::reload() +{ + // If no keys are used, the model should be rebuilt from scratch when + // reload() is called. + + QQmlComponent component(&engine, testFileUrl("model.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 9); + + QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int))); + QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int))); + QSignalSpy spyCount(model, SIGNAL(countChanged())); + //reload multiple times to test the xml query aborting + QMetaObject::invokeMethod(model, "reload"); + QMetaObject::invokeMethod(model, "reload"); + QCoreApplication::processEvents(); + QMetaObject::invokeMethod(model, "reload"); + QMetaObject::invokeMethod(model, "reload"); + QTRY_COMPARE(spyCount.count(), 1); + QTRY_COMPARE(spyInsert.count(), 1); + QTRY_COMPARE(spyRemove.count(), 1); + + QCOMPARE(spyInsert[0][0].toInt(), 0); + QCOMPARE(spyInsert[0][1].toInt(), 9); + + QCOMPARE(spyRemove[0][0].toInt(), 0); + QCOMPARE(spyRemove[0][1].toInt(), 9); + + delete model; +} + +void tst_qquickxmllistmodel::useKeys() +{ + // If using incremental updates through keys, the model should only + // insert & remove some of the items, instead of throwing everything + // away and causing the view to repaint the whole view. + + QFETCH(QString, oldXml); + QFETCH(int, oldCount); + QFETCH(QString, newXml); + QFETCH(QQmlXmlModelData, newData); + QFETCH(QList<QQuickXmlListRange>, insertRanges); + QFETCH(QList<QQuickXmlListRange>, removeRanges); + + QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + + model->setProperty("xml",oldXml); + QTRY_COMPARE(model->count(), oldCount); + + QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int))); + QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int))); + QSignalSpy spyCount(model, SIGNAL(countChanged())); + + model->setProperty("xml",newXml); + + if (oldCount != newData.count()) { + QTRY_COMPARE(model->count(), newData.count()); + QCOMPARE(spyCount.count(), 1); + } else { + QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0); + QCOMPARE(spyCount.count(), 0); + } + + QList<int> roles = model->roles(); + for (int i=0; i<model->count(); i++) { + for (int j=0; j<roles.count(); j++) + QCOMPARE(model->data(i, roles[j]), newData[i][j]); + } + + QCOMPARE(spyInsert.count(), insertRanges.count()); + for (int i=0; i<spyInsert.count(); i++) { + QCOMPARE(spyInsert[i][0].toInt(), insertRanges[i].first); + QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].second); + } + + QCOMPARE(spyRemove.count(), removeRanges.count()); + for (int i=0; i<spyRemove.count(); i++) { + QCOMPARE(spyRemove[i][0].toInt(), removeRanges[i].first); + QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].second); + } + + delete model; +} + +void tst_qquickxmllistmodel::useKeys_data() +{ + QTest::addColumn<QString>("oldXml"); + QTest::addColumn<int>("oldCount"); + QTest::addColumn<QString>("newXml"); + QTest::addColumn<QQmlXmlModelData>("newData"); + QTest::addColumn<QList<QQuickXmlListRange> >("insertRanges"); + QTest::addColumn<QList<QQuickXmlListRange> >("removeRanges"); + + QQmlXmlModelData modelData; + + QTest::newRow("append 1") + << makeItemXmlAndData("name=A,age=25,sport=Football") << 1 + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(1, 1)) + << QList<QQuickXmlListRange>(); + + QTest::newRow("append multiple") + << makeItemXmlAndData("name=A,age=25,sport=Football") << 1 + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(1, 2)) + << QList<QQuickXmlListRange>(); + + QTest::newRow("insert in different spots") + << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1 + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2)) + << QList<QQuickXmlListRange>(); + + QTest::newRow("insert in middle") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2 + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(1, 2)) + << QList<QQuickXmlListRange>(); + + QTest::newRow("remove first") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2 + << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData) + << modelData + << QList<QQuickXmlListRange>() + << (QList<QQuickXmlListRange>() << qMakePair(0, 1)); + + QTest::newRow("remove last") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2 + << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData) + << modelData + << QList<QQuickXmlListRange>() + << (QList<QQuickXmlListRange>() << qMakePair(1, 1)); + + QTest::newRow("remove from multiple spots") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 5 + << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData) + << modelData + << QList<QQuickXmlListRange>() + << (QList<QQuickXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2)); + + QTest::newRow("remove all") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3 + << makeItemXmlAndData("", &modelData) + << modelData + << QList<QQuickXmlListRange>() + << (QList<QQuickXmlListRange>() << qMakePair(0, 3)); + + QTest::newRow("replace item") + << makeItemXmlAndData("name=A,age=25,sport=Football") << 1 + << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(0, 1)) + << (QList<QQuickXmlListRange>() << qMakePair(0, 1)); + + QTest::newRow("add and remove simultaneously, in different spots") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf") << 4 + << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(1, 1)) + << (QList<QQuickXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2)); + + QTest::newRow("insert at start, remove at end i.e. rss feed") + << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3 + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(0, 2)) + << (QList<QQuickXmlListRange>() << qMakePair(1, 2)); + + QTest::newRow("remove at start, insert at end") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3 + << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(1, 2)) + << (QList<QQuickXmlListRange>() << qMakePair(0, 2)); + + QTest::newRow("all data has changed") + << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2 + << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData) + << modelData + << (QList<QQuickXmlListRange>() << qMakePair(0, 2)) + << (QList<QQuickXmlListRange>() << qMakePair(0, 2)); +} + +void tst_qquickxmllistmodel::noKeysValueChanges() +{ + // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml. + // If a 'sport' value is changed, the model should not be reloaded, + // since 'sport' is not marked as a key. + + QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + + QString xml; + + xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics"); + model->setProperty("xml",xml); + QTRY_COMPARE(model->count(), 2); + + model->setProperty("xml",""); + + QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int))); + QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int))); + QSignalSpy spyCount(model, SIGNAL(countChanged())); + + xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics"); + model->setProperty("xml",xml); + + // wait for the new xml data to be set, and verify no signals were emitted + QTRY_VERIFY(model->data(0, model->roles()[2]).toString() != QLatin1String("Football")); + QCOMPARE(model->data(0, model->roles()[2]).toString(), QLatin1String("AussieRules")); + + QVERIFY(spyInsert.count() == 0); + QVERIFY(spyRemove.count() == 0); + QVERIFY(spyCount.count() == 0); + + QCOMPARE(model->count(), 2); + + delete model; +} + +void tst_qquickxmllistmodel::keysChanged() +{ + // If the key roles change, the next time the data is reloaded, it should + // delete all its data and build a clean model (i.e. same behaviour as + // if no keys are set). + + QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + + QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics"); + model->setProperty("xml",xml); + QTRY_COMPARE(model->count(), 2); + + model->setProperty("xml",""); + + QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int))); + QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int))); + QSignalSpy spyCount(model, SIGNAL(countChanged())); + + QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey")); + model->setProperty("xml",xml); + + QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0); + + QCOMPARE(spyInsert.count(), 1); + QCOMPARE(spyInsert[0][0].toInt(), 0); + QCOMPARE(spyInsert[0][1].toInt(), 2); + + QCOMPARE(spyRemove.count(), 1); + QCOMPARE(spyRemove[0][0].toInt(), 0); + QCOMPARE(spyRemove[0][1].toInt(), 2); + + QCOMPARE(spyCount.count(), 0); + + delete model; +} + +void tst_qquickxmllistmodel::threading() +{ + QFETCH(int, xmlDataCount); + + QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); + + QListModelInterface *m1 = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(m1 != 0); + QListModelInterface *m2 = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(m2 != 0); + QListModelInterface *m3 = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(m3 != 0); + + for (int dataCount=0; dataCount<xmlDataCount; dataCount++) { + + QString data1, data2, data3; + for (int i=0; i<dataCount; i++) { + data1 += "name=A" + QString::number(i) + ",age=1" + QString::number(i) + ",sport=Football;"; + data2 += "name=B" + QString::number(i) + ",age=2" + QString::number(i) + ",sport=Athletics;"; + data3 += "name=C" + QString::number(i) + ",age=3" + QString::number(i) + ",sport=Curling;"; + } + + //Set the xml data multiple times with randomized order and mixed with multiple event loops + //to test the xml query reloading/aborting, the result should be stable. + m1->setProperty("xml",makeItemXmlAndData(data1)); + m2->setProperty("xml",makeItemXmlAndData(data2)); + m3->setProperty("xml",makeItemXmlAndData(data3)); + QCoreApplication::processEvents(); + m2->setProperty("xml",makeItemXmlAndData(data2)); + m1->setProperty("xml",makeItemXmlAndData(data1)); + m2->setProperty("xml",makeItemXmlAndData(data2)); + QCoreApplication::processEvents(); + m3->setProperty("xml",makeItemXmlAndData(data3)); + QCoreApplication::processEvents(); + m2->setProperty("xml",makeItemXmlAndData(data2)); + m1->setProperty("xml",makeItemXmlAndData(data1)); + m2->setProperty("xml",makeItemXmlAndData(data2)); + m3->setProperty("xml",makeItemXmlAndData(data3)); + QCoreApplication::processEvents(); + m2->setProperty("xml",makeItemXmlAndData(data2)); + m3->setProperty("xml",makeItemXmlAndData(data3)); + m3->setProperty("xml",makeItemXmlAndData(data3)); + QCoreApplication::processEvents(); + + QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount); + + for (int i=0; i<dataCount; i++) { + QCOMPARE(m1->data(i, m1->roles()[0]).toString(), QString("A" + QString::number(i))); + QCOMPARE(m1->data(i, m1->roles()[1]).toString(), QString("1" + QString::number(i))); + QCOMPARE(m1->data(i, m1->roles()[2]).toString(), QString("Football")); + + QCOMPARE(m2->data(i, m2->roles()[0]).toString(), QString("B" + QString::number(i))); + QCOMPARE(m2->data(i, m2->roles()[1]).toString(), QString("2" + QString::number(i))); + QCOMPARE(m2->data(i, m2->roles()[2]).toString(), QString("Athletics")); + + QCOMPARE(m3->data(i, m3->roles()[0]).toString(), QString("C" + QString::number(i))); + QCOMPARE(m3->data(i, m3->roles()[1]).toString(), QString("3" + QString::number(i))); + QCOMPARE(m3->data(i, m3->roles()[2]).toString(), QString("Curling")); + } + } + + delete m1; + delete m2; + delete m3; +} + +void tst_qquickxmllistmodel::threading_data() +{ + QTest::addColumn<int>("xmlDataCount"); + + QTest::newRow("1") << 1; + QTest::newRow("2") << 2; + QTest::newRow("10") << 10; +} + +void tst_qquickxmllistmodel::propertyChanges() +{ + QQmlComponent component(&engine, testFileUrl("propertychanges.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + QTRY_COMPARE(model->count(), 9); + + QObject *role = model->findChild<QObject*>("role"); + QVERIFY(role); + + QSignalSpy nameSpy(role, SIGNAL(nameChanged())); + QSignalSpy querySpy(role, SIGNAL(queryChanged())); + QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged())); + + role->setProperty("name","size"); + role->setProperty("query","size/string()"); + role->setProperty("isKey",true); + + QCOMPARE(role->property("name").toString(), QString("size")); + QCOMPARE(role->property("query").toString(), QString("size/string()")); + QVERIFY(role->property("isKey").toBool()); + + QCOMPARE(nameSpy.count(),1); + QCOMPARE(querySpy.count(),1); + QCOMPARE(isKeySpy.count(),1); + + role->setProperty("name","size"); + role->setProperty("query","size/string()"); + role->setProperty("isKey",true); + + QCOMPARE(nameSpy.count(),1); + QCOMPARE(querySpy.count(),1); + QCOMPARE(isKeySpy.count(),1); + + QSignalSpy sourceSpy(model, SIGNAL(sourceChanged())); + QSignalSpy xmlSpy(model, SIGNAL(xmlChanged())); + QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged())); + QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged())); + + model->setProperty("source",QUrl("")); + model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"); + model->setProperty("query","/Pets"); + model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";"); + + QCOMPARE(model->property("source").toUrl(), QUrl("")); + QCOMPARE(model->property("xml").toString(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>")); + QCOMPARE(model->property("query").toString(), QString("/Pets")); + QCOMPARE(model->property("namespaceDeclarations").toString(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";")); + + QTRY_VERIFY(model->count() == 1); + + QCOMPARE(sourceSpy.count(),1); + QCOMPARE(xmlSpy.count(),1); + QCOMPARE(modelQuerySpy.count(),1); + QCOMPARE(namespaceDeclarationsSpy.count(),1); + + model->setProperty("source",QUrl("")); + model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"); + model->setProperty("query","/Pets"); + model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";"); + + QCOMPARE(sourceSpy.count(),1); + QCOMPARE(xmlSpy.count(),1); + QCOMPARE(modelQuerySpy.count(),1); + QCOMPARE(namespaceDeclarationsSpy.count(),1); + + QTRY_VERIFY(model->count() == 1); + delete model; +} + +void tst_qquickxmllistmodel::roleCrash() +{ + // don't crash + QQmlComponent component(&engine, testFileUrl("roleCrash.qml")); + QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create()); + QVERIFY(model != 0); + delete model; +} + +QTEST_MAIN(tst_qquickxmllistmodel) + +#include "tst_qquickxmllistmodel.moc" |