1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** 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>
56 #include <QtDeclarative/qdeclarativeengine.h>
57 #include <QtDeclarative/qdeclarativecomponent.h>
58 #include <QtQuick1/private/qdeclarativexmllistmodel_p.h>
60 typedef QPair<int, int> QDeclarativeXmlListRange;
61 typedef QList<QVariantList> QDeclarativeXmlModelData;
63 Q_DECLARE_METATYPE(QList<QDeclarativeXmlListRange>)
64 Q_DECLARE_METATYPE(QDeclarativeXmlModelData)
65 Q_DECLARE_METATYPE(QDeclarative1XmlListModel::Status)
67 class tst_qdeclarativexmllistmodel : public QObject
72 tst_qdeclarativexmllistmodel() {}
76 qRegisterMetaType<QDeclarative1XmlListModel::Status>("QDeclarative1XmlListModel::Status");
81 void testTypes_data();
86 void uniqueRoleNames();
97 void noKeysValueChanges();
100 void threading_data();
101 void propertyChanges();
106 QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
112 if (!data.isEmpty()) {
113 QStringList items = data.split(";");
114 foreach(const QString &item, items) {
117 QVariantList variants;
118 xml += QLatin1String("<item>");
119 QStringList fields = item.split(",");
120 foreach(const QString &field, fields) {
121 QStringList values = field.split("=");
122 if (values.count() != 2) {
123 qWarning() << "makeItemXmlAndData: invalid field:" << field;
126 xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
130 int number = values[1].toInt(&isNum);
134 variants << values[1];
136 xml += QLatin1String("</item>");
138 modelData->append(variants);
142 QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
143 return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
146 QDeclarativeEngine engine;
149 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
153 QVariantMap lastSentHeaders;
156 QNetworkAccessManager *create(QObject *parent);
159 class CustomNetworkAccessManager : public QNetworkAccessManager
163 CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
164 : QNetworkAccessManager(parent), m_factory(factory) {}
167 QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
171 foreach (const QString &header, req.rawHeaderList())
172 map[header] = req.rawHeader(header.toUtf8());
173 m_factory->lastSentHeaders = map;
175 return QNetworkAccessManager::createRequest(op, req, outgoingData);
178 QPointer<CustomNetworkAccessManagerFactory> m_factory;
181 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
183 return new CustomNetworkAccessManager(this, parent);
187 void tst_qdeclarativexmllistmodel::buildModel()
189 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
190 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
192 QTRY_COMPARE(model->count(), 9);
195 roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
196 QHash<int, QVariant> data = model->data(3, roles);
197 QVERIFY(data.count() == 4);
198 QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Spot"));
199 QCOMPARE(data.value(Qt::UserRole+1).toString(), QLatin1String("Dog"));
200 QCOMPARE(data.value(Qt::UserRole+2).toInt(), 9);
201 QCOMPARE(data.value(Qt::UserRole+3).toString(), QLatin1String("Medium"));
206 void tst_qdeclarativexmllistmodel::testTypes()
208 QFETCH(QString, xml);
209 QFETCH(QString, roleName);
210 QFETCH(QVariant, expectedValue);
212 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/testtypes.qml"));
213 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
215 model->setXml(xml.toUtf8());
217 QTRY_COMPARE(model->count(), 1);
220 foreach (int i, model->roles()) {
221 if (model->toString(i) == roleName) {
228 if (expectedValue.toString() == "nan")
229 QVERIFY(qIsNaN(model->data(0, role).toDouble()));
231 QCOMPARE(model->data(0, role), expectedValue);
236 void tst_qdeclarativexmllistmodel::testTypes_data()
238 QTest::addColumn<QString>("xml");
239 QTest::addColumn<QString>("roleName");
240 QTest::addColumn<QVariant>("expectedValue");
242 QTest::newRow("missing string field") << "<data></data>"
243 << "stringValue" << QVariant("");
244 QTest::newRow("empty string") << "<data><a-string></a-string></data>"
245 << "stringValue" << QVariant("");
246 QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
247 << "stringValue" << QVariant("5");
248 QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
249 << "stringValue" << QVariant("abc def g");
251 QTest::newRow("missing number field") << "<data></data>"
252 << "numberValue" << QVariant("");
253 double nan = qQNaN();
254 QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
255 << "numberValue" << QVariant(nan);
256 QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
257 << "numberValue" << QVariant(nan);
258 QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
259 << "numberValue" << QVariant("-1");
260 QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
261 << "numberValue" << QVariant("-1.5");
262 QTest::newRow("0") << "<data><a-number>0</a-number></data>"
263 << "numberValue" << QVariant("0");
264 QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
265 << "numberValue" << QVariant("1");
266 QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
267 << "numberValue" << QVariant("1.5");
270 void tst_qdeclarativexmllistmodel::cdata()
272 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
273 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
275 QTRY_COMPARE(model->count(), 5);
278 roles << Qt::UserRole + 2;
279 QHash<int, QVariant> data = model->data(2, roles);
280 QVERIFY(data.count() == 1);
281 QVERIFY(data.value(Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
286 void tst_qdeclarativexmllistmodel::attributes()
288 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
289 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
291 QTRY_COMPARE(model->count(), 5);
293 roles << Qt::UserRole;
294 QHash<int, QVariant> data = model->data(2, roles);
295 QVERIFY(data.count() == 1);
296 QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
301 void tst_qdeclarativexmllistmodel::roles()
303 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
304 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
306 QTRY_COMPARE(model->count(), 9);
308 QList<int> roles = model->roles();
309 QCOMPARE(roles.count(), 4);
310 QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
311 QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
312 QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
313 QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
318 void tst_qdeclarativexmllistmodel::roleErrors()
320 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml"));
321 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml").toString() + ":6:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
322 QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml").toString() + ":9:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
324 //### make sure we receive all expected warning messages.
325 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
327 QTRY_COMPARE(model->count(), 9);
330 roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
331 QHash<int, QVariant> data = model->data(3, roles);
332 QVERIFY(data.count() == 4);
334 //### should any of these return valid values?
335 QCOMPARE(data.value(Qt::UserRole), QVariant());
336 QCOMPARE(data.value(Qt::UserRole+1), QVariant());
337 QCOMPARE(data.value(Qt::UserRole+2), QVariant());
339 QEXPECT_FAIL("", "QTBUG-10797", Continue);
340 QCOMPARE(data.value(Qt::UserRole+3), QVariant());
345 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
347 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/unique.qml"));
348 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());
349 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
351 QTRY_COMPARE(model->count(), 9);
353 QList<int> roles = model->roles();
354 QCOMPARE(roles.count(), 1);
360 void tst_qdeclarativexmllistmodel::xml()
362 QFETCH(QString, xml);
365 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
366 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
367 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarative1XmlListModel::Status)));
369 QVERIFY(model->errorString().isEmpty());
370 QCOMPARE(model->progress(), qreal(0.0));
371 QCOMPARE(model->status(), QDeclarative1XmlListModel::Loading);
372 QTRY_COMPARE(spy.count(), 1); spy.clear();
373 QCOMPARE(model->status(), QDeclarative1XmlListModel::Ready);
374 QVERIFY(model->errorString().isEmpty());
375 QCOMPARE(model->progress(), qreal(1.0));
376 QCOMPARE(model->count(), 9);
378 // if xml is empty (i.e. clearing) it won't have any effect if a source is set
380 model->setSource(QUrl());
382 QCOMPARE(model->progress(), qreal(1.0)); // immediately goes to 1.0 if using setXml()
383 QTRY_COMPARE(spy.count(), 1); spy.clear();
384 QCOMPARE(model->status(), QDeclarative1XmlListModel::Loading);
385 QTRY_COMPARE(spy.count(), 1); spy.clear();
386 QCOMPARE(model->status(), QDeclarative1XmlListModel::Ready);
387 QVERIFY(model->errorString().isEmpty());
388 QCOMPARE(model->count(), count);
393 void tst_qdeclarativexmllistmodel::xml_data()
395 QTest::addColumn<QString>("xml");
396 QTest::addColumn<int>("count");
398 QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
399 QTest::newRow("empty xml") << "" << 0;
400 QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
403 void tst_qdeclarativexmllistmodel::headers()
405 // ensure the QNetworkAccessManagers created for this test are immediately deleted
406 QDeclarativeEngine qmlEng;
408 CustomNetworkAccessManagerFactory factory;
409 qmlEng.setNetworkAccessManagerFactory(&factory);
411 QDeclarativeComponent component(&qmlEng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
412 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
414 QTRY_COMPARE(model->status(), QDeclarative1XmlListModel::Ready);
416 QVariantMap expectedHeaders;
417 expectedHeaders["Accept"] = "application/xml,*/*";
419 QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
420 foreach (const QString &header, expectedHeaders.keys()) {
421 QVERIFY(factory.lastSentHeaders.contains(header));
422 QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
428 void tst_qdeclarativexmllistmodel::source()
430 QFETCH(QUrl, source);
432 QFETCH(QDeclarative1XmlListModel::Status, status);
434 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
435 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
436 QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarative1XmlListModel::Status)));
438 QVERIFY(model->errorString().isEmpty());
439 QCOMPARE(model->progress(), qreal(0.0));
440 QCOMPARE(model->status(), QDeclarative1XmlListModel::Loading);
441 QTRY_COMPARE(spy.count(), 1); spy.clear();
442 QCOMPARE(model->status(), QDeclarative1XmlListModel::Ready);
443 QVERIFY(model->errorString().isEmpty());
444 QCOMPARE(model->progress(), qreal(1.0));
445 QCOMPARE(model->count(), 9);
447 model->setSource(source);
448 QCOMPARE(model->progress(), qreal(0.0));
449 QTRY_COMPARE(spy.count(), 1); spy.clear();
450 QCOMPARE(model->status(), QDeclarative1XmlListModel::Loading);
451 QVERIFY(model->errorString().isEmpty());
455 timer.setSingleShot(true);
456 connect(model, SIGNAL(statusChanged(QDeclarative1XmlListModel::Status)), &loop, SLOT(quit()));
457 connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
461 if (spy.count() == 0 && status != QDeclarative1XmlListModel::Ready) {
462 qWarning("QDeclarative1XmlListModel invalid source test timed out");
464 QCOMPARE(spy.count(), 1); spy.clear();
467 QCOMPARE(model->status(), status);
468 QCOMPARE(model->count(), count);
470 if (status == QDeclarative1XmlListModel::Ready)
471 QCOMPARE(model->progress(), qreal(1.0));
473 QCOMPARE(model->errorString().isEmpty(), status == QDeclarative1XmlListModel::Ready);
478 void tst_qdeclarativexmllistmodel::source_data()
480 QTest::addColumn<QUrl>("source");
481 QTest::addColumn<int>("count");
482 QTest::addColumn<QDeclarative1XmlListModel::Status>("status");
484 QTest::newRow("valid") << QUrl::fromLocalFile(SRCDIR "/data/model2.xml") << 2 << QDeclarative1XmlListModel::Ready;
485 QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 << QDeclarative1XmlListModel::Error;
488 QTemporaryFile *temp = new QTemporaryFile(this);
490 QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QDeclarative1XmlListModel::Ready;
494 void tst_qdeclarativexmllistmodel::data()
496 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
497 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
500 QHash<int,QVariant> blank;
501 for (int i=0; i<model->roles().count(); i++)
502 blank.insert(model->roles()[i], QVariant());
503 for (int i=0; i<9; i++) {
504 QCOMPARE(model->data(i, model->roles()), blank);
505 for (int j=0; j<model->roles().count(); j++) {
506 QCOMPARE(model->data(i, j), QVariant());
509 QTRY_COMPARE(model->count(), 9);
514 void tst_qdeclarativexmllistmodel::get()
516 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/get.qml"));
517 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
520 QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
521 QCOMPARE(model->property("preTest").toBool(), true);
523 QTRY_COMPARE(model->count(), 9);
525 QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
526 QCOMPARE(model->property("postTest").toBool(), true);
531 void tst_qdeclarativexmllistmodel::reload()
533 // If no keys are used, the model should be rebuilt from scratch when
534 // reload() is called.
536 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
537 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
539 QTRY_COMPARE(model->count(), 9);
541 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
542 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
543 QSignalSpy spyCount(model, SIGNAL(countChanged()));
545 //reload multiple times to test the xml query aborting
548 QCoreApplication::processEvents();
551 QTRY_COMPARE(spyCount.count(), 1);
552 QTRY_COMPARE(spyInsert.count(), 1);
553 QTRY_COMPARE(spyRemove.count(), 1);
555 QCOMPARE(spyInsert[0][0].toInt(), 0);
556 QCOMPARE(spyInsert[0][1].toInt(), 9);
558 QCOMPARE(spyRemove[0][0].toInt(), 0);
559 QCOMPARE(spyRemove[0][1].toInt(), 9);
564 void tst_qdeclarativexmllistmodel::useKeys()
566 // If using incremental updates through keys, the model should only
567 // insert & remove some of the items, instead of throwing everything
568 // away and causing the view to repaint the whole view.
570 QFETCH(QString, oldXml);
571 QFETCH(int, oldCount);
572 QFETCH(QString, newXml);
573 QFETCH(QDeclarativeXmlModelData, newData);
574 QFETCH(QList<QDeclarativeXmlListRange>, insertRanges);
575 QFETCH(QList<QDeclarativeXmlListRange>, removeRanges);
577 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
578 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
581 model->setXml(oldXml);
582 QTRY_COMPARE(model->count(), oldCount);
584 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
585 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
586 QSignalSpy spyCount(model, SIGNAL(countChanged()));
588 model->setXml(newXml);
590 if (oldCount != newData.count()) {
591 QTRY_COMPARE(model->count(), newData.count());
592 QCOMPARE(spyCount.count(), 1);
594 QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
595 QCOMPARE(spyCount.count(), 0);
598 QList<int> roles = model->roles();
599 for (int i=0; i<model->count(); i++) {
600 for (int j=0; j<roles.count(); j++)
601 QCOMPARE(model->data(i, roles[j]), newData[i][j]);
604 QCOMPARE(spyInsert.count(), insertRanges.count());
605 for (int i=0; i<spyInsert.count(); i++) {
606 QCOMPARE(spyInsert[i][0].toInt(), insertRanges[i].first);
607 QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].second);
610 QCOMPARE(spyRemove.count(), removeRanges.count());
611 for (int i=0; i<spyRemove.count(); i++) {
612 QCOMPARE(spyRemove[i][0].toInt(), removeRanges[i].first);
613 QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].second);
619 void tst_qdeclarativexmllistmodel::useKeys_data()
621 QTest::addColumn<QString>("oldXml");
622 QTest::addColumn<int>("oldCount");
623 QTest::addColumn<QString>("newXml");
624 QTest::addColumn<QDeclarativeXmlModelData>("newData");
625 QTest::addColumn<QList<QDeclarativeXmlListRange> >("insertRanges");
626 QTest::addColumn<QList<QDeclarativeXmlListRange> >("removeRanges");
628 QDeclarativeXmlModelData modelData;
630 QTest::newRow("append 1")
631 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
632 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData)
634 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
635 << QList<QDeclarativeXmlListRange>();
637 QTest::newRow("append multiple")
638 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
639 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
641 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
642 << QList<QDeclarativeXmlListRange>();
644 QTest::newRow("insert in different spots")
645 << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1
646 << 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)
648 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
649 << QList<QDeclarativeXmlListRange>();
651 QTest::newRow("insert in middle")
652 << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2
653 << 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)
655 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
656 << QList<QDeclarativeXmlListRange>();
658 QTest::newRow("remove first")
659 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
660 << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData)
662 << QList<QDeclarativeXmlListRange>()
663 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
665 QTest::newRow("remove last")
666 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
667 << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData)
669 << QList<QDeclarativeXmlListRange>()
670 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1));
672 QTest::newRow("remove from multiple spots")
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;name=E,age=65,sport=Fencing") << 5
674 << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData)
676 << QList<QDeclarativeXmlListRange>()
677 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
679 QTest::newRow("remove all")
680 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
681 << makeItemXmlAndData("", &modelData)
683 << QList<QDeclarativeXmlListRange>()
684 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 3));
686 QTest::newRow("replace item")
687 << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
688 << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
690 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1))
691 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
693 QTest::newRow("add and remove simultaneously, in different spots")
694 << 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
695 << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData)
697 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
698 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
700 QTest::newRow("insert at start, remove at end i.e. rss feed")
701 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3
702 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
704 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
705 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2));
707 QTest::newRow("remove at start, insert at end")
708 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
709 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData)
711 << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
712 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
714 QTest::newRow("all data has changed")
715 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2
716 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
718 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
719 << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
722 void tst_qdeclarativexmllistmodel::noKeysValueChanges()
724 // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml.
725 // If a 'sport' value is changed, the model should not be reloaded,
726 // since 'sport' is not marked as a key.
728 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
729 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
734 xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
736 QTRY_COMPARE(model->count(), 2);
740 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
741 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
742 QSignalSpy spyCount(model, SIGNAL(countChanged()));
744 xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
747 // wait for the new xml data to be set, and verify no signals were emitted
748 QTRY_VERIFY(model->data(0, model->roles()[2]).toString() != QLatin1String("Football"));
749 QCOMPARE(model->data(0, model->roles()[2]).toString(), QLatin1String("AussieRules"));
751 QVERIFY(spyInsert.count() == 0);
752 QVERIFY(spyRemove.count() == 0);
753 QVERIFY(spyCount.count() == 0);
755 QCOMPARE(model->count(), 2);
760 void tst_qdeclarativexmllistmodel::keysChanged()
762 // If the key roles change, the next time the data is reloaded, it should
763 // delete all its data and build a clean model (i.e. same behaviour as
764 // if no keys are set).
766 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
767 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
770 QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
772 QTRY_COMPARE(model->count(), 2);
776 QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
777 QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
778 QSignalSpy spyCount(model, SIGNAL(countChanged()));
780 QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
783 QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
785 QCOMPARE(spyInsert.count(), 1);
786 QCOMPARE(spyInsert[0][0].toInt(), 0);
787 QCOMPARE(spyInsert[0][1].toInt(), 2);
789 QCOMPARE(spyRemove.count(), 1);
790 QCOMPARE(spyRemove[0][0].toInt(), 0);
791 QCOMPARE(spyRemove[0][1].toInt(), 2);
793 QCOMPARE(spyCount.count(), 0);
798 void tst_qdeclarativexmllistmodel::threading()
800 QFETCH(int, xmlDataCount);
802 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
804 QDeclarative1XmlListModel *m1 = qobject_cast<QDeclarative1XmlListModel*>(component.create());
806 QDeclarative1XmlListModel *m2 = qobject_cast<QDeclarative1XmlListModel*>(component.create());
808 QDeclarative1XmlListModel *m3 = qobject_cast<QDeclarative1XmlListModel*>(component.create());
811 for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
813 QString data1, data2, data3;
814 for (int i=0; i<dataCount; i++) {
815 data1 += "name=A" + QString::number(i) + ",age=1" + QString::number(i) + ",sport=Football;";
816 data2 += "name=B" + QString::number(i) + ",age=2" + QString::number(i) + ",sport=Athletics;";
817 data3 += "name=C" + QString::number(i) + ",age=3" + QString::number(i) + ",sport=Curling;";
820 //Set the xml data multiple times with randomized order and mixed with multiple event loops
821 //to test the xml query reloading/aborting, the result should be stable.
822 m1->setXml(makeItemXmlAndData(data1));
823 m2->setXml(makeItemXmlAndData(data2));
824 m3->setXml(makeItemXmlAndData(data3));
825 QCoreApplication::processEvents();
826 m2->setXml(makeItemXmlAndData(data2));
827 m1->setXml(makeItemXmlAndData(data1));
828 m2->setXml(makeItemXmlAndData(data2));
829 QCoreApplication::processEvents();
830 m3->setXml(makeItemXmlAndData(data3));
831 QCoreApplication::processEvents();
832 m2->setXml(makeItemXmlAndData(data2));
833 m1->setXml(makeItemXmlAndData(data1));
834 m2->setXml(makeItemXmlAndData(data2));
835 m3->setXml(makeItemXmlAndData(data3));
836 QCoreApplication::processEvents();
837 m2->setXml(makeItemXmlAndData(data2));
838 m3->setXml(makeItemXmlAndData(data3));
839 m3->setXml(makeItemXmlAndData(data3));
840 QCoreApplication::processEvents();
842 QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount);
844 for (int i=0; i<dataCount; i++) {
845 QCOMPARE(m1->data(i, m1->roles()[0]).toString(), QString("A" + QString::number(i)));
846 QCOMPARE(m1->data(i, m1->roles()[1]).toString(), QString("1" + QString::number(i)));
847 QCOMPARE(m1->data(i, m1->roles()[2]).toString(), QString("Football"));
849 QCOMPARE(m2->data(i, m2->roles()[0]).toString(), QString("B" + QString::number(i)));
850 QCOMPARE(m2->data(i, m2->roles()[1]).toString(), QString("2" + QString::number(i)));
851 QCOMPARE(m2->data(i, m2->roles()[2]).toString(), QString("Athletics"));
853 QCOMPARE(m3->data(i, m3->roles()[0]).toString(), QString("C" + QString::number(i)));
854 QCOMPARE(m3->data(i, m3->roles()[1]).toString(), QString("3" + QString::number(i)));
855 QCOMPARE(m3->data(i, m3->roles()[2]).toString(), QString("Curling"));
864 void tst_qdeclarativexmllistmodel::threading_data()
866 QTest::addColumn<int>("xmlDataCount");
868 QTest::newRow("1") << 1;
869 QTest::newRow("2") << 2;
870 QTest::newRow("10") << 10;
873 void tst_qdeclarativexmllistmodel::propertyChanges()
875 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/propertychanges.qml"));
876 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
878 QTRY_COMPARE(model->count(), 9);
880 QDeclarative1XmlListModelRole *role = model->findChild<QDeclarative1XmlListModelRole*>("role");
883 QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
884 QSignalSpy querySpy(role, SIGNAL(queryChanged()));
885 QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
887 role->setName("size");
888 role->setQuery("size/string()");
889 role->setIsKey(true);
891 QCOMPARE(role->name(), QString("size"));
892 QCOMPARE(role->query(), QString("size/string()"));
893 QVERIFY(role->isKey());
895 QCOMPARE(nameSpy.count(),1);
896 QCOMPARE(querySpy.count(),1);
897 QCOMPARE(isKeySpy.count(),1);
899 role->setName("size");
900 role->setQuery("size/string()");
901 role->setIsKey(true);
903 QCOMPARE(nameSpy.count(),1);
904 QCOMPARE(querySpy.count(),1);
905 QCOMPARE(isKeySpy.count(),1);
907 QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
908 QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
909 QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
910 QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
912 model->setSource(QUrl(""));
913 model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
914 model->setQuery("/Pets");
915 model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
917 QCOMPARE(model->source(), QUrl(""));
918 QCOMPARE(model->xml(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
919 QCOMPARE(model->query(), QString("/Pets"));
920 QCOMPARE(model->namespaceDeclarations(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
922 QTRY_VERIFY(model->count() == 1);
924 QCOMPARE(sourceSpy.count(),1);
925 QCOMPARE(xmlSpy.count(),1);
926 QCOMPARE(modelQuerySpy.count(),1);
927 QCOMPARE(namespaceDeclarationsSpy.count(),1);
929 model->setSource(QUrl(""));
930 model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
931 model->setQuery("/Pets");
932 model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
934 QCOMPARE(sourceSpy.count(),1);
935 QCOMPARE(xmlSpy.count(),1);
936 QCOMPARE(modelQuerySpy.count(),1);
937 QCOMPARE(namespaceDeclarationsSpy.count(),1);
939 QTRY_VERIFY(model->count() == 1);
943 void tst_qdeclarativexmllistmodel::roleCrash()
946 QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleCrash.qml"));
947 QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
952 QTEST_MAIN(tst_qdeclarativexmllistmodel)
954 #include "tst_qdeclarativexmllistmodel.moc"