Remove "All rights reserved" line from license headers.
[profile/ivi/qtdeclarative.git] / tests / auto / qtquick1 / qdeclarativexmllistmodel / tst_qdeclarativexmllistmodel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <private/qdeclarativeengine_p.h>
42
43 #include <QtTest/QtTest>
44 #include <QtGlobal>
45 #include <math.h>
46
47 #include <qtest.h>
48 #include <QtTest/qsignalspy.h>
49 #include <QtDeclarative/qdeclarativenetworkaccessmanagerfactory.h>
50 #include <QtNetwork/qnetworkaccessmanager.h>
51 #include <QtNetwork/qnetworkrequest.h>
52 #include <QtCore/qtimer.h>
53 #include <QtCore/qfile.h>
54 #include <QtCore/qtemporaryfile.h>
55
56 #include <QtDeclarative/qdeclarativeengine.h>
57 #include <QtDeclarative/qdeclarativecomponent.h>
58 #include <QtQuick1/private/qdeclarativexmllistmodel_p.h>
59
60 typedef QPair<int, int> QDeclarativeXmlListRange;
61 typedef QList<QVariantList> QDeclarativeXmlModelData;
62
63 Q_DECLARE_METATYPE(QList<QDeclarativeXmlListRange>)
64 Q_DECLARE_METATYPE(QDeclarativeXmlModelData)
65 Q_DECLARE_METATYPE(QDeclarative1XmlListModel::Status)
66
67 class tst_qdeclarativexmllistmodel : public QObject
68
69 {
70     Q_OBJECT
71 public:
72     tst_qdeclarativexmllistmodel() {}
73
74 private slots:
75     void initTestCase() {
76         qRegisterMetaType<QDeclarative1XmlListModel::Status>("QDeclarative1XmlListModel::Status");
77     }
78
79     void buildModel();
80     void testTypes();
81     void testTypes_data();
82     void cdata();
83     void attributes();
84     void roles();
85     void roleErrors();
86     void uniqueRoleNames();
87     void headers();
88     void xml();
89     void xml_data();
90     void source();
91     void source_data();
92     void data();
93     void get();
94     void reload();
95     void useKeys();
96     void useKeys_data();
97     void noKeysValueChanges();
98     void keysChanged();
99     void threading();
100     void threading_data();
101     void propertyChanges();
102
103     void roleCrash();
104
105 private:
106     QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
107     {
108         if (modelData)
109             modelData->clear();
110         QString xml;
111
112         if (!data.isEmpty()) {
113             QStringList items = data.split(";");
114             foreach(const QString &item, items) {
115                 if (item.isEmpty())
116                     continue;
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;
124                         continue;
125                     }
126                     xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
127                     if (!modelData)
128                         continue;
129                     bool isNum = false;
130                     int number = values[1].toInt(&isNum);
131                     if (isNum)
132                         variants << number;
133                     else
134                         variants << values[1];
135                 }
136                 xml += QLatin1String("</item>");
137                 if (modelData)
138                     modelData->append(variants);
139             }
140         }
141
142         QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
143         return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
144     }
145
146     QDeclarativeEngine engine;
147 };
148
149 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
150 {
151     Q_OBJECT
152 public:
153     QVariantMap lastSentHeaders;
154
155 protected:
156     QNetworkAccessManager *create(QObject *parent);
157 };
158
159 class CustomNetworkAccessManager : public QNetworkAccessManager
160 {
161     Q_OBJECT
162 public:
163     CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
164         : QNetworkAccessManager(parent), m_factory(factory) {}
165
166 protected:
167     QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
168     {
169         if (m_factory) {
170             QVariantMap map;
171             foreach (const QString &header, req.rawHeaderList())
172                 map[header] = req.rawHeader(header.toUtf8());
173             m_factory->lastSentHeaders = map;
174         }
175         return QNetworkAccessManager::createRequest(op, req, outgoingData);
176     }
177
178     QPointer<CustomNetworkAccessManagerFactory> m_factory;
179 };
180
181 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
182 {
183     return new CustomNetworkAccessManager(this, parent);
184 }
185
186
187 void tst_qdeclarativexmllistmodel::buildModel()
188 {
189     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
190     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
191     QVERIFY(model != 0);
192     QTRY_COMPARE(model->count(), 9);
193
194     QList<int> roles;
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"));
202
203     delete model;
204 }
205
206 void tst_qdeclarativexmllistmodel::testTypes()
207 {
208     QFETCH(QString, xml);
209     QFETCH(QString, roleName);
210     QFETCH(QVariant, expectedValue);
211
212     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/testtypes.qml"));
213     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
214     QVERIFY(model != 0);
215     model->setXml(xml.toUtf8());
216     model->reload();
217     QTRY_COMPARE(model->count(), 1);
218
219     int role = -1;
220     foreach (int i, model->roles()) {
221         if (model->toString(i) == roleName) {
222             role = i;
223             break;
224         }
225     }
226     QVERIFY(role >= 0);
227
228     if (expectedValue.toString() == "nan")
229         QVERIFY(qIsNaN(model->data(0, role).toDouble()));
230     else
231         QCOMPARE(model->data(0, role), expectedValue);
232
233     delete model;
234 }
235
236 void tst_qdeclarativexmllistmodel::testTypes_data()
237 {
238     QTest::addColumn<QString>("xml");
239     QTest::addColumn<QString>("roleName");
240     QTest::addColumn<QVariant>("expectedValue");
241
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");
250
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");
268 }
269
270 void tst_qdeclarativexmllistmodel::cdata()
271 {
272     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
273     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
274     QVERIFY(model != 0);
275     QTRY_COMPARE(model->count(), 5);
276
277     QList<int> roles;
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>")));
282
283     delete model;
284 }
285
286 void tst_qdeclarativexmllistmodel::attributes()
287 {
288     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
289     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
290     QVERIFY(model != 0);
291     QTRY_COMPARE(model->count(), 5);
292     QList<int> roles;
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"));
297
298     delete model;
299 }
300
301 void tst_qdeclarativexmllistmodel::roles()
302 {
303     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
304     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
305     QVERIFY(model != 0);
306     QTRY_COMPARE(model->count(), 9);
307
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"));
314
315     delete model;
316 }
317
318 void tst_qdeclarativexmllistmodel::roleErrors()
319 {
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());
323
324     //### make sure we receive all expected warning messages.
325     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
326     QVERIFY(model != 0);
327     QTRY_COMPARE(model->count(), 9);
328
329     QList<int> roles;
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);
333
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());
338
339     QEXPECT_FAIL("", "QTBUG-10797", Continue);
340     QCOMPARE(data.value(Qt::UserRole+3), QVariant());
341
342     delete model;
343 }
344
345 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
346 {
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());
350     QVERIFY(model != 0);
351     QTRY_COMPARE(model->count(), 9);
352
353     QList<int> roles = model->roles();
354     QCOMPARE(roles.count(), 1);
355
356     delete model;
357 }
358
359
360 void tst_qdeclarativexmllistmodel::xml()
361 {
362     QFETCH(QString, xml);
363     QFETCH(int, count);
364
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)));
368
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);
377
378     // if xml is empty (i.e. clearing) it won't have any effect if a source is set
379     if (xml.isEmpty())
380         model->setSource(QUrl());
381     model->setXml(xml);
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);
389
390     delete model;
391 }
392
393 void tst_qdeclarativexmllistmodel::xml_data()
394 {
395     QTest::addColumn<QString>("xml");
396     QTest::addColumn<int>("count");
397
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;
401 }
402
403 void tst_qdeclarativexmllistmodel::headers()
404 {
405     // ensure the QNetworkAccessManagers created for this test are immediately deleted
406     QDeclarativeEngine qmlEng;
407
408     CustomNetworkAccessManagerFactory factory;
409     qmlEng.setNetworkAccessManagerFactory(&factory);
410
411     QDeclarativeComponent component(&qmlEng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
412     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
413     QVERIFY(model != 0);
414     QTRY_COMPARE(model->status(), QDeclarative1XmlListModel::Ready);
415
416     QVariantMap expectedHeaders;
417     expectedHeaders["Accept"] = "application/xml,*/*";
418
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());
423     }
424
425     delete model;
426 }
427
428 void tst_qdeclarativexmllistmodel::source()
429 {
430     QFETCH(QUrl, source);
431     QFETCH(int, count);
432     QFETCH(QDeclarative1XmlListModel::Status, status);
433
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)));
437
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);
446
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());
452
453     QEventLoop loop;
454     QTimer timer;
455     timer.setSingleShot(true);
456     connect(model, SIGNAL(statusChanged(QDeclarative1XmlListModel::Status)), &loop, SLOT(quit()));
457     connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
458     timer.start(20000);
459     loop.exec();
460
461     if (spy.count() == 0 && status != QDeclarative1XmlListModel::Ready) {
462         qWarning("QDeclarative1XmlListModel invalid source test timed out");
463     } else {
464         QCOMPARE(spy.count(), 1); spy.clear();
465     }
466
467     QCOMPARE(model->status(), status);
468     QCOMPARE(model->count(), count);
469
470     if (status == QDeclarative1XmlListModel::Ready)
471         QCOMPARE(model->progress(), qreal(1.0));
472
473     QCOMPARE(model->errorString().isEmpty(), status == QDeclarative1XmlListModel::Ready);
474
475     delete model;
476 }
477
478 void tst_qdeclarativexmllistmodel::source_data()
479 {
480     QTest::addColumn<QUrl>("source");
481     QTest::addColumn<int>("count");
482     QTest::addColumn<QDeclarative1XmlListModel::Status>("status");
483
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;
486
487     // empty file
488     QTemporaryFile *temp = new QTemporaryFile(this);
489     if (temp->open())
490         QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QDeclarative1XmlListModel::Ready;
491     temp->close();
492 }
493
494 void tst_qdeclarativexmllistmodel::data()
495 {
496     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
497     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());    
498     QVERIFY(model != 0);
499
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());
507         }
508     }
509     QTRY_COMPARE(model->count(), 9);
510
511     delete model;
512 }
513
514 void tst_qdeclarativexmllistmodel::get()
515 {
516     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/get.qml"));
517     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());    
518     QVERIFY(model != 0);
519
520     QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
521     QCOMPARE(model->property("preTest").toBool(), true);
522
523     QTRY_COMPARE(model->count(), 9);
524
525     QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
526     QCOMPARE(model->property("postTest").toBool(), true);
527
528     delete model;
529 }
530
531 void tst_qdeclarativexmllistmodel::reload()
532 {
533     // If no keys are used, the model should be rebuilt from scratch when
534     // reload() is called.
535
536     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
537     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
538     QVERIFY(model != 0);
539     QTRY_COMPARE(model->count(), 9);
540
541     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
542     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
543     QSignalSpy spyCount(model, SIGNAL(countChanged())); 
544
545     //reload multiple times to test the xml query aborting
546     model->reload();
547     model->reload();
548     QCoreApplication::processEvents();
549     model->reload();
550     model->reload();
551     QTRY_COMPARE(spyCount.count(), 1);
552     QTRY_COMPARE(spyInsert.count(), 1);
553     QTRY_COMPARE(spyRemove.count(), 1);
554
555     QCOMPARE(spyInsert[0][0].toInt(), 0);
556     QCOMPARE(spyInsert[0][1].toInt(), 9);
557
558     QCOMPARE(spyRemove[0][0].toInt(), 0);
559     QCOMPARE(spyRemove[0][1].toInt(), 9);
560
561     delete model;
562 }
563
564 void tst_qdeclarativexmllistmodel::useKeys()
565 {
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.
569
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);
576
577     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
578     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
579     QVERIFY(model != 0);
580
581     model->setXml(oldXml);
582     QTRY_COMPARE(model->count(), oldCount);
583
584     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
585     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
586     QSignalSpy spyCount(model, SIGNAL(countChanged()));
587
588     model->setXml(newXml);
589
590     if (oldCount != newData.count()) {
591         QTRY_COMPARE(model->count(), newData.count());
592         QCOMPARE(spyCount.count(), 1);
593     } else {
594         QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
595         QCOMPARE(spyCount.count(), 0);
596     }
597
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]);
602     }
603
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);
608     }
609
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);
614     }
615
616     delete model;
617 }
618
619 void tst_qdeclarativexmllistmodel::useKeys_data()
620 {
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");
627
628     QDeclarativeXmlModelData modelData;
629
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)
633         << modelData
634         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
635         << QList<QDeclarativeXmlListRange>();
636
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)
640         << modelData
641         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
642         << QList<QDeclarativeXmlListRange>();
643
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)
647         << modelData
648         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
649         << QList<QDeclarativeXmlListRange>();
650
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)
654         << modelData
655         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
656         << QList<QDeclarativeXmlListRange>();
657
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)
661         << modelData
662         << QList<QDeclarativeXmlListRange>()
663         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
664
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)
668         << modelData
669         << QList<QDeclarativeXmlListRange>()
670         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1));
671
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)
675         << modelData
676         << QList<QDeclarativeXmlListRange>()
677         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
678
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)
682         << modelData
683         << QList<QDeclarativeXmlListRange>()
684         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 3));
685
686     QTest::newRow("replace item")
687         << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
688         << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
689         << modelData
690         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1))
691         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
692
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)
696         << modelData
697         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
698         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
699
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)
703         << modelData
704         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
705         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2));
706
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)
710         << modelData
711         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
712         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
713
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)
717         << modelData
718         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
719         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
720 }
721
722 void tst_qdeclarativexmllistmodel::noKeysValueChanges()
723 {
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.
727
728     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
729     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
730     QVERIFY(model != 0);
731     
732     QString xml;
733     
734     xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
735     model->setXml(xml);
736     QTRY_COMPARE(model->count(), 2);
737
738     model->setXml("");
739
740     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
741     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
742     QSignalSpy spyCount(model, SIGNAL(countChanged()));
743
744     xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
745     model->setXml(xml);
746
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"));
750
751     QVERIFY(spyInsert.count() == 0);
752     QVERIFY(spyRemove.count() == 0);
753     QVERIFY(spyCount.count() == 0);
754
755     QCOMPARE(model->count(), 2);
756
757     delete model;
758 }
759
760 void tst_qdeclarativexmllistmodel::keysChanged()
761 {
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).
765
766     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
767     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
768     QVERIFY(model != 0);
769
770     QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
771     model->setXml(xml);
772     QTRY_COMPARE(model->count(), 2);
773
774     model->setXml("");
775
776     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
777     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
778     QSignalSpy spyCount(model, SIGNAL(countChanged()));
779
780     QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
781     model->setXml(xml);
782
783     QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
784
785     QCOMPARE(spyInsert.count(), 1);
786     QCOMPARE(spyInsert[0][0].toInt(), 0);
787     QCOMPARE(spyInsert[0][1].toInt(), 2);
788
789     QCOMPARE(spyRemove.count(), 1);
790     QCOMPARE(spyRemove[0][0].toInt(), 0);
791     QCOMPARE(spyRemove[0][1].toInt(), 2);
792
793     QCOMPARE(spyCount.count(), 0);
794
795     delete model;
796 }
797
798 void tst_qdeclarativexmllistmodel::threading()
799 {
800     QFETCH(int, xmlDataCount);
801
802     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
803
804     QDeclarative1XmlListModel *m1 = qobject_cast<QDeclarative1XmlListModel*>(component.create());
805     QVERIFY(m1 != 0); 
806     QDeclarative1XmlListModel *m2 = qobject_cast<QDeclarative1XmlListModel*>(component.create());
807     QVERIFY(m2 != 0); 
808     QDeclarative1XmlListModel *m3 = qobject_cast<QDeclarative1XmlListModel*>(component.create());
809     QVERIFY(m3 != 0); 
810
811     for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
812
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;";
818         }
819
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();
841
842         QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount);
843
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"));
848
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"));
852
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"));
856         }
857     }
858
859     delete m1;
860     delete m2;
861     delete m3;
862 }
863
864 void tst_qdeclarativexmllistmodel::threading_data()
865 {
866     QTest::addColumn<int>("xmlDataCount");
867
868     QTest::newRow("1") << 1;
869     QTest::newRow("2") << 2;
870     QTest::newRow("10") << 10;
871 }
872
873 void tst_qdeclarativexmllistmodel::propertyChanges()
874 {
875     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/propertychanges.qml"));
876     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
877     QVERIFY(model != 0);
878     QTRY_COMPARE(model->count(), 9);
879
880     QDeclarative1XmlListModelRole *role = model->findChild<QDeclarative1XmlListModelRole*>("role");
881     QVERIFY(role);
882
883     QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
884     QSignalSpy querySpy(role, SIGNAL(queryChanged()));
885     QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
886
887     role->setName("size");
888     role->setQuery("size/string()");
889     role->setIsKey(true);
890
891     QCOMPARE(role->name(), QString("size"));
892     QCOMPARE(role->query(), QString("size/string()"));
893     QVERIFY(role->isKey());
894
895     QCOMPARE(nameSpy.count(),1);
896     QCOMPARE(querySpy.count(),1);
897     QCOMPARE(isKeySpy.count(),1);
898
899     role->setName("size");
900     role->setQuery("size/string()");
901     role->setIsKey(true);
902
903     QCOMPARE(nameSpy.count(),1);
904     QCOMPARE(querySpy.count(),1);
905     QCOMPARE(isKeySpy.count(),1);
906
907     QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
908     QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
909     QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
910     QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
911
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/\";");
916
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/\";"));
921
922     QTRY_VERIFY(model->count() == 1);
923
924     QCOMPARE(sourceSpy.count(),1);
925     QCOMPARE(xmlSpy.count(),1);
926     QCOMPARE(modelQuerySpy.count(),1);
927     QCOMPARE(namespaceDeclarationsSpy.count(),1);
928
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/\";");
933
934     QCOMPARE(sourceSpy.count(),1);
935     QCOMPARE(xmlSpy.count(),1);
936     QCOMPARE(modelQuerySpy.count(),1);
937     QCOMPARE(namespaceDeclarationsSpy.count(),1);
938
939     QTRY_VERIFY(model->count() == 1);
940     delete model;
941 }
942
943 void tst_qdeclarativexmllistmodel::roleCrash()
944 {
945     // don't crash
946     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleCrash.qml"));
947     QDeclarative1XmlListModel *model = qobject_cast<QDeclarative1XmlListModel*>(component.create());
948     QVERIFY(model != 0);
949     delete model;
950 }
951
952 QTEST_MAIN(tst_qdeclarativexmllistmodel)
953
954 #include "tst_qdeclarativexmllistmodel.moc"