1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file. Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights. These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
40 ****************************************************************************/
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>
56 #ifdef QTEST_XMLPATTERNS
57 #include <QtDeclarative/qdeclarativeengine.h>
58 #include <QtDeclarative/qdeclarativecomponent.h>
59 #include <private/qdeclarativexmllistmodel_p.h>
60 #include "../../../shared/util.h"
63 // In Symbian OS test data is located in applications private dir
67 typedef QPair<int, int> QDeclarativeXmlListRange;
68 typedef QList<QVariantList> QDeclarativeXmlModelData;
70 Q_DECLARE_METATYPE(QList<QDeclarativeXmlListRange>)
71 Q_DECLARE_METATYPE(QDeclarativeXmlModelData)
72 Q_DECLARE_METATYPE(QDeclarativeXmlListModel::Status)
74 class tst_qdeclarativexmllistmodel : public QObject
79 tst_qdeclarativexmllistmodel() {}
83 qRegisterMetaType<QDeclarativeXmlListModel::Status>("QDeclarativeXmlListModel::Status");
88 void testTypes_data();
93 void uniqueRoleNames();
104 void noKeysValueChanges();
107 void threading_data();
108 void propertyChanges();
113 QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
119 if (!data.isEmpty()) {
120 QStringList items = data.split(";");
121 foreach(const QString &item, items) {
124 QVariantList variants;
125 xml += QLatin1String("<item>");
126 QStringList fields = item.split(",");
127 foreach(const QString &field, fields) {
128 QStringList values = field.split("=");
129 Q_ASSERT(values.count() == 2);
130 xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
134 int number = values[1].toInt(&isNum);
138 variants << values[1];
140 xml += QLatin1String("</item>");
142 modelData->append(variants);
146 QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
147 return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
150 QDeclarativeEngine engine;
153 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
157 QVariantMap lastSentHeaders;
160 QNetworkAccessManager *create(QObject *parent);
163 class CustomNetworkAccessManager : public QNetworkAccessManager
167 CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
168 : QNetworkAccessManager(parent), m_factory(factory) {}
171 QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
175 foreach (const QString &header, req.rawHeaderList())
176 map[header] = req.rawHeader(header.toUtf8());
177 m_factory->lastSentHeaders = map;
179 return QNetworkAccessManager::createRequest(op, req, outgoingData);
182 QPointer<CustomNetworkAccessManagerFactory> m_factory;
185 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
187 return new CustomNetworkAccessManager(this, parent);
191 void tst_qdeclarativexmllistmodel::buildModel()
193 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
194 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
196 QTRY_COMPARE(model->count(), 9);
199 roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
200 QHash<int, QVariant> data = model->data(3, roles);
201 QVERIFY(data.count() == 4);
202 QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Spot"));
203 QCOMPARE(data.value(Qt::UserRole+1).toString(), QLatin1String("Dog"));
204 QCOMPARE(data.value(Qt::UserRole+2).toInt(), 9);
205 QCOMPARE(data.value(Qt::UserRole+3).toString(), QLatin1String("Medium"));
210 void tst_qdeclarativexmllistmodel::testTypes()
212 QFETCH(QString, xml);
213 QFETCH(QString, roleName);
214 QFETCH(QVariant, expectedValue);
216 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/testtypes.qml"));
217 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
219 model->setXml(xml.toUtf8());
221 QTRY_COMPARE(model->count(), 1);
224 foreach (int i, model->roles()) {
225 if (model->toString(i) == roleName) {
232 if (expectedValue.toString() == "nan")
233 QVERIFY(qIsNaN(model->data(0, role).toDouble()));
235 QCOMPARE(model->data(0, role), expectedValue);
240 void tst_qdeclarativexmllistmodel::testTypes_data()
242 QTest::addColumn<QString>("xml");
243 QTest::addColumn<QString>("roleName");
244 QTest::addColumn<QVariant>("expectedValue");
246 QTest::newRow("missing string field") << "<data></data>"
247 << "stringValue" << QVariant("");
248 QTest::newRow("empty string") << "<data><a-string></a-string></data>"
249 << "stringValue" << QVariant("");
250 QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
251 << "stringValue" << QVariant("5");
252 QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
253 << "stringValue" << QVariant("abc def g");
255 QTest::newRow("missing number field") << "<data></data>"
256 << "numberValue" << QVariant("");
257 double nan = qQNaN();
258 QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
259 << "numberValue" << QVariant(nan);
260 QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
261 << "numberValue" << QVariant(nan);
262 QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
263 << "numberValue" << QVariant("-1");
264 QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
265 << "numberValue" << QVariant("-1.5");
266 QTest::newRow("0") << "<data><a-number>0</a-number></data>"
267 << "numberValue" << QVariant("0");
268 QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
269 << "numberValue" << QVariant("1");
270 QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
271 << "numberValue" << QVariant("1.5");
274 void tst_qdeclarativexmllistmodel::cdata()
276 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
277 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
279 QTRY_COMPARE(model->count(), 5);
282 roles << Qt::UserRole + 2;
283 QHash<int, QVariant> data = model->data(2, roles);
284 QVERIFY(data.count() == 1);
285 QVERIFY(data.value(Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
290 void tst_qdeclarativexmllistmodel::attributes()
292 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
293 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
295 QTRY_COMPARE(model->count(), 5);
297 roles << Qt::UserRole;
298 QHash<int, QVariant> data = model->data(2, roles);
299 QVERIFY(data.count() == 1);
300 QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
305 void tst_qdeclarativexmllistmodel::roles()
307 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
308 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
310 QTRY_COMPARE(model->count(), 9);
312 QList<int> roles = model->roles();
313 QCOMPARE(roles.count(), 4);
314 QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
315 QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
316 QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
317 QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
322 void tst_qdeclarativexmllistmodel::roleErrors()
324 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml"));
325 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml").toString() + ":6:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
326 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml").toString() + ":9:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
328 //### make sure we receive all expected warning messages.
329 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
331 QTRY_COMPARE(model->count(), 9);
334 roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
335 QHash<int, QVariant> data = model->data(3, roles);
336 QVERIFY(data.count() == 4);
338 //### should any of these return valid values?
339 QCOMPARE(data.value(Qt::UserRole), QVariant());
340 QCOMPARE(data.value(Qt::UserRole+1), QVariant());
341 QCOMPARE(data.value(Qt::UserRole+2), QVariant());
343 QEXPECT_FAIL("", "QTBUG-10797", Continue);
344 QCOMPARE(data.value(Qt::UserRole+3), QVariant());
349 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
351 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/unique.qml"));
352 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/unique.qml").toString() + ":7:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
353 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
355 QTRY_COMPARE(model->count(), 9);
357 QList<int> roles = model->roles();
358 QCOMPARE(roles.count(), 1);
364 void tst_qdeclarativexmllistmodel::xml()
366 QFETCH(QString, xml);
369 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
370 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
371 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
373 QVERIFY(model->errorString().isEmpty());
374 QCOMPARE(model->progress(), qreal(0.0));
375 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
376 QTRY_COMPARE(spy.count(), 1); spy.clear();
377 QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
378 QVERIFY(model->errorString().isEmpty());
379 QCOMPARE(model->progress(), qreal(1.0));
380 QCOMPARE(model->count(), 9);
382 // if xml is empty (i.e. clearing) it won't have any effect if a source is set
384 model->setSource(QUrl());
386 QCOMPARE(model->progress(), qreal(1.0)); // immediately goes to 1.0 if using setXml()
387 QTRY_COMPARE(spy.count(), 1); spy.clear();
388 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
389 QTRY_COMPARE(spy.count(), 1); spy.clear();
390 QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
391 QVERIFY(model->errorString().isEmpty());
392 QCOMPARE(model->count(), count);
397 void tst_qdeclarativexmllistmodel::xml_data()
399 QTest::addColumn<QString>("xml");
400 QTest::addColumn<int>("count");
402 QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
403 QTest::newRow("empty xml") << "" << 0;
404 QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
407 void tst_qdeclarativexmllistmodel::headers()
409 // ensure the QNetworkAccessManagers created for this test are immediately deleted
410 QDeclarativeEngine qmlEng;
412 CustomNetworkAccessManagerFactory factory;
413 qmlEng.setNetworkAccessManagerFactory(&factory);
415 QDeclarativeComponent component(&qmlEng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
416 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
418 QTRY_COMPARE(model->status(), QDeclarativeXmlListModel::Ready);
420 QVariantMap expectedHeaders;
421 expectedHeaders["Accept"] = "application/xml,*/*";
423 QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
424 foreach (const QString &header, expectedHeaders.keys()) {
425 QVERIFY(factory.lastSentHeaders.contains(header));
426 QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
432 void tst_qdeclarativexmllistmodel::source()
434 QFETCH(QUrl, source);
436 QFETCH(QDeclarativeXmlListModel::Status, status);
438 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
439 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
440 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
442 QVERIFY(model->errorString().isEmpty());
443 QCOMPARE(model->progress(), qreal(0.0));
444 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
445 QTRY_COMPARE(spy.count(), 1); spy.clear();
446 QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
447 QVERIFY(model->errorString().isEmpty());
448 QCOMPARE(model->progress(), qreal(1.0));
449 QCOMPARE(model->count(), 9);
451 model->setSource(source);
452 QCOMPARE(model->progress(), qreal(0.0));
453 QTRY_COMPARE(spy.count(), 1); spy.clear();
454 QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
455 QVERIFY(model->errorString().isEmpty());
459 timer.setSingleShot(true);
460 connect(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)), &loop, SLOT(quit()));
461 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
465 if (spy.count() == 0 && status != QDeclarativeXmlListModel::Ready) {
466 qWarning("QDeclarativeXmlListModel invalid source test timed out");
468 QCOMPARE(spy.count(), 1); spy.clear();
471 QCOMPARE(model->status(), status);
472 QCOMPARE(model->count(), count);
474 if (status == QDeclarativeXmlListModel::Ready)
475 QCOMPARE(model->progress(), qreal(1.0));
477 QCOMPARE(model->errorString().isEmpty(), status == QDeclarativeXmlListModel::Ready);
482 void tst_qdeclarativexmllistmodel::source_data()
484 QTest::addColumn<QUrl>("source");
485 QTest::addColumn<int>("count");
486 QTest::addColumn<QDeclarativeXmlListModel::Status>("status");
488 QTest::newRow("valid") << QUrl::fromLocalFile(SRCDIR "/data/model2.xml") << 2 << QDeclarativeXmlListModel::Ready;
489 QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 << QDeclarativeXmlListModel::Error;
492 QTemporaryFile *temp = new QTemporaryFile(this);
494 QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QDeclarativeXmlListModel::Ready;
498 void tst_qdeclarativexmllistmodel::data()
500 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
501 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
504 QHash<int,QVariant> blank;
505 for (int i=0; i<model->roles().count(); i++)
506 blank.insert(model->roles()[i], QVariant());
507 for (int i=0; i<9; i++) {
508 QCOMPARE(model->data(i, model->roles()), blank);
509 for (int j=0; j<model->roles().count(); j++) {
510 QCOMPARE(model->data(i, j), QVariant());
513 QTRY_COMPARE(model->count(), 9);
518 void tst_qdeclarativexmllistmodel::get()
520 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
521 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
523 QVERIFY(model->get(0).isUndefined());
525 QTRY_COMPARE(model->count(), 9);
526 QVERIFY(model->get(-1).isUndefined());
528 QScriptValue sv = model->get(0);
529 QCOMPARE(sv.property("name").toString(), QLatin1String("Polly"));
530 QCOMPARE(sv.property("type").toString(), QLatin1String("Parrot"));
531 QCOMPARE(sv.property("age").toNumber(), qsreal(12));
532 QCOMPARE(sv.property("size").toString(), QLatin1String("Small"));
535 QCOMPARE(sv.property("name").toString(), QLatin1String("Penny"));
536 QCOMPARE(sv.property("type").toString(), QLatin1String("Turtle"));
537 QCOMPARE(sv.property("age").toNumber(), qsreal(4));
538 QCOMPARE(sv.property("size").toString(), QLatin1String("Small"));
541 QCOMPARE(sv.property("name").toString(), QLatin1String("Rover"));
542 QCOMPARE(sv.property("type").toString(), QLatin1String("Dog"));
543 QCOMPARE(sv.property("age").toNumber(), qsreal(0));
544 QCOMPARE(sv.property("size").toString(), QLatin1String("Large"));
547 QCOMPARE(sv.property("name").toString(), QLatin1String("Tiny"));
548 QCOMPARE(sv.property("type").toString(), QLatin1String("Elephant"));
549 QCOMPARE(sv.property("age").toNumber(), qsreal(15));
550 QCOMPARE(sv.property("size").toString(), QLatin1String("Large"));
553 QVERIFY(sv.isUndefined());
558 void tst_qdeclarativexmllistmodel::reload()
560 // If no keys are used, the model should be rebuilt from scratch when
561 // reload() is called.
563 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
564 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
566 QTRY_COMPARE(model->count(), 9);
568 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
569 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
570 QSignalSpy spyCount(model, SIGNAL(countChanged()));
572 //reload multiple times to test the xml query aborting
575 QCoreApplication::processEvents();
578 QTRY_COMPARE(spyCount.count(), 1);
579 QTRY_COMPARE(spyInsert.count(), 1);
580 QTRY_COMPARE(spyRemove.count(), 1);
582 QCOMPARE(spyInsert[0][0].toInt(), 0);
583 QCOMPARE(spyInsert[0][1].toInt(), 9);
585 QCOMPARE(spyRemove[0][0].toInt(), 0);
586 QCOMPARE(spyRemove[0][1].toInt(), 9);
591 void tst_qdeclarativexmllistmodel::useKeys()
593 // If using incremental updates through keys, the model should only
594 // insert & remove some of the items, instead of throwing everything
595 // away and causing the view to repaint the whole view.
597 QFETCH(QString, oldXml);
598 QFETCH(int, oldCount);
599 QFETCH(QString, newXml);
600 QFETCH(QDeclarativeXmlModelData, newData);
601 QFETCH(QList<QDeclarativeXmlListRange>, insertRanges);
602 QFETCH(QList<QDeclarativeXmlListRange>, removeRanges);
604 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
605 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
608 model->setXml(oldXml);
609 QTRY_COMPARE(model->count(), oldCount);
611 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
612 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
613 QSignalSpy spyCount(model, SIGNAL(countChanged()));
615 model->setXml(newXml);
617 if (oldCount != newData.count()) {
618 QTRY_COMPARE(model->count(), newData.count());
619 QCOMPARE(spyCount.count(), 1);
621 QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
622 QCOMPARE(spyCount.count(), 0);
625 QList<int> roles = model->roles();
626 for (int i=0; i<model->count(); i++) {
627 for (int j=0; j<roles.count(); j++)
628 QCOMPARE(model->data(i, roles[j]), newData[i][j]);
631 QCOMPARE(spyInsert.count(), insertRanges.count());
632 for (int i=0; i<spyInsert.count(); i++) {
633 QCOMPARE(spyInsert[i][0].toInt(), insertRanges[i].first);
634 QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].second);
637 QCOMPARE(spyRemove.count(), removeRanges.count());
638 for (int i=0; i<spyRemove.count(); i++) {
639 QCOMPARE(spyRemove[i][0].toInt(), removeRanges[i].first);
640 QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].second);
646 void tst_qdeclarativexmllistmodel::useKeys_data()
648 QTest::addColumn<QString>("oldXml");
649 QTest::addColumn<int>("oldCount");
650 QTest::addColumn<QString>("newXml");
651 QTest::addColumn<QDeclarativeXmlModelData>("newData");
652 QTest::addColumn<QList<QDeclarativeXmlListRange> >("insertRanges");
653 QTest::addColumn<QList<QDeclarativeXmlListRange> >("removeRanges");
655 QDeclarativeXmlModelData modelData;
657 QTest::newRow("append 1")
658 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
659 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData)
661 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
662 << QList<QDeclarativeXmlListRange>();
664 QTest::newRow("append multiple")
665 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
666 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
668 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
669 << QList<QDeclarativeXmlListRange>();
671 QTest::newRow("insert in different spots")
672 << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1
673 << 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)
675 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
676 << QList<QDeclarativeXmlListRange>();
678 QTest::newRow("insert in middle")
679 << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2
680 << 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)
682 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
683 << QList<QDeclarativeXmlListRange>();
685 QTest::newRow("remove first")
686 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
687 << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData)
689 << QList<QDeclarativeXmlListRange>()
690 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
692 QTest::newRow("remove last")
693 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
694 << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData)
696 << QList<QDeclarativeXmlListRange>()
697 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1));
699 QTest::newRow("remove from multiple spots")
700 << 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
701 << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData)
703 << QList<QDeclarativeXmlListRange>()
704 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
706 QTest::newRow("remove all")
707 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
708 << makeItemXmlAndData("", &modelData)
710 << QList<QDeclarativeXmlListRange>()
711 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 3));
713 QTest::newRow("replace item")
714 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
715 << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
717 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1))
718 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
720 QTest::newRow("add and remove simultaneously, in different spots")
721 << 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
722 << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData)
724 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
725 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
727 QTest::newRow("insert at start, remove at end i.e. rss feed")
728 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3
729 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
731 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
732 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2));
734 QTest::newRow("remove at start, insert at end")
735 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
736 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData)
738 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
739 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
741 QTest::newRow("all data has changed")
742 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2
743 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
745 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
746 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
749 void tst_qdeclarativexmllistmodel::noKeysValueChanges()
751 // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml.
752 // If a 'sport' value is changed, the model should not be reloaded,
753 // since 'sport' is not marked as a key.
755 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
756 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
761 xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
763 QTRY_COMPARE(model->count(), 2);
767 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
768 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
769 QSignalSpy spyCount(model, SIGNAL(countChanged()));
771 xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
774 // wait for the new xml data to be set, and verify no signals were emitted
775 QTRY_VERIFY(model->data(0, model->roles()[2]).toString() != QLatin1String("Football"));
776 QCOMPARE(model->data(0, model->roles()[2]).toString(), QLatin1String("AussieRules"));
778 QVERIFY(spyInsert.count() == 0);
779 QVERIFY(spyRemove.count() == 0);
780 QVERIFY(spyCount.count() == 0);
782 QCOMPARE(model->count(), 2);
787 void tst_qdeclarativexmllistmodel::keysChanged()
789 // If the key roles change, the next time the data is reloaded, it should
790 // delete all its data and build a clean model (i.e. same behaviour as
791 // if no keys are set).
793 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
794 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
797 QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
799 QTRY_COMPARE(model->count(), 2);
803 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
804 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
805 QSignalSpy spyCount(model, SIGNAL(countChanged()));
807 QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
810 QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
812 QCOMPARE(spyInsert.count(), 1);
813 QCOMPARE(spyInsert[0][0].toInt(), 0);
814 QCOMPARE(spyInsert[0][1].toInt(), 2);
816 QCOMPARE(spyRemove.count(), 1);
817 QCOMPARE(spyRemove[0][0].toInt(), 0);
818 QCOMPARE(spyRemove[0][1].toInt(), 2);
820 QCOMPARE(spyCount.count(), 0);
825 void tst_qdeclarativexmllistmodel::threading()
827 QFETCH(int, xmlDataCount);
829 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
831 QDeclarativeXmlListModel *m1 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
833 QDeclarativeXmlListModel *m2 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
835 QDeclarativeXmlListModel *m3 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
838 for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
840 QString data1, data2, data3;
841 for (int i=0; i<dataCount; i++) {
842 data1 += "name=A" + QString::number(i) + ",age=1" + QString::number(i) + ",sport=Football;";
843 data2 += "name=B" + QString::number(i) + ",age=2" + QString::number(i) + ",sport=Athletics;";
844 data3 += "name=C" + QString::number(i) + ",age=3" + QString::number(i) + ",sport=Curling;";
847 //Set the xml data multiple times with randomized order and mixed with multiple event loops
848 //to test the xml query reloading/aborting, the result should be stable.
849 m1->setXml(makeItemXmlAndData(data1));
850 m2->setXml(makeItemXmlAndData(data2));
851 m3->setXml(makeItemXmlAndData(data3));
852 QCoreApplication::processEvents();
853 m2->setXml(makeItemXmlAndData(data2));
854 m1->setXml(makeItemXmlAndData(data1));
855 m2->setXml(makeItemXmlAndData(data2));
856 QCoreApplication::processEvents();
857 m3->setXml(makeItemXmlAndData(data3));
858 QCoreApplication::processEvents();
859 m2->setXml(makeItemXmlAndData(data2));
860 m1->setXml(makeItemXmlAndData(data1));
861 m2->setXml(makeItemXmlAndData(data2));
862 m3->setXml(makeItemXmlAndData(data3));
863 QCoreApplication::processEvents();
864 m2->setXml(makeItemXmlAndData(data2));
865 m3->setXml(makeItemXmlAndData(data3));
866 m3->setXml(makeItemXmlAndData(data3));
867 QCoreApplication::processEvents();
869 QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount);
871 for (int i=0; i<dataCount; i++) {
872 QCOMPARE(m1->data(i, m1->roles()[0]).toString(), QString("A" + QString::number(i)));
873 QCOMPARE(m1->data(i, m1->roles()[1]).toString(), QString("1" + QString::number(i)));
874 QCOMPARE(m1->data(i, m1->roles()[2]).toString(), QString("Football"));
876 QCOMPARE(m2->data(i, m2->roles()[0]).toString(), QString("B" + QString::number(i)));
877 QCOMPARE(m2->data(i, m2->roles()[1]).toString(), QString("2" + QString::number(i)));
878 QCOMPARE(m2->data(i, m2->roles()[2]).toString(), QString("Athletics"));
880 QCOMPARE(m3->data(i, m3->roles()[0]).toString(), QString("C" + QString::number(i)));
881 QCOMPARE(m3->data(i, m3->roles()[1]).toString(), QString("3" + QString::number(i)));
882 QCOMPARE(m3->data(i, m3->roles()[2]).toString(), QString("Curling"));
891 void tst_qdeclarativexmllistmodel::threading_data()
893 QTest::addColumn<int>("xmlDataCount");
895 QTest::newRow("1") << 1;
896 QTest::newRow("2") << 2;
897 QTest::newRow("10") << 10;
900 void tst_qdeclarativexmllistmodel::propertyChanges()
902 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/propertychanges.qml"));
903 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
905 QTRY_COMPARE(model->count(), 9);
907 QDeclarativeXmlListModelRole *role = model->findChild<QDeclarativeXmlListModelRole*>("role");
910 QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
911 QSignalSpy querySpy(role, SIGNAL(queryChanged()));
912 QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
914 role->setName("size");
915 role->setQuery("size/string()");
916 role->setIsKey(true);
918 QCOMPARE(role->name(), QString("size"));
919 QCOMPARE(role->query(), QString("size/string()"));
920 QVERIFY(role->isKey());
922 QCOMPARE(nameSpy.count(),1);
923 QCOMPARE(querySpy.count(),1);
924 QCOMPARE(isKeySpy.count(),1);
926 role->setName("size");
927 role->setQuery("size/string()");
928 role->setIsKey(true);
930 QCOMPARE(nameSpy.count(),1);
931 QCOMPARE(querySpy.count(),1);
932 QCOMPARE(isKeySpy.count(),1);
934 QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
935 QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
936 QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
937 QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
939 model->setSource(QUrl(""));
940 model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
941 model->setQuery("/Pets");
942 model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
944 QCOMPARE(model->source(), QUrl(""));
945 QCOMPARE(model->xml(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
946 QCOMPARE(model->query(), QString("/Pets"));
947 QCOMPARE(model->namespaceDeclarations(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
949 QTRY_VERIFY(model->count() == 1);
951 QCOMPARE(sourceSpy.count(),1);
952 QCOMPARE(xmlSpy.count(),1);
953 QCOMPARE(modelQuerySpy.count(),1);
954 QCOMPARE(namespaceDeclarationsSpy.count(),1);
956 model->setSource(QUrl(""));
957 model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
958 model->setQuery("/Pets");
959 model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
961 QCOMPARE(sourceSpy.count(),1);
962 QCOMPARE(xmlSpy.count(),1);
963 QCOMPARE(modelQuerySpy.count(),1);
964 QCOMPARE(namespaceDeclarationsSpy.count(),1);
966 QTRY_VERIFY(model->count() == 1);
970 void tst_qdeclarativexmllistmodel::roleCrash()
973 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleCrash.qml"));
974 QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
979 QTEST_MAIN(tst_qdeclarativexmllistmodel)
981 #include "tst_qdeclarativexmllistmodel.moc"