1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** 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 ****************************************************************************/
41 #include <private/qdeclarativeengine_p.h>
43 #include <QtTest/QtTest>
48 #include <QtTest/qsignalspy.h>
49 #include <QtDeclarative/qdeclarativenetworkaccessmanagerfactory.h>
50 #include <QtNetwork/qnetworkaccessmanager.h>
51 #include <QtNetwork/qnetworkrequest.h>
52 #include <QtCore/qtimer.h>
53 #include <QtCore/qfile.h>
54 #include <QtCore/qtemporaryfile.h>
55 #include "../shared/util.h"
57 #include <QtDeclarative/qdeclarativeengine.h>
58 #include <QtDeclarative/qdeclarativecomponent.h>
59 #include <private/qdeclarativexmllistmodel_p.h>
60 #include "../../../shared/util.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 QObject
74 tst_qdeclarativexmllistmodel() {}
78 qRegisterMetaType<QDeclarativeXmlListModel::Status>("QDeclarativeXmlListModel::Status");
83 void testTypes_data();
88 void uniqueRoleNames();
99 void noKeysValueChanges();
102 void threading_data();
103 void propertyChanges();
108 QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
114 if (!data.isEmpty()) {
115 QStringList items = data.split(";");
116 foreach(const QString &item, items) {
119 QVariantList variants;
120 xml += QLatin1String("<item>");
121 QStringList fields = item.split(",");
122 foreach(const QString &field, fields) {
123 QStringList values = field.split("=");
124 if (values.count() != 2) {
125 qWarning() << "makeItemXmlAndData: invalid field:" << field;
128 xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
132 int number = values[1].toInt(&isNum);
136 variants << values[1];
138 xml += QLatin1String("</item>");
140 modelData->append(variants);
144 QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
145 return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
148 QDeclarativeEngine engine;
151 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
155 QVariantMap lastSentHeaders;
158 QNetworkAccessManager *create(QObject *parent);
161 class CustomNetworkAccessManager : public QNetworkAccessManager
165 CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
166 : QNetworkAccessManager(parent), m_factory(factory) {}
169 QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
173 foreach (const QString &header, req.rawHeaderList())
174 map[header] = req.rawHeader(header.toUtf8());
175 m_factory->lastSentHeaders = map;
177 return QNetworkAccessManager::createRequest(op, req, outgoingData);
180 QPointer<CustomNetworkAccessManagerFactory> m_factory;
183 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
185 return new CustomNetworkAccessManager(this, parent);
189 void tst_qdeclarativexmllistmodel::buildModel()
191 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
192 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
194 QTRY_COMPARE(model->count(), 9);
197 roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
198 QHash<int, QVariant> data = model->data(3, roles);
199 QVERIFY(data.count() == 4);
200 QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Spot"));
201 QCOMPARE(data.value(Qt::UserRole+1).toString(), QLatin1String("Dog"));
202 QCOMPARE(data.value(Qt::UserRole+2).toInt(), 9);
203 QCOMPARE(data.value(Qt::UserRole+3).toString(), QLatin1String("Medium"));
208 void tst_qdeclarativexmllistmodel::testTypes()
210 QFETCH(QString, xml);
211 QFETCH(QString, roleName);
212 QFETCH(QVariant, expectedValue);
214 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("testtypes.qml")));
215 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
217 model->setXml(xml.toUtf8());
219 QTRY_COMPARE(model->count(), 1);
222 foreach (int i, model->roles()) {
223 if (model->toString(i) == roleName) {
230 if (expectedValue.toString() == "nan")
231 QVERIFY(qIsNaN(model->data(0, role).toDouble()));
233 QCOMPARE(model->data(0, role), expectedValue);
238 void tst_qdeclarativexmllistmodel::testTypes_data()
240 QTest::addColumn<QString>("xml");
241 QTest::addColumn<QString>("roleName");
242 QTest::addColumn<QVariant>("expectedValue");
244 QTest::newRow("missing string field") << "<data></data>"
245 << "stringValue" << QVariant("");
246 QTest::newRow("empty string") << "<data><a-string></a-string></data>"
247 << "stringValue" << QVariant("");
248 QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
249 << "stringValue" << QVariant("5");
250 QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
251 << "stringValue" << QVariant("abc def g");
253 QTest::newRow("missing number field") << "<data></data>"
254 << "numberValue" << QVariant("");
255 double nan = qQNaN();
256 QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
257 << "numberValue" << QVariant(nan);
258 QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
259 << "numberValue" << QVariant(nan);
260 QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
261 << "numberValue" << QVariant("-1");
262 QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
263 << "numberValue" << QVariant("-1.5");
264 QTest::newRow("0") << "<data><a-number>0</a-number></data>"
265 << "numberValue" << QVariant("0");
266 QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
267 << "numberValue" << QVariant("1");
268 QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
269 << "numberValue" << QVariant("1.5");
272 void tst_qdeclarativexmllistmodel::cdata()
274 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("recipes.qml")));
275 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
277 QTRY_COMPARE(model->count(), 5);
280 roles << Qt::UserRole + 2;
281 QHash<int, QVariant> data = model->data(2, roles);
282 QVERIFY(data.count() == 1);
283 QVERIFY(data.value(Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
288 void tst_qdeclarativexmllistmodel::attributes()
290 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("recipes.qml")));
291 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
293 QTRY_COMPARE(model->count(), 5);
295 roles << Qt::UserRole;
296 QHash<int, QVariant> data = model->data(2, roles);
297 QVERIFY(data.count() == 1);
298 QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
303 void tst_qdeclarativexmllistmodel::roles()
305 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
306 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
308 QTRY_COMPARE(model->count(), 9);
310 QList<int> roles = model->roles();
311 QCOMPARE(roles.count(), 4);
312 QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
313 QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
314 QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
315 QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
320 void tst_qdeclarativexmllistmodel::roleErrors()
322 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("roleErrors.qml")));
323 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(TESTDATA("roleErrors.qml")).toString() + ":6:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
324 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(TESTDATA("roleErrors.qml")).toString() + ":9:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
326 //### make sure we receive all expected warning messages.
327 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
329 QTRY_COMPARE(model->count(), 9);
332 roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
333 QHash<int, QVariant> data = model->data(3, roles);
334 QVERIFY(data.count() == 4);
336 //### should any of these return valid values?
337 QCOMPARE(data.value(Qt::UserRole), QVariant());
338 QCOMPARE(data.value(Qt::UserRole+1), QVariant());
339 QCOMPARE(data.value(Qt::UserRole+2), QVariant());
341 QEXPECT_FAIL("", "QTBUG-10797", Continue);
342 QCOMPARE(data.value(Qt::UserRole+3), QVariant());
347 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
349 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("unique.qml")));
350 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(TESTDATA("unique.qml")).toString() + ":7:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
351 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
353 QTRY_COMPARE(model->count(), 9);
355 QList<int> roles = model->roles();
356 QCOMPARE(roles.count(), 1);
362 void tst_qdeclarativexmllistmodel::xml()
364 QFETCH(QString, xml);
367 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
368 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
369 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
371 QVERIFY(model->errorString().isEmpty());
372 QCOMPARE(model->progress(), qreal(0.0));
373 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
374 QTRY_COMPARE(spy.count(), 1); spy.clear();
375 QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
376 QVERIFY(model->errorString().isEmpty());
377 QCOMPARE(model->progress(), qreal(1.0));
378 QCOMPARE(model->count(), 9);
380 // if xml is empty (i.e. clearing) it won't have any effect if a source is set
382 model->setSource(QUrl());
384 QCOMPARE(model->progress(), qreal(1.0)); // immediately goes to 1.0 if using setXml()
385 QTRY_COMPARE(spy.count(), 1); spy.clear();
386 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
387 QTRY_COMPARE(spy.count(), 1); spy.clear();
389 QCOMPARE(model->status(), QDeclarativeXmlListModel::Null);
391 QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
392 QVERIFY(model->errorString().isEmpty());
393 QCOMPARE(model->count(), count);
398 void tst_qdeclarativexmllistmodel::xml_data()
400 QTest::addColumn<QString>("xml");
401 QTest::addColumn<int>("count");
403 QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
404 QTest::newRow("empty xml") << "" << 0;
405 QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
408 void tst_qdeclarativexmllistmodel::headers()
410 // ensure the QNetworkAccessManagers created for this test are immediately deleted
411 QDeclarativeEngine qmlEng;
413 CustomNetworkAccessManagerFactory factory;
414 qmlEng.setNetworkAccessManagerFactory(&factory);
416 QDeclarativeComponent component(&qmlEng, QUrl::fromLocalFile(TESTDATA("model.qml")));
417 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
419 QTRY_COMPARE(model->status(), QDeclarativeXmlListModel::Ready);
421 QVariantMap expectedHeaders;
422 expectedHeaders["Accept"] = "application/xml,*/*";
424 QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
425 foreach (const QString &header, expectedHeaders.keys()) {
426 QVERIFY(factory.lastSentHeaders.contains(header));
427 QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
433 void tst_qdeclarativexmllistmodel::source()
435 QFETCH(QUrl, source);
437 QFETCH(QDeclarativeXmlListModel::Status, status);
439 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
440 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
441 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
443 QVERIFY(model->errorString().isEmpty());
444 QCOMPARE(model->progress(), qreal(0.0));
445 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
446 QTRY_COMPARE(spy.count(), 1); spy.clear();
447 QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
448 QVERIFY(model->errorString().isEmpty());
449 QCOMPARE(model->progress(), qreal(1.0));
450 QCOMPARE(model->count(), 9);
452 model->setSource(source);
453 if (model->source().isEmpty())
454 QCOMPARE(model->status(), QDeclarativeXmlListModel::Null);
455 QCOMPARE(model->progress(), qreal(0.0));
456 QTRY_COMPARE(spy.count(), 1); spy.clear();
457 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
458 QVERIFY(model->errorString().isEmpty());
462 timer.setSingleShot(true);
463 connect(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)), &loop, SLOT(quit()));
464 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
468 if (spy.count() == 0 && status != QDeclarativeXmlListModel::Ready) {
469 qWarning("QDeclarativeXmlListModel invalid source test timed out");
471 QCOMPARE(spy.count(), 1); spy.clear();
474 QCOMPARE(model->status(), status);
475 QCOMPARE(model->count(), count);
477 if (status == QDeclarativeXmlListModel::Ready)
478 QCOMPARE(model->progress(), qreal(1.0));
480 QCOMPARE(model->errorString().isEmpty(), status == QDeclarativeXmlListModel::Ready);
485 void tst_qdeclarativexmllistmodel::source_data()
487 QTest::addColumn<QUrl>("source");
488 QTest::addColumn<int>("count");
489 QTest::addColumn<QDeclarativeXmlListModel::Status>("status");
491 QTest::newRow("valid") << QUrl::fromLocalFile(TESTDATA("model2.xml")) << 2 << QDeclarativeXmlListModel::Ready;
492 QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 << QDeclarativeXmlListModel::Error;
495 QTemporaryFile *temp = new QTemporaryFile(this);
497 QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QDeclarativeXmlListModel::Ready;
501 void tst_qdeclarativexmllistmodel::data()
503 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
504 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
507 QHash<int,QVariant> blank;
508 for (int i=0; i<model->roles().count(); i++)
509 blank.insert(model->roles()[i], QVariant());
510 for (int i=0; i<9; i++) {
511 QCOMPARE(model->data(i, model->roles()), blank);
512 for (int j=0; j<model->roles().count(); j++) {
513 QCOMPARE(model->data(i, j), QVariant());
516 QTRY_COMPARE(model->count(), 9);
521 void tst_qdeclarativexmllistmodel::get()
523 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("get.qml")));
524 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
528 QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
529 QCOMPARE(model->property("preTest").toBool(), true);
531 QTRY_COMPARE(model->count(), 9);
533 QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
534 QCOMPARE(model->property("postTest").toBool(), true);
539 void tst_qdeclarativexmllistmodel::reload()
541 // If no keys are used, the model should be rebuilt from scratch when
542 // reload() is called.
544 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
545 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
547 QTRY_COMPARE(model->count(), 9);
549 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
550 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
551 QSignalSpy spyCount(model, SIGNAL(countChanged()));
553 //reload multiple times to test the xml query aborting
556 QCoreApplication::processEvents();
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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
586 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
589 model->setXml(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->setXml(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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
737 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
742 xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
744 QTRY_COMPARE(model->count(), 2);
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");
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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
775 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
778 QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
780 QTRY_COMPARE(model->count(), 2);
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"));
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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
812 QDeclarativeXmlListModel *m1 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
814 QDeclarativeXmlListModel *m2 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
816 QDeclarativeXmlListModel *m3 = qobject_cast<QDeclarativeXmlListModel*>(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->setXml(makeItemXmlAndData(data1));
831 m2->setXml(makeItemXmlAndData(data2));
832 m3->setXml(makeItemXmlAndData(data3));
833 QCoreApplication::processEvents();
834 m2->setXml(makeItemXmlAndData(data2));
835 m1->setXml(makeItemXmlAndData(data1));
836 m2->setXml(makeItemXmlAndData(data2));
837 QCoreApplication::processEvents();
838 m3->setXml(makeItemXmlAndData(data3));
839 QCoreApplication::processEvents();
840 m2->setXml(makeItemXmlAndData(data2));
841 m1->setXml(makeItemXmlAndData(data1));
842 m2->setXml(makeItemXmlAndData(data2));
843 m3->setXml(makeItemXmlAndData(data3));
844 QCoreApplication::processEvents();
845 m2->setXml(makeItemXmlAndData(data2));
846 m3->setXml(makeItemXmlAndData(data3));
847 m3->setXml(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, QUrl::fromLocalFile(TESTDATA("propertychanges.qml")));
884 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
886 QTRY_COMPARE(model->count(), 9);
888 QDeclarativeXmlListModelRole *role = model->findChild<QDeclarativeXmlListModelRole*>("role");
891 QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
892 QSignalSpy querySpy(role, SIGNAL(queryChanged()));
893 QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
895 role->setName("size");
896 role->setQuery("size/string()");
897 role->setIsKey(true);
899 QCOMPARE(role->name(), QString("size"));
900 QCOMPARE(role->query(), QString("size/string()"));
901 QVERIFY(role->isKey());
903 QCOMPARE(nameSpy.count(),1);
904 QCOMPARE(querySpy.count(),1);
905 QCOMPARE(isKeySpy.count(),1);
907 role->setName("size");
908 role->setQuery("size/string()");
909 role->setIsKey(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->setSource(QUrl(""));
921 model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
922 model->setQuery("/Pets");
923 model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
925 QCOMPARE(model->source(), QUrl(""));
926 QCOMPARE(model->xml(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
927 QCOMPARE(model->query(), QString("/Pets"));
928 QCOMPARE(model->namespaceDeclarations(), 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->setSource(QUrl(""));
938 model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
939 model->setQuery("/Pets");
940 model->setNamespaceDeclarations("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, QUrl::fromLocalFile(TESTDATA("roleCrash.qml")));
955 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
960 QTEST_MAIN(tst_qdeclarativexmllistmodel)
962 #include "tst_qdeclarativexmllistmodel.moc"