3328232236826fecdf20842e430a88f3dfad1d05
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qdeclarativexmllistmodel / tst_qdeclarativexmllistmodel.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
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 #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 #include "../shared/util.h"
56
57 #include <QtDeclarative/qdeclarativeengine.h>
58 #include <QtDeclarative/qdeclarativecomponent.h>
59 #include <private/qdeclarativexmllistmodel_p.h>
60 #include "../../../shared/util.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 QObject
70
71 {
72     Q_OBJECT
73 public:
74     tst_qdeclarativexmllistmodel() {}
75
76 private slots:
77     void initTestCase() {
78         qRegisterMetaType<QDeclarativeXmlListModel::Status>("QDeclarativeXmlListModel::Status");
79     }
80
81     void buildModel();
82     void testTypes();
83     void testTypes_data();
84     void cdata();
85     void attributes();
86     void roles();
87     void roleErrors();
88     void uniqueRoleNames();
89     void headers();
90     void xml();
91     void xml_data();
92     void source();
93     void source_data();
94     void data();
95     void get();
96     void reload();
97     void useKeys();
98     void useKeys_data();
99     void noKeysValueChanges();
100     void keysChanged();
101     void threading();
102     void threading_data();
103     void propertyChanges();
104
105     void roleCrash();
106
107 private:
108     QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
109     {
110         if (modelData)
111             modelData->clear();
112         QString xml;
113
114         if (!data.isEmpty()) {
115             QStringList items = data.split(";");
116             foreach(const QString &item, items) {
117                 if (item.isEmpty())
118                     continue;
119                 QVariantList variants;
120                 xml += QLatin1String("<item>");
121                 QStringList fields = item.split(",");
122                 foreach(const QString &field, fields) {
123                     QStringList values = field.split("=");
124                     if (values.count() != 2) {
125                         qWarning() << "makeItemXmlAndData: invalid field:" << field;
126                         continue;
127                     }
128                     xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
129                     if (!modelData)
130                         continue;
131                     bool isNum = false;
132                     int number = values[1].toInt(&isNum);
133                     if (isNum)
134                         variants << number;
135                     else
136                         variants << values[1];
137                 }
138                 xml += QLatin1String("</item>");
139                 if (modelData)
140                     modelData->append(variants);
141             }
142         }
143
144         QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
145         return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
146     }
147
148     QDeclarativeEngine engine;
149 };
150
151 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
152 {
153     Q_OBJECT
154 public:
155     QVariantMap lastSentHeaders;
156
157 protected:
158     QNetworkAccessManager *create(QObject *parent);
159 };
160
161 class CustomNetworkAccessManager : public QNetworkAccessManager
162 {
163     Q_OBJECT
164 public:
165     CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
166         : QNetworkAccessManager(parent), m_factory(factory) {}
167
168 protected:
169     QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
170     {
171         if (m_factory) {
172             QVariantMap map;
173             foreach (const QString &header, req.rawHeaderList())
174                 map[header] = req.rawHeader(header.toUtf8());
175             m_factory->lastSentHeaders = map;
176         }
177         return QNetworkAccessManager::createRequest(op, req, outgoingData);
178     }
179
180     QPointer<CustomNetworkAccessManagerFactory> m_factory;
181 };
182
183 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
184 {
185     return new CustomNetworkAccessManager(this, parent);
186 }
187
188
189 void tst_qdeclarativexmllistmodel::buildModel()
190 {
191     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
192     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
193     QVERIFY(model != 0);
194     QTRY_COMPARE(model->count(), 9);
195
196     QList<int> roles;
197     roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
198     QHash<int, QVariant> data = model->data(3, roles);
199     QVERIFY(data.count() == 4);
200     QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Spot"));
201     QCOMPARE(data.value(Qt::UserRole+1).toString(), QLatin1String("Dog"));
202     QCOMPARE(data.value(Qt::UserRole+2).toInt(), 9);
203     QCOMPARE(data.value(Qt::UserRole+3).toString(), QLatin1String("Medium"));
204
205     delete model;
206 }
207
208 void tst_qdeclarativexmllistmodel::testTypes()
209 {
210     QFETCH(QString, xml);
211     QFETCH(QString, roleName);
212     QFETCH(QVariant, expectedValue);
213
214     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("testtypes.qml")));
215     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
216     QVERIFY(model != 0);
217     model->setXml(xml.toUtf8());
218     model->reload();
219     QTRY_COMPARE(model->count(), 1);
220
221     int role = -1;
222     foreach (int i, model->roles()) {
223         if (model->toString(i) == roleName) {
224             role = i;
225             break;
226         }
227     }
228     QVERIFY(role >= 0);
229
230     if (expectedValue.toString() == "nan")
231         QVERIFY(qIsNaN(model->data(0, role).toDouble()));
232     else
233         QCOMPARE(model->data(0, role), expectedValue);
234
235     delete model;
236 }
237
238 void tst_qdeclarativexmllistmodel::testTypes_data()
239 {
240     QTest::addColumn<QString>("xml");
241     QTest::addColumn<QString>("roleName");
242     QTest::addColumn<QVariant>("expectedValue");
243
244     QTest::newRow("missing string field") << "<data></data>"
245             << "stringValue" << QVariant("");
246     QTest::newRow("empty string") << "<data><a-string></a-string></data>"
247             << "stringValue" << QVariant("");
248     QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
249             << "stringValue" << QVariant("5");
250     QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
251             << "stringValue" << QVariant("abc def g");
252
253     QTest::newRow("missing number field") << "<data></data>"
254             << "numberValue" << QVariant("");
255     double nan = qQNaN();
256     QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
257             << "numberValue" << QVariant(nan);
258     QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
259             << "numberValue" << QVariant(nan);
260     QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
261             << "numberValue" << QVariant("-1");
262     QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
263             << "numberValue" << QVariant("-1.5");
264     QTest::newRow("0") << "<data><a-number>0</a-number></data>"
265             << "numberValue" << QVariant("0");
266     QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
267             << "numberValue" << QVariant("1");
268     QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
269             << "numberValue" << QVariant("1.5");
270 }
271
272 void tst_qdeclarativexmllistmodel::cdata()
273 {
274     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("recipes.qml")));
275     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
276     QVERIFY(model != 0);
277     QTRY_COMPARE(model->count(), 5);
278
279     QList<int> roles;
280     roles << Qt::UserRole + 2;
281     QHash<int, QVariant> data = model->data(2, roles);
282     QVERIFY(data.count() == 1);
283     QVERIFY(data.value(Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
284
285     delete model;
286 }
287
288 void tst_qdeclarativexmllistmodel::attributes()
289 {
290     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("recipes.qml")));
291     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
292     QVERIFY(model != 0);
293     QTRY_COMPARE(model->count(), 5);
294     QList<int> roles;
295     roles << Qt::UserRole;
296     QHash<int, QVariant> data = model->data(2, roles);
297     QVERIFY(data.count() == 1);
298     QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
299
300     delete model;
301 }
302
303 void tst_qdeclarativexmllistmodel::roles()
304 {
305     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
306     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
307     QVERIFY(model != 0);
308     QTRY_COMPARE(model->count(), 9);
309
310     QList<int> roles = model->roles();
311     QCOMPARE(roles.count(), 4);
312     QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
313     QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
314     QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
315     QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
316
317     delete model;
318 }
319
320 void tst_qdeclarativexmllistmodel::roleErrors()
321 {
322     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("roleErrors.qml")));
323     QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(TESTDATA("roleErrors.qml")).toString() + ":6:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
324     QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(TESTDATA("roleErrors.qml")).toString() + ":9:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
325
326     //### make sure we receive all expected warning messages.
327     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
328     QVERIFY(model != 0);
329     QTRY_COMPARE(model->count(), 9);
330
331     QList<int> roles;
332     roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
333     QHash<int, QVariant> data = model->data(3, roles);
334     QVERIFY(data.count() == 4);
335
336     //### should any of these return valid values?
337     QCOMPARE(data.value(Qt::UserRole), QVariant());
338     QCOMPARE(data.value(Qt::UserRole+1), QVariant());
339     QCOMPARE(data.value(Qt::UserRole+2), QVariant());
340
341     QEXPECT_FAIL("", "QTBUG-10797", Continue);
342     QCOMPARE(data.value(Qt::UserRole+3), QVariant());
343
344     delete model;
345 }
346
347 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
348 {
349     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("unique.qml")));
350     QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(TESTDATA("unique.qml")).toString() + ":7:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
351     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
352     QVERIFY(model != 0);
353     QTRY_COMPARE(model->count(), 9);
354
355     QList<int> roles = model->roles();
356     QCOMPARE(roles.count(), 1);
357
358     delete model;
359 }
360
361
362 void tst_qdeclarativexmllistmodel::xml()
363 {
364     QFETCH(QString, xml);
365     QFETCH(int, count);
366
367     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
368     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
369     QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
370
371     QVERIFY(model->errorString().isEmpty());
372     QCOMPARE(model->progress(), qreal(0.0));
373     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
374     QTRY_COMPARE(spy.count(), 1); spy.clear();
375     QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
376     QVERIFY(model->errorString().isEmpty());
377     QCOMPARE(model->progress(), qreal(1.0));
378     QCOMPARE(model->count(), 9);
379
380     // if xml is empty (i.e. clearing) it won't have any effect if a source is set
381     if (xml.isEmpty())
382         model->setSource(QUrl());
383     model->setXml(xml);
384     QCOMPARE(model->progress(), qreal(1.0));   // immediately goes to 1.0 if using setXml()
385     QTRY_COMPARE(spy.count(), 1); spy.clear();
386     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
387     QTRY_COMPARE(spy.count(), 1); spy.clear();
388     if (xml.isEmpty())
389         QCOMPARE(model->status(), QDeclarativeXmlListModel::Null);
390     else
391         QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
392     QVERIFY(model->errorString().isEmpty());
393     QCOMPARE(model->count(), count);
394
395     delete model;
396 }
397
398 void tst_qdeclarativexmllistmodel::xml_data()
399 {
400     QTest::addColumn<QString>("xml");
401     QTest::addColumn<int>("count");
402
403     QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
404     QTest::newRow("empty xml") << "" << 0;
405     QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
406 }
407
408 void tst_qdeclarativexmllistmodel::headers()
409 {
410     // ensure the QNetworkAccessManagers created for this test are immediately deleted
411     QDeclarativeEngine qmlEng;
412
413     CustomNetworkAccessManagerFactory factory;
414     qmlEng.setNetworkAccessManagerFactory(&factory);
415
416     QDeclarativeComponent component(&qmlEng, QUrl::fromLocalFile(TESTDATA("model.qml")));
417     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
418     QVERIFY(model != 0);
419     QTRY_COMPARE(model->status(), QDeclarativeXmlListModel::Ready);
420
421     QVariantMap expectedHeaders;
422     expectedHeaders["Accept"] = "application/xml,*/*";
423
424     QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
425     foreach (const QString &header, expectedHeaders.keys()) {
426         QVERIFY(factory.lastSentHeaders.contains(header));
427         QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
428     }
429
430     delete model;
431 }
432
433 void tst_qdeclarativexmllistmodel::source()
434 {
435     QFETCH(QUrl, source);
436     QFETCH(int, count);
437     QFETCH(QDeclarativeXmlListModel::Status, status);
438
439     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
440     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
441     QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
442
443     QVERIFY(model->errorString().isEmpty());
444     QCOMPARE(model->progress(), qreal(0.0));
445     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
446     QTRY_COMPARE(spy.count(), 1); spy.clear();
447     QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
448     QVERIFY(model->errorString().isEmpty());
449     QCOMPARE(model->progress(), qreal(1.0));
450     QCOMPARE(model->count(), 9);
451
452     model->setSource(source);
453     if (model->source().isEmpty())
454         QCOMPARE(model->status(), QDeclarativeXmlListModel::Null);
455     QCOMPARE(model->progress(), qreal(0.0));
456     QTRY_COMPARE(spy.count(), 1); spy.clear();
457     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
458     QVERIFY(model->errorString().isEmpty());
459
460     QEventLoop loop;
461     QTimer timer;
462     timer.setSingleShot(true);
463     connect(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)), &loop, SLOT(quit()));
464     connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
465     timer.start(20000);
466     loop.exec();
467
468     if (spy.count() == 0 && status != QDeclarativeXmlListModel::Ready) {
469         qWarning("QDeclarativeXmlListModel invalid source test timed out");
470     } else {
471         QCOMPARE(spy.count(), 1); spy.clear();
472     }
473
474     QCOMPARE(model->status(), status);
475     QCOMPARE(model->count(), count);
476
477     if (status == QDeclarativeXmlListModel::Ready)
478         QCOMPARE(model->progress(), qreal(1.0));
479
480     QCOMPARE(model->errorString().isEmpty(), status == QDeclarativeXmlListModel::Ready);
481
482     delete model;
483 }
484
485 void tst_qdeclarativexmllistmodel::source_data()
486 {
487     QTest::addColumn<QUrl>("source");
488     QTest::addColumn<int>("count");
489     QTest::addColumn<QDeclarativeXmlListModel::Status>("status");
490
491     QTest::newRow("valid") << QUrl::fromLocalFile(TESTDATA("model2.xml")) << 2 << QDeclarativeXmlListModel::Ready;
492     QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 << QDeclarativeXmlListModel::Error;
493
494     // empty file
495     QTemporaryFile *temp = new QTemporaryFile(this);
496     if (temp->open())
497         QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QDeclarativeXmlListModel::Ready;
498     temp->close();
499 }
500
501 void tst_qdeclarativexmllistmodel::data()
502 {
503     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
504     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());    
505     QVERIFY(model != 0);
506
507     QHash<int,QVariant> blank;
508     for (int i=0; i<model->roles().count(); i++)
509         blank.insert(model->roles()[i], QVariant());
510     for (int i=0; i<9; i++)  {
511         QCOMPARE(model->data(i, model->roles()), blank);
512         for (int j=0; j<model->roles().count(); j++) {
513             QCOMPARE(model->data(i, j), QVariant());
514         }
515     }
516     QTRY_COMPARE(model->count(), 9);
517
518     delete model;
519 }
520
521 void tst_qdeclarativexmllistmodel::get()
522 {
523     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("get.qml")));
524     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
525
526     QVERIFY(model != 0);
527
528     QVERIFY(QMetaObject::invokeMethod(model, "runPreTest"));
529     QCOMPARE(model->property("preTest").toBool(), true);
530
531     QTRY_COMPARE(model->count(), 9);
532
533     QVERIFY(QMetaObject::invokeMethod(model, "runPostTest"));
534     QCOMPARE(model->property("postTest").toBool(), true);
535
536     delete model;
537 }
538
539 void tst_qdeclarativexmllistmodel::reload()
540 {
541     // If no keys are used, the model should be rebuilt from scratch when
542     // reload() is called.
543
544     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(TESTDATA("model.qml")));
545     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
546     QVERIFY(model != 0);
547     QTRY_COMPARE(model->count(), 9);
548
549     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
550     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
551     QSignalSpy spyCount(model, SIGNAL(countChanged())); 
552
553     //reload multiple times to test the xml query aborting
554     model->reload();
555     model->reload();
556     QCoreApplication::processEvents();
557     model->reload();
558     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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
586     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
587     QVERIFY(model != 0);
588
589     model->setXml(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->setXml(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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
737     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(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->setXml(xml);
744     QTRY_COMPARE(model->count(), 2);
745
746     model->setXml("");
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->setXml(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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
775     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(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->setXml(xml);
780     QTRY_COMPARE(model->count(), 2);
781
782     model->setXml("");
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->setXml(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, QUrl::fromLocalFile(TESTDATA("roleKeys.qml")));
811
812     QDeclarativeXmlListModel *m1 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
813     QVERIFY(m1 != 0); 
814     QDeclarativeXmlListModel *m2 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
815     QVERIFY(m2 != 0); 
816     QDeclarativeXmlListModel *m3 = qobject_cast<QDeclarativeXmlListModel*>(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->setXml(makeItemXmlAndData(data1));
831         m2->setXml(makeItemXmlAndData(data2));
832         m3->setXml(makeItemXmlAndData(data3));
833         QCoreApplication::processEvents();
834         m2->setXml(makeItemXmlAndData(data2));
835         m1->setXml(makeItemXmlAndData(data1));
836         m2->setXml(makeItemXmlAndData(data2));
837         QCoreApplication::processEvents();
838         m3->setXml(makeItemXmlAndData(data3));
839         QCoreApplication::processEvents();
840         m2->setXml(makeItemXmlAndData(data2));
841         m1->setXml(makeItemXmlAndData(data1));
842         m2->setXml(makeItemXmlAndData(data2));
843         m3->setXml(makeItemXmlAndData(data3));
844         QCoreApplication::processEvents();
845         m2->setXml(makeItemXmlAndData(data2));
846         m3->setXml(makeItemXmlAndData(data3));
847         m3->setXml(makeItemXmlAndData(data3));
848         QCoreApplication::processEvents();
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, QUrl::fromLocalFile(TESTDATA("propertychanges.qml")));
884     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
885     QVERIFY(model != 0);
886     QTRY_COMPARE(model->count(), 9);
887
888     QDeclarativeXmlListModelRole *role = model->findChild<QDeclarativeXmlListModelRole*>("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->setName("size");
896     role->setQuery("size/string()");
897     role->setIsKey(true);
898
899     QCOMPARE(role->name(), QString("size"));
900     QCOMPARE(role->query(), QString("size/string()"));
901     QVERIFY(role->isKey());
902
903     QCOMPARE(nameSpy.count(),1);
904     QCOMPARE(querySpy.count(),1);
905     QCOMPARE(isKeySpy.count(),1);
906
907     role->setName("size");
908     role->setQuery("size/string()");
909     role->setIsKey(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->setSource(QUrl(""));
921     model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
922     model->setQuery("/Pets");
923     model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
924
925     QCOMPARE(model->source(), QUrl(""));
926     QCOMPARE(model->xml(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
927     QCOMPARE(model->query(), QString("/Pets"));
928     QCOMPARE(model->namespaceDeclarations(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
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->setSource(QUrl(""));
938     model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
939     model->setQuery("/Pets");
940     model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
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, QUrl::fromLocalFile(TESTDATA("roleCrash.qml")));
955     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
956     QVERIFY(model != 0);
957     delete model;
958 }
959
960 QTEST_MAIN(tst_qdeclarativexmllistmodel)
961
962 #include "tst_qdeclarativexmllistmodel.moc"