1 /****************************************************************************
3 ** Copyright (C) 2012 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 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtTest/QtTest>
45 #include <QMetaObject>
47 #include <QtTest/qsignalspy.h>
48 #include <QtDeclarative/qdeclarativenetworkaccessmanagerfactory.h>
49 #include <QtNetwork/qnetworkaccessmanager.h>
50 #include <QtNetwork/qnetworkrequest.h>
51 #include <QtCore/qtimer.h>
52 #include <QtCore/qfile.h>
53 #include <QtCore/qtemporaryfile.h>
54 #include "../../shared/util.h"
55 #include <private/qdeclarativeengine_p.h>
57 #include <QtDeclarative/qdeclarativeengine.h>
58 #include <QtDeclarative/qdeclarativecomponent.h>
59 #include <private/qlistmodelinterface_p.h>
60 #include "../../../../src/imports/xmllistmodel/qdeclarativexmllistmodel_p.h"
62 typedef QPair<int, int> QDeclarativeXmlListRange;
63 typedef QList<QVariantList> QDeclarativeXmlModelData;
65 Q_DECLARE_METATYPE(QList<QDeclarativeXmlListRange>)
66 Q_DECLARE_METATYPE(QDeclarativeXmlModelData)
67 Q_DECLARE_METATYPE(QDeclarativeXmlListModel::Status)
69 class tst_qdeclarativexmllistmodel : public QDeclarativeDataTest
74 tst_qdeclarativexmllistmodel() {}
78 QDeclarativeDataTest::initTestCase();
79 qRegisterMetaType<QDeclarativeXmlListModel::Status>();
84 void testTypes_data();
89 void uniqueRoleNames();
100 void noKeysValueChanges();
103 void threading_data();
104 void propertyChanges();
109 QString errorString(QListModelInterface* model) {
111 QMetaObject::invokeMethod(model, "errorString", Q_RETURN_ARG(QString, ret));
115 QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
121 if (!data.isEmpty()) {
122 QStringList items = data.split(";");
123 foreach(const QString &item, items) {
126 QVariantList variants;
127 xml += QLatin1String("<item>");
128 QStringList fields = item.split(",");
129 foreach(const QString &field, fields) {
130 QStringList values = field.split("=");
131 if (values.count() != 2) {
132 qWarning() << "makeItemXmlAndData: invalid field:" << field;
135 xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
139 int number = values[1].toInt(&isNum);
143 variants << values[1];
145 xml += QLatin1String("</item>");
147 modelData->append(variants);
151 QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
152 return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
155 QDeclarativeEngine engine;
158 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
162 QVariantMap lastSentHeaders;
165 QNetworkAccessManager *create(QObject *parent);
168 class CustomNetworkAccessManager : public QNetworkAccessManager
172 CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
173 : QNetworkAccessManager(parent), m_factory(factory) {}
176 QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
180 foreach (const QString &header, req.rawHeaderList())
181 map[header] = req.rawHeader(header.toUtf8());
182 m_factory->lastSentHeaders = map;
184 return QNetworkAccessManager::createRequest(op, req, outgoingData);
187 QPointer<CustomNetworkAccessManagerFactory> m_factory;
190 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
192 return new CustomNetworkAccessManager(this, parent);
196 void tst_qdeclarativexmllistmodel::buildModel()
198 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
199 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
201 QTRY_COMPARE(model->count(), 9);
203 QCOMPARE(model->data(3, Qt::UserRole).toString(), QLatin1String("Spot"));
204 QCOMPARE(model->data(3, Qt::UserRole+1).toString(), QLatin1String("Dog"));
205 QCOMPARE(model->data(3, Qt::UserRole+2).toInt(), 9);
206 QCOMPARE(model->data(3, Qt::UserRole+3).toString(), QLatin1String("Medium"));
211 void tst_qdeclarativexmllistmodel::testTypes()
213 QFETCH(QString, xml);
214 QFETCH(QString, roleName);
215 QFETCH(QVariant, expectedValue);
217 QDeclarativeComponent component(&engine, testFileUrl("testtypes.qml"));
218 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
220 model->setProperty("xml",xml.toUtf8());
221 QMetaObject::invokeMethod(model, "reload");
222 QTRY_COMPARE(model->count(), 1);
225 foreach (int i, model->roles()) {
226 if (model->toString(i) == roleName) {
233 if (expectedValue.toString() == "nan")
234 QVERIFY(qIsNaN(model->data(0, role).toDouble()));
236 QCOMPARE(model->data(0, role), expectedValue);
241 void tst_qdeclarativexmllistmodel::testTypes_data()
243 QTest::addColumn<QString>("xml");
244 QTest::addColumn<QString>("roleName");
245 QTest::addColumn<QVariant>("expectedValue");
247 QTest::newRow("missing string field") << "<data></data>"
248 << "stringValue" << QVariant("");
249 QTest::newRow("empty string") << "<data><a-string></a-string></data>"
250 << "stringValue" << QVariant("");
251 QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
252 << "stringValue" << QVariant("5");
253 QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
254 << "stringValue" << QVariant("abc def g");
256 QTest::newRow("missing number field") << "<data></data>"
257 << "numberValue" << QVariant("");
258 double nan = qQNaN();
259 QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
260 << "numberValue" << QVariant(nan);
261 QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
262 << "numberValue" << QVariant(nan);
263 QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
264 << "numberValue" << QVariant("-1");
265 QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
266 << "numberValue" << QVariant("-1.5");
267 QTest::newRow("0") << "<data><a-number>0</a-number></data>"
268 << "numberValue" << QVariant("0");
269 QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
270 << "numberValue" << QVariant("1");
271 QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
272 << "numberValue" << QVariant("1.5");
275 void tst_qdeclarativexmllistmodel::cdata()
277 QDeclarativeComponent component(&engine, testFileUrl("recipes.qml"));
278 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
280 QTRY_COMPARE(model->count(), 5);
282 QVERIFY(model->data(2, Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
287 void tst_qdeclarativexmllistmodel::attributes()
289 QDeclarativeComponent component(&engine, testFileUrl("recipes.qml"));
290 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
292 QTRY_COMPARE(model->count(), 5);
293 QCOMPARE(model->data(2, Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
298 void tst_qdeclarativexmllistmodel::roles()
300 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
301 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
303 QTRY_COMPARE(model->count(), 9);
305 QList<int> roles = model->roles();
306 QCOMPARE(roles.count(), 4);
307 QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
308 QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
309 QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
310 QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
315 void tst_qdeclarativexmllistmodel::roleErrors()
317 QDeclarativeComponent component(&engine, testFileUrl("roleErrors.qml"));
318 QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":7:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
319 QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":10:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
321 //### make sure we receive all expected warning messages.
322 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
324 QTRY_COMPARE(model->count(), 9);
327 //### should any of these return valid values?
328 QCOMPARE(model->data(3, Qt::UserRole), QVariant());
329 QCOMPARE(model->data(3, Qt::UserRole+1), QVariant());
330 QCOMPARE(model->data(3, Qt::UserRole+2), QVariant());
332 QEXPECT_FAIL("", "QTBUG-10797", Continue);
333 QCOMPARE(model->data(3, Qt::UserRole+3), QVariant());
338 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
340 QDeclarativeComponent component(&engine, testFileUrl("unique.qml"));
341 QTest::ignoreMessage(QtWarningMsg, (testFileUrl("unique.qml").toString() + ":8:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
342 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
344 QTRY_COMPARE(model->count(), 9);
346 QList<int> roles = model->roles();
347 QCOMPARE(roles.count(), 1);
353 void tst_qdeclarativexmllistmodel::xml()
355 QFETCH(QString, xml);
358 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
359 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
361 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
362 QVERIFY(errorString(model).isEmpty());
363 QCOMPARE(model->property("progress").toDouble(), qreal(0.0));
364 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
365 QDeclarativeXmlListModel::Loading);
366 QTRY_COMPARE(spy.count(), 1); spy.clear();
368 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
369 QDeclarativeXmlListModel::Ready);
370 QVERIFY(errorString(model).isEmpty());
371 QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
372 QCOMPARE(model->count(), 9);
374 // if xml is empty (i.e. clearing) it won't have any effect if a source is set
376 model->setProperty("source",QUrl());
377 model->setProperty("xml",xml);
378 QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); // immediately goes to 1.0 if using setXml()
379 QTRY_COMPARE(spy.count(), 1); spy.clear();
380 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
381 QDeclarativeXmlListModel::Loading);
382 QTRY_COMPARE(spy.count(), 1); spy.clear();
384 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
385 QDeclarativeXmlListModel::Null);
387 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
388 QDeclarativeXmlListModel::Ready);
389 QVERIFY(errorString(model).isEmpty());
390 QCOMPARE(model->count(), count);
395 void tst_qdeclarativexmllistmodel::xml_data()
397 QTest::addColumn<QString>("xml");
398 QTest::addColumn<int>("count");
400 QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
401 QTest::newRow("empty xml") << "" << 0;
402 QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
405 void tst_qdeclarativexmllistmodel::headers()
407 // ensure the QNetworkAccessManagers created for this test are immediately deleted
408 QDeclarativeEngine qmlEng;
410 CustomNetworkAccessManagerFactory factory;
411 qmlEng.setNetworkAccessManagerFactory(&factory);
413 QDeclarativeComponent component(&qmlEng, testFileUrl("model.qml"));
414 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
416 QTRY_COMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
417 QDeclarativeXmlListModel::Ready);
419 QVariantMap expectedHeaders;
420 expectedHeaders["Accept"] = "application/xml,*/*";
422 QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
423 foreach (const QString &header, expectedHeaders.keys()) {
424 QVERIFY(factory.lastSentHeaders.contains(header));
425 QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
431 void tst_qdeclarativexmllistmodel::source()
433 QFETCH(QUrl, source);
435 QFETCH(QDeclarativeXmlListModel::Status, status);
437 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
438 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
439 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
441 QVERIFY(errorString(model).isEmpty());
442 QCOMPARE(model->property("progress").toDouble(), qreal(0.0));
443 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
444 QDeclarativeXmlListModel::Loading);
445 QTRY_COMPARE(spy.count(), 1); spy.clear();
446 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
447 QDeclarativeXmlListModel::Ready);
448 QVERIFY(errorString(model).isEmpty());
449 QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
450 QCOMPARE(model->count(), 9);
452 model->setProperty("source",source);
453 if (model->property("source").toString().isEmpty())
454 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
455 QDeclarativeXmlListModel::Null);
456 QCOMPARE(model->property("progress").toDouble(), qreal(0.0));
457 QTRY_COMPARE(spy.count(), 1); spy.clear();
458 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
459 QDeclarativeXmlListModel::Loading);
460 QVERIFY(errorString(model).isEmpty());
464 timer.setSingleShot(true);
465 connect(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)), &loop, SLOT(quit()));
466 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
470 if (spy.count() == 0 && status != QDeclarativeXmlListModel::Ready) {
471 qWarning("QDeclarativeXmlListModel invalid source test timed out");
473 QCOMPARE(spy.count(), 1); spy.clear();
476 QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")), status);
477 QCOMPARE(model->count(), count);
479 if (status == QDeclarativeXmlListModel::Ready)
480 QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
482 QCOMPARE(errorString(model).isEmpty(), status == QDeclarativeXmlListModel::Ready);
487 void tst_qdeclarativexmllistmodel::source_data()
489 QTest::addColumn<QUrl>("source");
490 QTest::addColumn<int>("count");
491 QTest::addColumn<QDeclarativeXmlListModel::Status>("status");
493 QTest::newRow("valid") << testFileUrl("model2.xml") << 2
494 << QDeclarativeXmlListModel::Ready;
495 QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0
496 << QDeclarativeXmlListModel::Error;
499 QTemporaryFile *temp = new QTemporaryFile(this);
501 QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0
502 << QDeclarativeXmlListModel::Ready;
506 void tst_qdeclarativexmllistmodel::data()
508 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
509 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
512 for (int i=0; i<9; i++) {
513 for (int j=0; j<model->roles().count(); j++) {
514 QCOMPARE(model->data(i, j), QVariant());
517 QTRY_COMPARE(model->count(), 9);
522 void tst_qdeclarativexmllistmodel::get()
524 QDeclarativeComponent component(&engine, testFileUrl("get.qml"));
525 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
529 QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
530 QCOMPARE(model->property("preTest").toBool(), true);
532 QTRY_COMPARE(model->count(), 9);
534 QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
535 QCOMPARE(model->property("postTest").toBool(), true);
540 void tst_qdeclarativexmllistmodel::reload()
542 // If no keys are used, the model should be rebuilt from scratch when
543 // reload() is called.
545 QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
546 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
548 QTRY_COMPARE(model->count(), 9);
550 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
551 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
552 QSignalSpy spyCount(model, SIGNAL(countChanged()));
553 //reload multiple times to test the xml query aborting
554 QMetaObject::invokeMethod(model, "reload");
555 QMetaObject::invokeMethod(model, "reload");
556 QCoreApplication::processEvents();
557 QMetaObject::invokeMethod(model, "reload");
558 QMetaObject::invokeMethod(model, "reload");
559 QTRY_COMPARE(spyCount.count(), 1);
560 QTRY_COMPARE(spyInsert.count(), 1);
561 QTRY_COMPARE(spyRemove.count(), 1);
563 QCOMPARE(spyInsert[0][0].toInt(), 0);
564 QCOMPARE(spyInsert[0][1].toInt(), 9);
566 QCOMPARE(spyRemove[0][0].toInt(), 0);
567 QCOMPARE(spyRemove[0][1].toInt(), 9);
572 void tst_qdeclarativexmllistmodel::useKeys()
574 // If using incremental updates through keys, the model should only
575 // insert & remove some of the items, instead of throwing everything
576 // away and causing the view to repaint the whole view.
578 QFETCH(QString, oldXml);
579 QFETCH(int, oldCount);
580 QFETCH(QString, newXml);
581 QFETCH(QDeclarativeXmlModelData, newData);
582 QFETCH(QList<QDeclarativeXmlListRange>, insertRanges);
583 QFETCH(QList<QDeclarativeXmlListRange>, removeRanges);
585 QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
586 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
589 model->setProperty("xml",oldXml);
590 QTRY_COMPARE(model->count(), oldCount);
592 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
593 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
594 QSignalSpy spyCount(model, SIGNAL(countChanged()));
596 model->setProperty("xml",newXml);
598 if (oldCount != newData.count()) {
599 QTRY_COMPARE(model->count(), newData.count());
600 QCOMPARE(spyCount.count(), 1);
602 QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
603 QCOMPARE(spyCount.count(), 0);
606 QList<int> roles = model->roles();
607 for (int i=0; i<model->count(); i++) {
608 for (int j=0; j<roles.count(); j++)
609 QCOMPARE(model->data(i, roles[j]), newData[i][j]);
612 QCOMPARE(spyInsert.count(), insertRanges.count());
613 for (int i=0; i<spyInsert.count(); i++) {
614 QCOMPARE(spyInsert[i][0].toInt(), insertRanges[i].first);
615 QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].second);
618 QCOMPARE(spyRemove.count(), removeRanges.count());
619 for (int i=0; i<spyRemove.count(); i++) {
620 QCOMPARE(spyRemove[i][0].toInt(), removeRanges[i].first);
621 QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].second);
627 void tst_qdeclarativexmllistmodel::useKeys_data()
629 QTest::addColumn<QString>("oldXml");
630 QTest::addColumn<int>("oldCount");
631 QTest::addColumn<QString>("newXml");
632 QTest::addColumn<QDeclarativeXmlModelData>("newData");
633 QTest::addColumn<QList<QDeclarativeXmlListRange> >("insertRanges");
634 QTest::addColumn<QList<QDeclarativeXmlListRange> >("removeRanges");
636 QDeclarativeXmlModelData modelData;
638 QTest::newRow("append 1")
639 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
640 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData)
642 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
643 << QList<QDeclarativeXmlListRange>();
645 QTest::newRow("append multiple")
646 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
647 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
649 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
650 << QList<QDeclarativeXmlListRange>();
652 QTest::newRow("insert in different spots")
653 << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1
654 << 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)
656 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
657 << QList<QDeclarativeXmlListRange>();
659 QTest::newRow("insert in middle")
660 << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2
661 << 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)
663 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
664 << QList<QDeclarativeXmlListRange>();
666 QTest::newRow("remove first")
667 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
668 << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData)
670 << QList<QDeclarativeXmlListRange>()
671 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
673 QTest::newRow("remove last")
674 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
675 << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData)
677 << QList<QDeclarativeXmlListRange>()
678 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1));
680 QTest::newRow("remove from multiple spots")
681 << 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
682 << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData)
684 << QList<QDeclarativeXmlListRange>()
685 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
687 QTest::newRow("remove all")
688 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
689 << makeItemXmlAndData("", &modelData)
691 << QList<QDeclarativeXmlListRange>()
692 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 3));
694 QTest::newRow("replace item")
695 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
696 << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
698 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1))
699 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
701 QTest::newRow("add and remove simultaneously, in different spots")
702 << 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
703 << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData)
705 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
706 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
708 QTest::newRow("insert at start, remove at end i.e. rss feed")
709 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3
710 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
712 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
713 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2));
715 QTest::newRow("remove at start, insert at end")
716 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
717 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData)
719 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
720 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
722 QTest::newRow("all data has changed")
723 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2
724 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
726 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
727 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
730 void tst_qdeclarativexmllistmodel::noKeysValueChanges()
732 // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml.
733 // If a 'sport' value is changed, the model should not be reloaded,
734 // since 'sport' is not marked as a key.
736 QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
737 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
742 xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
743 model->setProperty("xml",xml);
744 QTRY_COMPARE(model->count(), 2);
746 model->setProperty("xml","");
748 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
749 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
750 QSignalSpy spyCount(model, SIGNAL(countChanged()));
752 xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
753 model->setProperty("xml",xml);
755 // wait for the new xml data to be set, and verify no signals were emitted
756 QTRY_VERIFY(model->data(0, model->roles()[2]).toString() != QLatin1String("Football"));
757 QCOMPARE(model->data(0, model->roles()[2]).toString(), QLatin1String("AussieRules"));
759 QVERIFY(spyInsert.count() == 0);
760 QVERIFY(spyRemove.count() == 0);
761 QVERIFY(spyCount.count() == 0);
763 QCOMPARE(model->count(), 2);
768 void tst_qdeclarativexmllistmodel::keysChanged()
770 // If the key roles change, the next time the data is reloaded, it should
771 // delete all its data and build a clean model (i.e. same behaviour as
772 // if no keys are set).
774 QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
775 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
778 QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
779 model->setProperty("xml",xml);
780 QTRY_COMPARE(model->count(), 2);
782 model->setProperty("xml","");
784 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
785 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
786 QSignalSpy spyCount(model, SIGNAL(countChanged()));
788 QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
789 model->setProperty("xml",xml);
791 QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
793 QCOMPARE(spyInsert.count(), 1);
794 QCOMPARE(spyInsert[0][0].toInt(), 0);
795 QCOMPARE(spyInsert[0][1].toInt(), 2);
797 QCOMPARE(spyRemove.count(), 1);
798 QCOMPARE(spyRemove[0][0].toInt(), 0);
799 QCOMPARE(spyRemove[0][1].toInt(), 2);
801 QCOMPARE(spyCount.count(), 0);
806 void tst_qdeclarativexmllistmodel::threading()
808 QFETCH(int, xmlDataCount);
810 QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
812 QListModelInterface *m1 = qobject_cast<QListModelInterface*>(component.create());
814 QListModelInterface *m2 = qobject_cast<QListModelInterface*>(component.create());
816 QListModelInterface *m3 = qobject_cast<QListModelInterface*>(component.create());
819 for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
821 QString data1, data2, data3;
822 for (int i=0; i<dataCount; i++) {
823 data1 += "name=A" + QString::number(i) + ",age=1" + QString::number(i) + ",sport=Football;";
824 data2 += "name=B" + QString::number(i) + ",age=2" + QString::number(i) + ",sport=Athletics;";
825 data3 += "name=C" + QString::number(i) + ",age=3" + QString::number(i) + ",sport=Curling;";
828 //Set the xml data multiple times with randomized order and mixed with multiple event loops
829 //to test the xml query reloading/aborting, the result should be stable.
830 m1->setProperty("xml",makeItemXmlAndData(data1));
831 m2->setProperty("xml",makeItemXmlAndData(data2));
832 m3->setProperty("xml",makeItemXmlAndData(data3));
833 QCoreApplication::processEvents();
834 m2->setProperty("xml",makeItemXmlAndData(data2));
835 m1->setProperty("xml",makeItemXmlAndData(data1));
836 m2->setProperty("xml",makeItemXmlAndData(data2));
837 QCoreApplication::processEvents();
838 m3->setProperty("xml",makeItemXmlAndData(data3));
839 QCoreApplication::processEvents();
840 m2->setProperty("xml",makeItemXmlAndData(data2));
841 m1->setProperty("xml",makeItemXmlAndData(data1));
842 m2->setProperty("xml",makeItemXmlAndData(data2));
843 m3->setProperty("xml",makeItemXmlAndData(data3));
844 QCoreApplication::processEvents();
845 m2->setProperty("xml",makeItemXmlAndData(data2));
846 m3->setProperty("xml",makeItemXmlAndData(data3));
847 m3->setProperty("xml",makeItemXmlAndData(data3));
848 QCoreApplication::processEvents();
850 QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount);
852 for (int i=0; i<dataCount; i++) {
853 QCOMPARE(m1->data(i, m1->roles()[0]).toString(), QString("A" + QString::number(i)));
854 QCOMPARE(m1->data(i, m1->roles()[1]).toString(), QString("1" + QString::number(i)));
855 QCOMPARE(m1->data(i, m1->roles()[2]).toString(), QString("Football"));
857 QCOMPARE(m2->data(i, m2->roles()[0]).toString(), QString("B" + QString::number(i)));
858 QCOMPARE(m2->data(i, m2->roles()[1]).toString(), QString("2" + QString::number(i)));
859 QCOMPARE(m2->data(i, m2->roles()[2]).toString(), QString("Athletics"));
861 QCOMPARE(m3->data(i, m3->roles()[0]).toString(), QString("C" + QString::number(i)));
862 QCOMPARE(m3->data(i, m3->roles()[1]).toString(), QString("3" + QString::number(i)));
863 QCOMPARE(m3->data(i, m3->roles()[2]).toString(), QString("Curling"));
872 void tst_qdeclarativexmllistmodel::threading_data()
874 QTest::addColumn<int>("xmlDataCount");
876 QTest::newRow("1") << 1;
877 QTest::newRow("2") << 2;
878 QTest::newRow("10") << 10;
881 void tst_qdeclarativexmllistmodel::propertyChanges()
883 QDeclarativeComponent component(&engine, testFileUrl("propertychanges.qml"));
884 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
886 QTRY_COMPARE(model->count(), 9);
888 QObject *role = model->findChild<QObject*>("role");
891 QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
892 QSignalSpy querySpy(role, SIGNAL(queryChanged()));
893 QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
895 role->setProperty("name","size");
896 role->setProperty("query","size/string()");
897 role->setProperty("isKey",true);
899 QCOMPARE(role->property("name").toString(), QString("size"));
900 QCOMPARE(role->property("query").toString(), QString("size/string()"));
901 QVERIFY(role->property("isKey").toBool());
903 QCOMPARE(nameSpy.count(),1);
904 QCOMPARE(querySpy.count(),1);
905 QCOMPARE(isKeySpy.count(),1);
907 role->setProperty("name","size");
908 role->setProperty("query","size/string()");
909 role->setProperty("isKey",true);
911 QCOMPARE(nameSpy.count(),1);
912 QCOMPARE(querySpy.count(),1);
913 QCOMPARE(isKeySpy.count(),1);
915 QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
916 QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
917 QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
918 QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
920 model->setProperty("source",QUrl(""));
921 model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
922 model->setProperty("query","/Pets");
923 model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";");
925 QCOMPARE(model->property("source").toUrl(), QUrl(""));
926 QCOMPARE(model->property("xml").toString(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
927 QCOMPARE(model->property("query").toString(), QString("/Pets"));
928 QCOMPARE(model->property("namespaceDeclarations").toString(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
930 QTRY_VERIFY(model->count() == 1);
932 QCOMPARE(sourceSpy.count(),1);
933 QCOMPARE(xmlSpy.count(),1);
934 QCOMPARE(modelQuerySpy.count(),1);
935 QCOMPARE(namespaceDeclarationsSpy.count(),1);
937 model->setProperty("source",QUrl(""));
938 model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
939 model->setProperty("query","/Pets");
940 model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";");
942 QCOMPARE(sourceSpy.count(),1);
943 QCOMPARE(xmlSpy.count(),1);
944 QCOMPARE(modelQuerySpy.count(),1);
945 QCOMPARE(namespaceDeclarationsSpy.count(),1);
947 QTRY_VERIFY(model->count() == 1);
951 void tst_qdeclarativexmllistmodel::roleCrash()
954 QDeclarativeComponent component(&engine, testFileUrl("roleCrash.qml"));
955 QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
960 QTEST_MAIN(tst_qdeclarativexmllistmodel)
962 #include "tst_qdeclarativexmllistmodel.moc"