d4a22465389750b547f165dbd17425b72973fd95
[profile/ivi/qtdeclarative.git] / tests / auto / qtquick2 / qdeclarativexmllistmodel / tst_qdeclarativexmllistmodel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: http://www.qt-project.org/
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
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.
17 **
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.
21 **
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.
29 **
30 ** Other Usage
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.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include <QtTest/QtTest>
43 #include <QtGlobal>
44 #include <math.h>
45 #include <QMetaObject>
46 #include <qtest.h>
47 #include <QtTest/qsignalspy.h>
48 #include <QtDeclarative/qdeclarativenetworkaccessmanagerfactory.h>
49 #include <QtNetwork/qnetworkaccessmanager.h>
50 #include <QtNetwork/qnetworkrequest.h>
51 #include <QtCore/qtimer.h>
52 #include <QtCore/qfile.h>
53 #include <QtCore/qtemporaryfile.h>
54 #include "../../shared/util.h"
55 #include <private/qdeclarativeengine_p.h>
56
57 #include <QtDeclarative/qdeclarativeengine.h>
58 #include <QtDeclarative/qdeclarativecomponent.h>
59 #include <private/qlistmodelinterface_p.h>
60 #include "../../../../src/imports/xmllistmodel/qdeclarativexmllistmodel_p.h"
61
62 typedef QPair<int, int> QDeclarativeXmlListRange;
63 typedef QList<QVariantList> QDeclarativeXmlModelData;
64
65 Q_DECLARE_METATYPE(QList<QDeclarativeXmlListRange>)
66 Q_DECLARE_METATYPE(QDeclarativeXmlModelData)
67 Q_DECLARE_METATYPE(QDeclarativeXmlListModel::Status)
68
69 class tst_qdeclarativexmllistmodel : public QDeclarativeDataTest
70
71 {
72     Q_OBJECT
73 public:
74     tst_qdeclarativexmllistmodel() {}
75
76 private slots:
77     void initTestCase() {
78         QDeclarativeDataTest::initTestCase();
79         qRegisterMetaType<QDeclarativeXmlListModel::Status>();
80     }
81
82     void buildModel();
83     void testTypes();
84     void testTypes_data();
85     void cdata();
86     void attributes();
87     void roles();
88     void roleErrors();
89     void uniqueRoleNames();
90     void headers();
91     void xml();
92     void xml_data();
93     void source();
94     void source_data();
95     void data();
96     void get();
97     void reload();
98     void useKeys();
99     void useKeys_data();
100     void noKeysValueChanges();
101     void keysChanged();
102     void threading();
103     void threading_data();
104     void propertyChanges();
105
106     void roleCrash();
107
108 private:
109     QString errorString(QListModelInterface* model) {
110         QString ret;
111         QMetaObject::invokeMethod(model, "errorString", Q_RETURN_ARG(QString, ret));
112         return ret;
113     }
114
115     QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
116     {
117         if (modelData)
118             modelData->clear();
119         QString xml;
120
121         if (!data.isEmpty()) {
122             QStringList items = data.split(";");
123             foreach(const QString &item, items) {
124                 if (item.isEmpty())
125                     continue;
126                 QVariantList variants;
127                 xml += QLatin1String("<item>");
128                 QStringList fields = item.split(",");
129                 foreach(const QString &field, fields) {
130                     QStringList values = field.split("=");
131                     if (values.count() != 2) {
132                         qWarning() << "makeItemXmlAndData: invalid field:" << field;
133                         continue;
134                     }
135                     xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
136                     if (!modelData)
137                         continue;
138                     bool isNum = false;
139                     int number = values[1].toInt(&isNum);
140                     if (isNum)
141                         variants << number;
142                     else
143                         variants << values[1];
144                 }
145                 xml += QLatin1String("</item>");
146                 if (modelData)
147                     modelData->append(variants);
148             }
149         }
150
151         QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
152         return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
153     }
154
155     QDeclarativeEngine engine;
156 };
157
158 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
159 {
160     Q_OBJECT
161 public:
162     QVariantMap lastSentHeaders;
163
164 protected:
165     QNetworkAccessManager *create(QObject *parent);
166 };
167
168 class CustomNetworkAccessManager : public QNetworkAccessManager
169 {
170     Q_OBJECT
171 public:
172     CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
173         : QNetworkAccessManager(parent), m_factory(factory) {}
174
175 protected:
176     QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
177     {
178         if (m_factory) {
179             QVariantMap map;
180             foreach (const QString &header, req.rawHeaderList())
181                 map[header] = req.rawHeader(header.toUtf8());
182             m_factory->lastSentHeaders = map;
183         }
184         return QNetworkAccessManager::createRequest(op, req, outgoingData);
185     }
186
187     QPointer<CustomNetworkAccessManagerFactory> m_factory;
188 };
189
190 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
191 {
192     return new CustomNetworkAccessManager(this, parent);
193 }
194
195
196 void tst_qdeclarativexmllistmodel::buildModel()
197 {
198     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
199     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
200     QVERIFY(model != 0);
201     QTRY_COMPARE(model->count(), 9);
202
203     QCOMPARE(model->data(3, Qt::UserRole).toString(), QLatin1String("Spot"));
204     QCOMPARE(model->data(3, Qt::UserRole+1).toString(), QLatin1String("Dog"));
205     QCOMPARE(model->data(3, Qt::UserRole+2).toInt(), 9);
206     QCOMPARE(model->data(3, Qt::UserRole+3).toString(), QLatin1String("Medium"));
207
208     delete model;
209 }
210
211 void tst_qdeclarativexmllistmodel::testTypes()
212 {
213     QFETCH(QString, xml);
214     QFETCH(QString, roleName);
215     QFETCH(QVariant, expectedValue);
216
217     QDeclarativeComponent component(&engine, testFileUrl("testtypes.qml"));
218     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
219     QVERIFY(model != 0);
220     model->setProperty("xml",xml.toUtf8());
221     QMetaObject::invokeMethod(model, "reload");
222     QTRY_COMPARE(model->count(), 1);
223
224     int role = -1;
225     foreach (int i, model->roles()) {
226         if (model->toString(i) == roleName) {
227             role = i;
228             break;
229         }
230     }
231     QVERIFY(role >= 0);
232
233     if (expectedValue.toString() == "nan")
234         QVERIFY(qIsNaN(model->data(0, role).toDouble()));
235     else
236         QCOMPARE(model->data(0, role), expectedValue);
237
238     delete model;
239 }
240
241 void tst_qdeclarativexmllistmodel::testTypes_data()
242 {
243     QTest::addColumn<QString>("xml");
244     QTest::addColumn<QString>("roleName");
245     QTest::addColumn<QVariant>("expectedValue");
246
247     QTest::newRow("missing string field") << "<data></data>"
248             << "stringValue" << QVariant("");
249     QTest::newRow("empty string") << "<data><a-string></a-string></data>"
250             << "stringValue" << QVariant("");
251     QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
252             << "stringValue" << QVariant("5");
253     QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
254             << "stringValue" << QVariant("abc def g");
255
256     QTest::newRow("missing number field") << "<data></data>"
257             << "numberValue" << QVariant("");
258     double nan = qQNaN();
259     QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
260             << "numberValue" << QVariant(nan);
261     QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
262             << "numberValue" << QVariant(nan);
263     QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
264             << "numberValue" << QVariant("-1");
265     QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
266             << "numberValue" << QVariant("-1.5");
267     QTest::newRow("0") << "<data><a-number>0</a-number></data>"
268             << "numberValue" << QVariant("0");
269     QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
270             << "numberValue" << QVariant("1");
271     QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
272             << "numberValue" << QVariant("1.5");
273 }
274
275 void tst_qdeclarativexmllistmodel::cdata()
276 {
277     QDeclarativeComponent component(&engine, testFileUrl("recipes.qml"));
278     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
279     QVERIFY(model != 0);
280     QTRY_COMPARE(model->count(), 5);
281
282     QVERIFY(model->data(2, Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
283
284     delete model;
285 }
286
287 void tst_qdeclarativexmllistmodel::attributes()
288 {
289     QDeclarativeComponent component(&engine, testFileUrl("recipes.qml"));
290     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
291     QVERIFY(model != 0);
292     QTRY_COMPARE(model->count(), 5);
293     QCOMPARE(model->data(2, Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
294
295     delete model;
296 }
297
298 void tst_qdeclarativexmllistmodel::roles()
299 {
300     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
301     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
302     QVERIFY(model != 0);
303     QTRY_COMPARE(model->count(), 9);
304
305     QList<int> roles = model->roles();
306     QCOMPARE(roles.count(), 4);
307     QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
308     QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
309     QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
310     QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
311
312     delete model;
313 }
314
315 void tst_qdeclarativexmllistmodel::roleErrors()
316 {
317     QDeclarativeComponent component(&engine, testFileUrl("roleErrors.qml"));
318     QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":7:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
319     QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":10:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
320
321     //### make sure we receive all expected warning messages.
322     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
323     QVERIFY(model != 0);
324     QTRY_COMPARE(model->count(), 9);
325
326
327     //### should any of these return valid values?
328     QCOMPARE(model->data(3, Qt::UserRole), QVariant());
329     QCOMPARE(model->data(3, Qt::UserRole+1), QVariant());
330     QCOMPARE(model->data(3, Qt::UserRole+2), QVariant());
331
332     QEXPECT_FAIL("", "QTBUG-10797", Continue);
333     QCOMPARE(model->data(3, Qt::UserRole+3), QVariant());
334
335     delete model;
336 }
337
338 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
339 {
340     QDeclarativeComponent component(&engine, testFileUrl("unique.qml"));
341     QTest::ignoreMessage(QtWarningMsg, (testFileUrl("unique.qml").toString() + ":8:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
342     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
343     QVERIFY(model != 0);
344     QTRY_COMPARE(model->count(), 9);
345
346     QList<int> roles = model->roles();
347     QCOMPARE(roles.count(), 1);
348
349     delete model;
350 }
351
352
353 void tst_qdeclarativexmllistmodel::xml()
354 {
355     QFETCH(QString, xml);
356     QFETCH(int, count);
357
358     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
359     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
360
361     QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
362     QVERIFY(errorString(model).isEmpty());
363     QCOMPARE(model->property("progress").toDouble(), qreal(0.0));
364     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
365              QDeclarativeXmlListModel::Loading);
366     QTRY_COMPARE(spy.count(), 1); spy.clear();
367     QTest::qWait(50);
368     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
369              QDeclarativeXmlListModel::Ready);
370     QVERIFY(errorString(model).isEmpty());
371     QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
372     QCOMPARE(model->count(), 9);
373
374     // if xml is empty (i.e. clearing) it won't have any effect if a source is set
375     if (xml.isEmpty())
376         model->setProperty("source",QUrl());
377     model->setProperty("xml",xml);
378     QCOMPARE(model->property("progress").toDouble(), qreal(1.0));   // immediately goes to 1.0 if using setXml()
379     QTRY_COMPARE(spy.count(), 1); spy.clear();
380     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
381              QDeclarativeXmlListModel::Loading);
382     QTRY_COMPARE(spy.count(), 1); spy.clear();
383     if (xml.isEmpty())
384         QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
385                  QDeclarativeXmlListModel::Null);
386     else
387         QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
388                  QDeclarativeXmlListModel::Ready);
389     QVERIFY(errorString(model).isEmpty());
390     QCOMPARE(model->count(), count);
391
392     delete model;
393 }
394
395 void tst_qdeclarativexmllistmodel::xml_data()
396 {
397     QTest::addColumn<QString>("xml");
398     QTest::addColumn<int>("count");
399
400     QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
401     QTest::newRow("empty xml") << "" << 0;
402     QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
403 }
404
405 void tst_qdeclarativexmllistmodel::headers()
406 {
407     // ensure the QNetworkAccessManagers created for this test are immediately deleted
408     QDeclarativeEngine qmlEng;
409
410     CustomNetworkAccessManagerFactory factory;
411     qmlEng.setNetworkAccessManagerFactory(&factory);
412
413     QDeclarativeComponent component(&qmlEng, testFileUrl("model.qml"));
414     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
415     QVERIFY(model != 0);
416     QTRY_COMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
417                  QDeclarativeXmlListModel::Ready);
418
419     QVariantMap expectedHeaders;
420     expectedHeaders["Accept"] = "application/xml,*/*";
421
422     QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
423     foreach (const QString &header, expectedHeaders.keys()) {
424         QVERIFY(factory.lastSentHeaders.contains(header));
425         QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
426     }
427
428     delete model;
429 }
430
431 void tst_qdeclarativexmllistmodel::source()
432 {
433     QFETCH(QUrl, source);
434     QFETCH(int, count);
435     QFETCH(QDeclarativeXmlListModel::Status, status);
436
437     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
438     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
439     QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
440
441     QVERIFY(errorString(model).isEmpty());
442     QCOMPARE(model->property("progress").toDouble(), qreal(0.0));
443     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
444              QDeclarativeXmlListModel::Loading);
445     QTRY_COMPARE(spy.count(), 1); spy.clear();
446     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
447              QDeclarativeXmlListModel::Ready);
448     QVERIFY(errorString(model).isEmpty());
449     QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
450     QCOMPARE(model->count(), 9);
451
452     model->setProperty("source",source);
453     if (model->property("source").toString().isEmpty())
454         QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
455                  QDeclarativeXmlListModel::Null);
456     QCOMPARE(model->property("progress").toDouble(), qreal(0.0));
457     QTRY_COMPARE(spy.count(), 1); spy.clear();
458     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")),
459              QDeclarativeXmlListModel::Loading);
460     QVERIFY(errorString(model).isEmpty());
461
462     QEventLoop loop;
463     QTimer timer;
464     timer.setSingleShot(true);
465     connect(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)), &loop, SLOT(quit()));
466     connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
467     timer.start(20000);
468     loop.exec();
469
470     if (spy.count() == 0 && status != QDeclarativeXmlListModel::Ready) {
471         qWarning("QDeclarativeXmlListModel invalid source test timed out");
472     } else {
473         QCOMPARE(spy.count(), 1); spy.clear();
474     }
475
476     QCOMPARE(qvariant_cast<QDeclarativeXmlListModel::Status>(model->property("status")), status);
477     QCOMPARE(model->count(), count);
478
479     if (status == QDeclarativeXmlListModel::Ready)
480         QCOMPARE(model->property("progress").toDouble(), qreal(1.0));
481
482     QCOMPARE(errorString(model).isEmpty(), status == QDeclarativeXmlListModel::Ready);
483
484     delete model;
485 }
486
487 void tst_qdeclarativexmllistmodel::source_data()
488 {
489     QTest::addColumn<QUrl>("source");
490     QTest::addColumn<int>("count");
491     QTest::addColumn<QDeclarativeXmlListModel::Status>("status");
492
493     QTest::newRow("valid") << testFileUrl("model2.xml") << 2
494                            << QDeclarativeXmlListModel::Ready;
495     QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0
496                              << QDeclarativeXmlListModel::Error;
497
498     // empty file
499     QTemporaryFile *temp = new QTemporaryFile(this);
500     if (temp->open())
501         QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0
502                                     << QDeclarativeXmlListModel::Ready;
503     temp->close();
504 }
505
506 void tst_qdeclarativexmllistmodel::data()
507 {
508     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
509     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
510     QVERIFY(model != 0);
511
512     for (int i=0; i<9; i++)  {
513         for (int j=0; j<model->roles().count(); j++) {
514             QCOMPARE(model->data(i, j), QVariant());
515         }
516     }
517     QTRY_COMPARE(model->count(), 9);
518
519     delete model;
520 }
521
522 void tst_qdeclarativexmllistmodel::get()
523 {
524     QDeclarativeComponent component(&engine, testFileUrl("get.qml"));
525     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
526
527     QVERIFY(model != 0);
528
529     QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
530     QCOMPARE(model->property("preTest").toBool(), true);
531
532     QTRY_COMPARE(model->count(), 9);
533
534     QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
535     QCOMPARE(model->property("postTest").toBool(), true);
536
537     delete model;
538 }
539
540 void tst_qdeclarativexmllistmodel::reload()
541 {
542     // If no keys are used, the model should be rebuilt from scratch when
543     // reload() is called.
544
545     QDeclarativeComponent component(&engine, testFileUrl("model.qml"));
546     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
547     QVERIFY(model != 0);
548     QTRY_COMPARE(model->count(), 9);
549
550     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
551     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
552     QSignalSpy spyCount(model, SIGNAL(countChanged()));
553     //reload multiple times to test the xml query aborting
554     QMetaObject::invokeMethod(model, "reload");
555     QMetaObject::invokeMethod(model, "reload");
556     QCoreApplication::processEvents();
557     QMetaObject::invokeMethod(model, "reload");
558     QMetaObject::invokeMethod(model, "reload");
559     QTRY_COMPARE(spyCount.count(), 1);
560     QTRY_COMPARE(spyInsert.count(), 1);
561     QTRY_COMPARE(spyRemove.count(), 1);
562
563     QCOMPARE(spyInsert[0][0].toInt(), 0);
564     QCOMPARE(spyInsert[0][1].toInt(), 9);
565
566     QCOMPARE(spyRemove[0][0].toInt(), 0);
567     QCOMPARE(spyRemove[0][1].toInt(), 9);
568
569     delete model;
570 }
571
572 void tst_qdeclarativexmllistmodel::useKeys()
573 {
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.
577
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);
584
585     QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
586     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
587     QVERIFY(model != 0);
588
589     model->setProperty("xml",oldXml);
590     QTRY_COMPARE(model->count(), oldCount);
591
592     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
593     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
594     QSignalSpy spyCount(model, SIGNAL(countChanged()));
595
596     model->setProperty("xml",newXml);
597
598     if (oldCount != newData.count()) {
599         QTRY_COMPARE(model->count(), newData.count());
600         QCOMPARE(spyCount.count(), 1);
601     } else {
602         QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
603         QCOMPARE(spyCount.count(), 0);
604     }
605
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]);
610     }
611
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);
616     }
617
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);
622     }
623
624     delete model;
625 }
626
627 void tst_qdeclarativexmllistmodel::useKeys_data()
628 {
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");
635
636     QDeclarativeXmlModelData modelData;
637
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)
641         << modelData
642         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
643         << QList<QDeclarativeXmlListRange>();
644
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)
648         << modelData
649         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
650         << QList<QDeclarativeXmlListRange>();
651
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)
655         << modelData
656         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
657         << QList<QDeclarativeXmlListRange>();
658
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)
662         << modelData
663         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
664         << QList<QDeclarativeXmlListRange>();
665
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)
669         << modelData
670         << QList<QDeclarativeXmlListRange>()
671         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
672
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)
676         << modelData
677         << QList<QDeclarativeXmlListRange>()
678         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1));
679
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)
683         << modelData
684         << QList<QDeclarativeXmlListRange>()
685         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
686
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)
690         << modelData
691         << QList<QDeclarativeXmlListRange>()
692         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 3));
693
694     QTest::newRow("replace item")
695         << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
696         << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
697         << modelData
698         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1))
699         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
700
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)
704         << modelData
705         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
706         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
707
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)
711         << modelData
712         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
713         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2));
714
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)
718         << modelData
719         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
720         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
721
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)
725         << modelData
726         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
727         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
728 }
729
730 void tst_qdeclarativexmllistmodel::noKeysValueChanges()
731 {
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.
735
736     QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
737     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
738     QVERIFY(model != 0);
739     
740     QString xml;
741     
742     xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
743     model->setProperty("xml",xml);
744     QTRY_COMPARE(model->count(), 2);
745
746     model->setProperty("xml","");
747
748     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
749     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
750     QSignalSpy spyCount(model, SIGNAL(countChanged()));
751
752     xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
753     model->setProperty("xml",xml);
754
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"));
758
759     QVERIFY(spyInsert.count() == 0);
760     QVERIFY(spyRemove.count() == 0);
761     QVERIFY(spyCount.count() == 0);
762
763     QCOMPARE(model->count(), 2);
764
765     delete model;
766 }
767
768 void tst_qdeclarativexmllistmodel::keysChanged()
769 {
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).
773
774     QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
775     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
776     QVERIFY(model != 0);
777
778     QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
779     model->setProperty("xml",xml);
780     QTRY_COMPARE(model->count(), 2);
781
782     model->setProperty("xml","");
783
784     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
785     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
786     QSignalSpy spyCount(model, SIGNAL(countChanged()));
787
788     QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
789     model->setProperty("xml",xml);
790
791     QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
792
793     QCOMPARE(spyInsert.count(), 1);
794     QCOMPARE(spyInsert[0][0].toInt(), 0);
795     QCOMPARE(spyInsert[0][1].toInt(), 2);
796
797     QCOMPARE(spyRemove.count(), 1);
798     QCOMPARE(spyRemove[0][0].toInt(), 0);
799     QCOMPARE(spyRemove[0][1].toInt(), 2);
800
801     QCOMPARE(spyCount.count(), 0);
802
803     delete model;
804 }
805
806 void tst_qdeclarativexmllistmodel::threading()
807 {
808     QFETCH(int, xmlDataCount);
809
810     QDeclarativeComponent component(&engine, testFileUrl("roleKeys.qml"));
811
812     QListModelInterface *m1 = qobject_cast<QListModelInterface*>(component.create());
813     QVERIFY(m1 != 0); 
814     QListModelInterface *m2 = qobject_cast<QListModelInterface*>(component.create());
815     QVERIFY(m2 != 0); 
816     QListModelInterface *m3 = qobject_cast<QListModelInterface*>(component.create());
817     QVERIFY(m3 != 0); 
818
819     for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
820
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;";
826         }
827
828         //Set the xml data multiple times with randomized order and mixed with multiple event loops
829         //to test the xml query reloading/aborting, the result should be stable.
830         m1->setProperty("xml",makeItemXmlAndData(data1));
831         m2->setProperty("xml",makeItemXmlAndData(data2));
832         m3->setProperty("xml",makeItemXmlAndData(data3));
833         QCoreApplication::processEvents();
834         m2->setProperty("xml",makeItemXmlAndData(data2));
835         m1->setProperty("xml",makeItemXmlAndData(data1));
836         m2->setProperty("xml",makeItemXmlAndData(data2));
837         QCoreApplication::processEvents();
838         m3->setProperty("xml",makeItemXmlAndData(data3));
839         QCoreApplication::processEvents();
840         m2->setProperty("xml",makeItemXmlAndData(data2));
841         m1->setProperty("xml",makeItemXmlAndData(data1));
842         m2->setProperty("xml",makeItemXmlAndData(data2));
843         m3->setProperty("xml",makeItemXmlAndData(data3));
844         QCoreApplication::processEvents();
845         m2->setProperty("xml",makeItemXmlAndData(data2));
846         m3->setProperty("xml",makeItemXmlAndData(data3));
847         m3->setProperty("xml",makeItemXmlAndData(data3));
848         QCoreApplication::processEvents();
849
850         QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount);
851
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"));
856
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"));
860
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"));
864         }
865     }
866
867     delete m1;
868     delete m2;
869     delete m3;
870 }
871
872 void tst_qdeclarativexmllistmodel::threading_data()
873 {
874     QTest::addColumn<int>("xmlDataCount");
875
876     QTest::newRow("1") << 1;
877     QTest::newRow("2") << 2;
878     QTest::newRow("10") << 10;
879 }
880
881 void tst_qdeclarativexmllistmodel::propertyChanges()
882 {
883     QDeclarativeComponent component(&engine, testFileUrl("propertychanges.qml"));
884     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
885     QVERIFY(model != 0);
886     QTRY_COMPARE(model->count(), 9);
887
888     QObject *role = model->findChild<QObject*>("role");
889     QVERIFY(role);
890
891     QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
892     QSignalSpy querySpy(role, SIGNAL(queryChanged()));
893     QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
894
895     role->setProperty("name","size");
896     role->setProperty("query","size/string()");
897     role->setProperty("isKey",true);
898
899     QCOMPARE(role->property("name").toString(), QString("size"));
900     QCOMPARE(role->property("query").toString(), QString("size/string()"));
901     QVERIFY(role->property("isKey").toBool());
902
903     QCOMPARE(nameSpy.count(),1);
904     QCOMPARE(querySpy.count(),1);
905     QCOMPARE(isKeySpy.count(),1);
906
907     role->setProperty("name","size");
908     role->setProperty("query","size/string()");
909     role->setProperty("isKey",true);
910
911     QCOMPARE(nameSpy.count(),1);
912     QCOMPARE(querySpy.count(),1);
913     QCOMPARE(isKeySpy.count(),1);
914
915     QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
916     QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
917     QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
918     QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
919
920     model->setProperty("source",QUrl(""));
921     model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
922     model->setProperty("query","/Pets");
923     model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";");
924
925     QCOMPARE(model->property("source").toUrl(), QUrl(""));
926     QCOMPARE(model->property("xml").toString(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
927     QCOMPARE(model->property("query").toString(), QString("/Pets"));
928     QCOMPARE(model->property("namespaceDeclarations").toString(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
929
930     QTRY_VERIFY(model->count() == 1);
931
932     QCOMPARE(sourceSpy.count(),1);
933     QCOMPARE(xmlSpy.count(),1);
934     QCOMPARE(modelQuerySpy.count(),1);
935     QCOMPARE(namespaceDeclarationsSpy.count(),1);
936
937     model->setProperty("source",QUrl(""));
938     model->setProperty("xml","<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
939     model->setProperty("query","/Pets");
940     model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";");
941
942     QCOMPARE(sourceSpy.count(),1);
943     QCOMPARE(xmlSpy.count(),1);
944     QCOMPARE(modelQuerySpy.count(),1);
945     QCOMPARE(namespaceDeclarationsSpy.count(),1);
946
947     QTRY_VERIFY(model->count() == 1);
948     delete model;
949 }
950
951 void tst_qdeclarativexmllistmodel::roleCrash()
952 {
953     // don't crash
954     QDeclarativeComponent component(&engine, testFileUrl("roleCrash.qml"));
955     QListModelInterface *model = qobject_cast<QListModelInterface*>(component.create());
956     QVERIFY(model != 0);
957     delete model;
958 }
959
960 QTEST_MAIN(tst_qdeclarativexmllistmodel)
961
962 #include "tst_qdeclarativexmllistmodel.moc"