Initial import from the monolithic Qt.
[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 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
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 #ifdef QTEST_XMLPATTERNS
57 #include <QtDeclarative/qdeclarativeengine.h>
58 #include <QtDeclarative/qdeclarativecomponent.h>
59 #include <private/qdeclarativexmllistmodel_p.h>
60 #include "../../../shared/util.h"
61
62 #ifdef Q_OS_SYMBIAN
63 // In Symbian OS test data is located in applications private dir
64 #define SRCDIR "."
65 #endif
66
67 typedef QPair<int, int> QDeclarativeXmlListRange;
68 typedef QList<QVariantList> QDeclarativeXmlModelData;
69
70 Q_DECLARE_METATYPE(QList<QDeclarativeXmlListRange>)
71 Q_DECLARE_METATYPE(QDeclarativeXmlModelData)
72 Q_DECLARE_METATYPE(QDeclarativeXmlListModel::Status)
73
74 class tst_qdeclarativexmllistmodel : public QObject
75
76 {
77     Q_OBJECT
78 public:
79     tst_qdeclarativexmllistmodel() {}
80
81 private slots:
82     void initTestCase() {
83         qRegisterMetaType<QDeclarativeXmlListModel::Status>("QDeclarativeXmlListModel::Status");
84     }
85
86     void buildModel();
87     void testTypes();
88     void testTypes_data();
89     void cdata();
90     void attributes();
91     void roles();
92     void roleErrors();
93     void uniqueRoleNames();
94     void headers();
95     void xml();
96     void xml_data();
97     void source();
98     void source_data();
99     void data();
100     void get();
101     void reload();
102     void useKeys();
103     void useKeys_data();
104     void noKeysValueChanges();
105     void keysChanged();
106     void threading();
107     void threading_data();
108     void propertyChanges();
109
110     void roleCrash();
111
112 private:
113     QString makeItemXmlAndData(const QString &data, QDeclarativeXmlModelData *modelData = 0) const
114     {
115         if (modelData)
116             modelData->clear();
117         QString xml;
118
119         if (!data.isEmpty()) {
120             QStringList items = data.split(";");
121             foreach(const QString &item, items) {
122                 if (item.isEmpty())
123                     continue;
124                 QVariantList variants;
125                 xml += QLatin1String("<item>");
126                 QStringList fields = item.split(",");
127                 foreach(const QString &field, fields) {
128                     QStringList values = field.split("=");
129                     Q_ASSERT(values.count() == 2);
130                     xml += QString("<%1>%2</%1>").arg(values[0], values[1]);
131                     if (!modelData)
132                         continue;
133                     bool isNum = false;
134                     int number = values[1].toInt(&isNum);
135                     if (isNum)
136                         variants << number;
137                     else
138                         variants << values[1];
139                 }
140                 xml += QLatin1String("</item>");
141                 if (modelData)
142                     modelData->append(variants);
143             }
144         }
145
146         QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
147         return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
148     }
149
150     QDeclarativeEngine engine;
151 };
152
153 class CustomNetworkAccessManagerFactory : public QObject, public QDeclarativeNetworkAccessManagerFactory
154 {
155     Q_OBJECT
156 public:
157     QVariantMap lastSentHeaders;
158
159 protected:
160     QNetworkAccessManager *create(QObject *parent);
161 };
162
163 class CustomNetworkAccessManager : public QNetworkAccessManager
164 {
165     Q_OBJECT
166 public:
167     CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent)
168         : QNetworkAccessManager(parent), m_factory(factory) {}
169
170 protected:
171     QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0)
172     {
173         if (m_factory) {
174             QVariantMap map;
175             foreach (const QString &header, req.rawHeaderList())
176                 map[header] = req.rawHeader(header.toUtf8());
177             m_factory->lastSentHeaders = map;
178         }
179         return QNetworkAccessManager::createRequest(op, req, outgoingData);
180     }
181
182     QPointer<CustomNetworkAccessManagerFactory> m_factory;
183 };
184
185 QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent)
186 {
187     return new CustomNetworkAccessManager(this, parent);
188 }
189
190
191 void tst_qdeclarativexmllistmodel::buildModel()
192 {
193     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
194     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
195     QVERIFY(model != 0);
196     QTRY_COMPARE(model->count(), 9);
197
198     QList<int> roles;
199     roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
200     QHash<int, QVariant> data = model->data(3, roles);
201     QVERIFY(data.count() == 4);
202     QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Spot"));
203     QCOMPARE(data.value(Qt::UserRole+1).toString(), QLatin1String("Dog"));
204     QCOMPARE(data.value(Qt::UserRole+2).toInt(), 9);
205     QCOMPARE(data.value(Qt::UserRole+3).toString(), QLatin1String("Medium"));
206
207     delete model;
208 }
209
210 void tst_qdeclarativexmllistmodel::testTypes()
211 {
212     QFETCH(QString, xml);
213     QFETCH(QString, roleName);
214     QFETCH(QVariant, expectedValue);
215
216     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/testtypes.qml"));
217     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
218     QVERIFY(model != 0);
219     model->setXml(xml.toUtf8());
220     model->reload();
221     QTRY_COMPARE(model->count(), 1);
222
223     int role = -1;
224     foreach (int i, model->roles()) {
225         if (model->toString(i) == roleName) {
226             role = i;
227             break;
228         }
229     }
230     QVERIFY(role >= 0);
231
232     if (expectedValue.toString() == "nan")
233         QVERIFY(qIsNaN(model->data(0, role).toDouble()));
234     else
235         QCOMPARE(model->data(0, role), expectedValue);
236
237     delete model;
238 }
239
240 void tst_qdeclarativexmllistmodel::testTypes_data()
241 {
242     QTest::addColumn<QString>("xml");
243     QTest::addColumn<QString>("roleName");
244     QTest::addColumn<QVariant>("expectedValue");
245
246     QTest::newRow("missing string field") << "<data></data>"
247             << "stringValue" << QVariant("");
248     QTest::newRow("empty string") << "<data><a-string></a-string></data>"
249             << "stringValue" << QVariant("");
250     QTest::newRow("1-char string") << "<data><a-string>5</a-string></data>"
251             << "stringValue" << QVariant("5");
252     QTest::newRow("string ok") << "<data><a-string>abc def g</a-string></data>"
253             << "stringValue" << QVariant("abc def g");
254
255     QTest::newRow("missing number field") << "<data></data>"
256             << "numberValue" << QVariant("");
257     double nan = qQNaN();
258     QTest::newRow("empty number field") << "<data><a-number></a-number></data>"
259             << "numberValue" << QVariant(nan);
260     QTest::newRow("number field with string") << "<data><a-number>a string</a-number></data>"
261             << "numberValue" << QVariant(nan);
262     QTest::newRow("-1") << "<data><a-number>-1</a-number></data>"
263             << "numberValue" << QVariant("-1");
264     QTest::newRow("-1.5") << "<data><a-number>-1.5</a-number></data>"
265             << "numberValue" << QVariant("-1.5");
266     QTest::newRow("0") << "<data><a-number>0</a-number></data>"
267             << "numberValue" << QVariant("0");
268     QTest::newRow("+1") << "<data><a-number>1</a-number></data>"
269             << "numberValue" << QVariant("1");
270     QTest::newRow("+1.5") << "<data><a-number>1.5</a-number></data>"
271             << "numberValue" << QVariant("1.5");
272 }
273
274 void tst_qdeclarativexmllistmodel::cdata()
275 {
276     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
277     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
278     QVERIFY(model != 0);
279     QTRY_COMPARE(model->count(), 5);
280
281     QList<int> roles;
282     roles << Qt::UserRole + 2;
283     QHash<int, QVariant> data = model->data(2, roles);
284     QVERIFY(data.count() == 1);
285     QVERIFY(data.value(Qt::UserRole+2).toString().startsWith(QLatin1String("<html>")));
286
287     delete model;
288 }
289
290 void tst_qdeclarativexmllistmodel::attributes()
291 {
292     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/recipes.qml"));
293     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
294     QVERIFY(model != 0);
295     QTRY_COMPARE(model->count(), 5);
296     QList<int> roles;
297     roles << Qt::UserRole;
298     QHash<int, QVariant> data = model->data(2, roles);
299     QVERIFY(data.count() == 1);
300     QCOMPARE(data.value(Qt::UserRole).toString(), QLatin1String("Vegetable Soup"));
301
302     delete model;
303 }
304
305 void tst_qdeclarativexmllistmodel::roles()
306 {
307     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
308     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
309     QVERIFY(model != 0);
310     QTRY_COMPARE(model->count(), 9);
311
312     QList<int> roles = model->roles();
313     QCOMPARE(roles.count(), 4);
314     QCOMPARE(model->toString(roles.at(0)), QLatin1String("name"));
315     QCOMPARE(model->toString(roles.at(1)), QLatin1String("type"));
316     QCOMPARE(model->toString(roles.at(2)), QLatin1String("age"));
317     QCOMPARE(model->toString(roles.at(3)), QLatin1String("size"));
318
319     delete model;
320 }
321
322 void tst_qdeclarativexmllistmodel::roleErrors()
323 {
324     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml"));
325     QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml").toString() + ":6:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData());
326     QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/roleErrors.qml").toString() + ":9:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData());
327
328     //### make sure we receive all expected warning messages.
329     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
330     QVERIFY(model != 0);
331     QTRY_COMPARE(model->count(), 9);
332
333     QList<int> roles;
334     roles << Qt::UserRole << Qt::UserRole + 1 << Qt::UserRole + 2 << Qt::UserRole + 3;
335     QHash<int, QVariant> data = model->data(3, roles);
336     QVERIFY(data.count() == 4);
337
338     //### should any of these return valid values?
339     QCOMPARE(data.value(Qt::UserRole), QVariant());
340     QCOMPARE(data.value(Qt::UserRole+1), QVariant());
341     QCOMPARE(data.value(Qt::UserRole+2), QVariant());
342
343     QEXPECT_FAIL("", "QTBUG-10797", Continue);
344     QCOMPARE(data.value(Qt::UserRole+3), QVariant());
345
346     delete model;
347 }
348
349 void tst_qdeclarativexmllistmodel::uniqueRoleNames()
350 {
351     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/unique.qml"));
352     QTest::ignoreMessage(QtWarningMsg, (QUrl::fromLocalFile(SRCDIR "/data/unique.qml").toString() + ":7:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData());
353     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
354     QVERIFY(model != 0);
355     QTRY_COMPARE(model->count(), 9);
356
357     QList<int> roles = model->roles();
358     QCOMPARE(roles.count(), 1);
359
360     delete model;
361 }
362
363
364 void tst_qdeclarativexmllistmodel::xml()
365 {
366     QFETCH(QString, xml);
367     QFETCH(int, count);
368
369     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
370     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
371     QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
372
373     QVERIFY(model->errorString().isEmpty());
374     QCOMPARE(model->progress(), qreal(0.0));
375     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
376     QTRY_COMPARE(spy.count(), 1); spy.clear();
377     QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
378     QVERIFY(model->errorString().isEmpty());
379     QCOMPARE(model->progress(), qreal(1.0));
380     QCOMPARE(model->count(), 9);
381
382     // if xml is empty (i.e. clearing) it won't have any effect if a source is set
383     if (xml.isEmpty())
384         model->setSource(QUrl());
385     model->setXml(xml);
386     QCOMPARE(model->progress(), qreal(1.0));   // immediately goes to 1.0 if using setXml()
387     QTRY_COMPARE(spy.count(), 1); spy.clear();
388     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
389     QTRY_COMPARE(spy.count(), 1); spy.clear();
390     QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
391     QVERIFY(model->errorString().isEmpty());
392     QCOMPARE(model->count(), count);
393
394     delete model;
395 }
396
397 void tst_qdeclarativexmllistmodel::xml_data()
398 {
399     QTest::addColumn<QString>("xml");
400     QTest::addColumn<int>("count");
401
402     QTest::newRow("xml with no items") << "<Pets></Pets>" << 0;
403     QTest::newRow("empty xml") << "" << 0;
404     QTest::newRow("one item") << "<Pets><Pet><name>Hobbes</name><type>Tiger</type><age>7</age><size>Large</size></Pet></Pets>" << 1;
405 }
406
407 void tst_qdeclarativexmllistmodel::headers()
408 {
409     // ensure the QNetworkAccessManagers created for this test are immediately deleted
410     QDeclarativeEngine qmlEng;
411
412     CustomNetworkAccessManagerFactory factory;
413     qmlEng.setNetworkAccessManagerFactory(&factory);
414
415     QDeclarativeComponent component(&qmlEng, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
416     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
417     QVERIFY(model != 0);
418     QTRY_COMPARE(model->status(), QDeclarativeXmlListModel::Ready);
419
420     QVariantMap expectedHeaders;
421     expectedHeaders["Accept"] = "application/xml,*/*";
422
423     QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count());
424     foreach (const QString &header, expectedHeaders.keys()) {
425         QVERIFY(factory.lastSentHeaders.contains(header));
426         QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString());
427     }
428
429     delete model;
430 }
431
432 void tst_qdeclarativexmllistmodel::source()
433 {
434     QFETCH(QUrl, source);
435     QFETCH(int, count);
436     QFETCH(QDeclarativeXmlListModel::Status, status);
437
438     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
439     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
440     QSignalSpy spy(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)));
441
442     QVERIFY(model->errorString().isEmpty());
443     QCOMPARE(model->progress(), qreal(0.0));
444     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
445     QTRY_COMPARE(spy.count(), 1); spy.clear();
446     QCOMPARE(model->status(), QDeclarativeXmlListModel::Ready);
447     QVERIFY(model->errorString().isEmpty());
448     QCOMPARE(model->progress(), qreal(1.0));
449     QCOMPARE(model->count(), 9);
450
451     model->setSource(source);
452     QCOMPARE(model->progress(), qreal(0.0));
453     QTRY_COMPARE(spy.count(), 1); spy.clear();
454     QCOMPARE(model->status(), QDeclarativeXmlListModel::Loading);
455     QVERIFY(model->errorString().isEmpty());
456
457     QEventLoop loop;
458     QTimer timer;
459     timer.setSingleShot(true);
460     connect(model, SIGNAL(statusChanged(QDeclarativeXmlListModel::Status)), &loop, SLOT(quit()));
461     connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
462     timer.start(20000);
463     loop.exec();
464
465     if (spy.count() == 0 && status != QDeclarativeXmlListModel::Ready) {
466         qWarning("QDeclarativeXmlListModel invalid source test timed out");
467     } else {
468         QCOMPARE(spy.count(), 1); spy.clear();
469     }
470
471     QCOMPARE(model->status(), status);
472     QCOMPARE(model->count(), count);
473
474     if (status == QDeclarativeXmlListModel::Ready)
475         QCOMPARE(model->progress(), qreal(1.0));
476
477     QCOMPARE(model->errorString().isEmpty(), status == QDeclarativeXmlListModel::Ready);
478
479     delete model;
480 }
481
482 void tst_qdeclarativexmllistmodel::source_data()
483 {
484     QTest::addColumn<QUrl>("source");
485     QTest::addColumn<int>("count");
486     QTest::addColumn<QDeclarativeXmlListModel::Status>("status");
487
488     QTest::newRow("valid") << QUrl::fromLocalFile(SRCDIR "/data/model2.xml") << 2 << QDeclarativeXmlListModel::Ready;
489     QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 << QDeclarativeXmlListModel::Error;
490
491     // empty file
492     QTemporaryFile *temp = new QTemporaryFile(this);
493     if (temp->open())
494         QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QDeclarativeXmlListModel::Ready;
495     temp->close();
496 }
497
498 void tst_qdeclarativexmllistmodel::data()
499 {
500     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
501     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());    
502     QVERIFY(model != 0);
503
504     QHash<int,QVariant> blank;
505     for (int i=0; i<model->roles().count(); i++)
506         blank.insert(model->roles()[i], QVariant());
507     for (int i=0; i<9; i++)  {
508         QCOMPARE(model->data(i, model->roles()), blank);
509         for (int j=0; j<model->roles().count(); j++) {
510             QCOMPARE(model->data(i, j), QVariant());
511         }
512     }
513     QTRY_COMPARE(model->count(), 9);
514
515     delete model;
516 }
517
518 void tst_qdeclarativexmllistmodel::get()
519 {
520     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
521     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());    
522     QVERIFY(model != 0);
523     QVERIFY(model->get(0).isUndefined());
524
525     QTRY_COMPARE(model->count(), 9);
526     QVERIFY(model->get(-1).isUndefined());
527
528     QScriptValue sv = model->get(0);
529     QCOMPARE(sv.property("name").toString(), QLatin1String("Polly"));
530     QCOMPARE(sv.property("type").toString(), QLatin1String("Parrot"));
531     QCOMPARE(sv.property("age").toNumber(), qsreal(12));
532     QCOMPARE(sv.property("size").toString(), QLatin1String("Small"));
533
534     sv = model->get(1);
535     QCOMPARE(sv.property("name").toString(), QLatin1String("Penny"));
536     QCOMPARE(sv.property("type").toString(), QLatin1String("Turtle"));
537     QCOMPARE(sv.property("age").toNumber(), qsreal(4));
538     QCOMPARE(sv.property("size").toString(), QLatin1String("Small"));
539
540     sv = model->get(7);
541     QCOMPARE(sv.property("name").toString(), QLatin1String("Rover"));
542     QCOMPARE(sv.property("type").toString(), QLatin1String("Dog"));
543     QCOMPARE(sv.property("age").toNumber(), qsreal(0));
544     QCOMPARE(sv.property("size").toString(), QLatin1String("Large"));
545
546     sv = model->get(8);
547     QCOMPARE(sv.property("name").toString(), QLatin1String("Tiny"));
548     QCOMPARE(sv.property("type").toString(), QLatin1String("Elephant"));
549     QCOMPARE(sv.property("age").toNumber(), qsreal(15));
550     QCOMPARE(sv.property("size").toString(), QLatin1String("Large"));
551
552     sv = model->get(9);
553     QVERIFY(sv.isUndefined());
554
555     delete model;
556 }
557
558 void tst_qdeclarativexmllistmodel::reload()
559 {
560     // If no keys are used, the model should be rebuilt from scratch when
561     // reload() is called.
562
563     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/model.qml"));
564     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
565     QVERIFY(model != 0);
566     QTRY_COMPARE(model->count(), 9);
567
568     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
569     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
570     QSignalSpy spyCount(model, SIGNAL(countChanged())); 
571
572     //reload multiple times to test the xml query aborting
573     model->reload();
574     model->reload();
575     QCoreApplication::processEvents();
576     model->reload();
577     model->reload();
578     QTRY_COMPARE(spyCount.count(), 1);
579     QTRY_COMPARE(spyInsert.count(), 1);
580     QTRY_COMPARE(spyRemove.count(), 1);
581
582     QCOMPARE(spyInsert[0][0].toInt(), 0);
583     QCOMPARE(spyInsert[0][1].toInt(), 9);
584
585     QCOMPARE(spyRemove[0][0].toInt(), 0);
586     QCOMPARE(spyRemove[0][1].toInt(), 9);
587
588     delete model;
589 }
590
591 void tst_qdeclarativexmllistmodel::useKeys()
592 {
593     // If using incremental updates through keys, the model should only
594     // insert & remove some of the items, instead of throwing everything
595     // away and causing the view to repaint the whole view.
596
597     QFETCH(QString, oldXml);
598     QFETCH(int, oldCount);
599     QFETCH(QString, newXml);
600     QFETCH(QDeclarativeXmlModelData, newData);
601     QFETCH(QList<QDeclarativeXmlListRange>, insertRanges);
602     QFETCH(QList<QDeclarativeXmlListRange>, removeRanges);
603
604     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
605     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
606     QVERIFY(model != 0);
607
608     model->setXml(oldXml);
609     QTRY_COMPARE(model->count(), oldCount);
610
611     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
612     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
613     QSignalSpy spyCount(model, SIGNAL(countChanged()));
614
615     model->setXml(newXml);
616
617     if (oldCount != newData.count()) {
618         QTRY_COMPARE(model->count(), newData.count());
619         QCOMPARE(spyCount.count(), 1);
620     } else {
621         QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
622         QCOMPARE(spyCount.count(), 0);
623     }
624
625     QList<int> roles = model->roles();
626     for (int i=0; i<model->count(); i++) {
627         for (int j=0; j<roles.count(); j++)
628             QCOMPARE(model->data(i, roles[j]), newData[i][j]);
629     }
630
631     QCOMPARE(spyInsert.count(), insertRanges.count());
632     for (int i=0; i<spyInsert.count(); i++) {
633         QCOMPARE(spyInsert[i][0].toInt(), insertRanges[i].first);
634         QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].second);
635     }
636
637     QCOMPARE(spyRemove.count(), removeRanges.count());
638     for (int i=0; i<spyRemove.count(); i++) {
639         QCOMPARE(spyRemove[i][0].toInt(), removeRanges[i].first);
640         QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].second);
641     }
642
643     delete model;
644 }
645
646 void tst_qdeclarativexmllistmodel::useKeys_data()
647 {
648     QTest::addColumn<QString>("oldXml");
649     QTest::addColumn<int>("oldCount");
650     QTest::addColumn<QString>("newXml");
651     QTest::addColumn<QDeclarativeXmlModelData>("newData");
652     QTest::addColumn<QList<QDeclarativeXmlListRange> >("insertRanges");
653     QTest::addColumn<QList<QDeclarativeXmlListRange> >("removeRanges");
654
655     QDeclarativeXmlModelData modelData;
656
657     QTest::newRow("append 1")
658         << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
659         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData)
660         << modelData
661         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
662         << QList<QDeclarativeXmlListRange>();
663
664     QTest::newRow("append multiple")
665         << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
666         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
667         << modelData
668         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
669         << QList<QDeclarativeXmlListRange>();
670
671     QTest::newRow("insert in different spots")
672         << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1
673         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
674         << modelData
675         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
676         << QList<QDeclarativeXmlListRange>();
677
678     QTest::newRow("insert in middle")
679         << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2
680         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
681         << modelData
682         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
683         << QList<QDeclarativeXmlListRange>();
684
685     QTest::newRow("remove first")
686         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
687         << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData)
688         << modelData
689         << QList<QDeclarativeXmlListRange>()
690         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
691
692     QTest::newRow("remove last")
693         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2
694         << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData)
695         << modelData
696         << QList<QDeclarativeXmlListRange>()
697         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1));
698
699     QTest::newRow("remove from multiple spots")
700         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 5
701         << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData)
702         << modelData
703         << QList<QDeclarativeXmlListRange>()
704         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1) << qMakePair(3,2));
705
706     QTest::newRow("remove all")
707         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
708         << makeItemXmlAndData("", &modelData)
709         << modelData
710         << QList<QDeclarativeXmlListRange>()
711         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 3));
712
713     QTest::newRow("replace item")
714         << makeItemXmlAndData("name=A,age=25,sport=Football") << 1
715         << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData)
716         << modelData
717         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1))
718         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1));
719
720     QTest::newRow("add and remove simultaneously, in different spots")
721         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling;name=D,age=55,sport=Golf") << 4
722         << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData)
723         << modelData
724         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 1))
725         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
726
727     QTest::newRow("insert at start, remove at end i.e. rss feed")
728         << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3
729         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData)
730         << modelData
731         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
732         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2));
733
734     QTest::newRow("remove at start, insert at end")
735         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3
736         << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData)
737         << modelData
738         << (QList<QDeclarativeXmlListRange>() << qMakePair(1, 2))
739         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
740
741     QTest::newRow("all data has changed")
742         << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2
743         << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData)
744         << modelData
745         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2))
746         << (QList<QDeclarativeXmlListRange>() << qMakePair(0, 2));
747 }
748
749 void tst_qdeclarativexmllistmodel::noKeysValueChanges()
750 {
751     // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml.
752     // If a 'sport' value is changed, the model should not be reloaded,
753     // since 'sport' is not marked as a key.
754
755     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
756     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
757     QVERIFY(model != 0);
758     
759     QString xml;
760     
761     xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
762     model->setXml(xml);
763     QTRY_COMPARE(model->count(), 2);
764
765     model->setXml("");
766
767     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
768     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
769     QSignalSpy spyCount(model, SIGNAL(countChanged()));
770
771     xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
772     model->setXml(xml);
773
774     // wait for the new xml data to be set, and verify no signals were emitted
775     QTRY_VERIFY(model->data(0, model->roles()[2]).toString() != QLatin1String("Football"));
776     QCOMPARE(model->data(0, model->roles()[2]).toString(), QLatin1String("AussieRules"));
777
778     QVERIFY(spyInsert.count() == 0);
779     QVERIFY(spyRemove.count() == 0);
780     QVERIFY(spyCount.count() == 0);
781
782     QCOMPARE(model->count(), 2);
783
784     delete model;
785 }
786
787 void tst_qdeclarativexmllistmodel::keysChanged()
788 {
789     // If the key roles change, the next time the data is reloaded, it should
790     // delete all its data and build a clean model (i.e. same behaviour as
791     // if no keys are set).
792
793     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
794     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
795     QVERIFY(model != 0);
796
797     QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
798     model->setXml(xml);
799     QTRY_COMPARE(model->count(), 2);
800
801     model->setXml("");
802
803     QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
804     QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
805     QSignalSpy spyCount(model, SIGNAL(countChanged()));
806
807     QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
808     model->setXml(xml);
809
810     QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
811
812     QCOMPARE(spyInsert.count(), 1);
813     QCOMPARE(spyInsert[0][0].toInt(), 0);
814     QCOMPARE(spyInsert[0][1].toInt(), 2);
815
816     QCOMPARE(spyRemove.count(), 1);
817     QCOMPARE(spyRemove[0][0].toInt(), 0);
818     QCOMPARE(spyRemove[0][1].toInt(), 2);
819
820     QCOMPARE(spyCount.count(), 0);
821
822     delete model;
823 }
824
825 void tst_qdeclarativexmllistmodel::threading()
826 {
827     QFETCH(int, xmlDataCount);
828
829     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
830
831     QDeclarativeXmlListModel *m1 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
832     QVERIFY(m1 != 0); 
833     QDeclarativeXmlListModel *m2 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
834     QVERIFY(m2 != 0); 
835     QDeclarativeXmlListModel *m3 = qobject_cast<QDeclarativeXmlListModel*>(component.create());
836     QVERIFY(m3 != 0); 
837
838     for (int dataCount=0; dataCount<xmlDataCount; dataCount++) {
839
840         QString data1, data2, data3;
841         for (int i=0; i<dataCount; i++) {
842             data1 += "name=A" + QString::number(i) + ",age=1" + QString::number(i) + ",sport=Football;";
843             data2 += "name=B" + QString::number(i) + ",age=2" + QString::number(i) + ",sport=Athletics;";
844             data3 += "name=C" + QString::number(i) + ",age=3" + QString::number(i) + ",sport=Curling;";
845         }
846
847         //Set the xml data multiple times with randomized order and mixed with multiple event loops
848         //to test the xml query reloading/aborting, the result should be stable.
849         m1->setXml(makeItemXmlAndData(data1));
850         m2->setXml(makeItemXmlAndData(data2));
851         m3->setXml(makeItemXmlAndData(data3));
852         QCoreApplication::processEvents();
853         m2->setXml(makeItemXmlAndData(data2));
854         m1->setXml(makeItemXmlAndData(data1));
855         m2->setXml(makeItemXmlAndData(data2));
856         QCoreApplication::processEvents();
857         m3->setXml(makeItemXmlAndData(data3));
858         QCoreApplication::processEvents();
859         m2->setXml(makeItemXmlAndData(data2));
860         m1->setXml(makeItemXmlAndData(data1));
861         m2->setXml(makeItemXmlAndData(data2));
862         m3->setXml(makeItemXmlAndData(data3));
863         QCoreApplication::processEvents();
864         m2->setXml(makeItemXmlAndData(data2));
865         m3->setXml(makeItemXmlAndData(data3));
866         m3->setXml(makeItemXmlAndData(data3));
867         QCoreApplication::processEvents();
868
869         QTRY_VERIFY(m1->count() == dataCount && m2->count() == dataCount && m3->count() == dataCount);
870
871         for (int i=0; i<dataCount; i++) {
872             QCOMPARE(m1->data(i, m1->roles()[0]).toString(), QString("A" + QString::number(i)));
873             QCOMPARE(m1->data(i, m1->roles()[1]).toString(), QString("1" + QString::number(i)));
874             QCOMPARE(m1->data(i, m1->roles()[2]).toString(), QString("Football"));
875
876             QCOMPARE(m2->data(i, m2->roles()[0]).toString(), QString("B" + QString::number(i)));
877             QCOMPARE(m2->data(i, m2->roles()[1]).toString(), QString("2" + QString::number(i)));
878             QCOMPARE(m2->data(i, m2->roles()[2]).toString(), QString("Athletics"));
879
880             QCOMPARE(m3->data(i, m3->roles()[0]).toString(), QString("C" + QString::number(i)));
881             QCOMPARE(m3->data(i, m3->roles()[1]).toString(), QString("3" + QString::number(i)));
882             QCOMPARE(m3->data(i, m3->roles()[2]).toString(), QString("Curling"));
883         }
884     }
885
886     delete m1;
887     delete m2;
888     delete m3;
889 }
890
891 void tst_qdeclarativexmllistmodel::threading_data()
892 {
893     QTest::addColumn<int>("xmlDataCount");
894
895     QTest::newRow("1") << 1;
896     QTest::newRow("2") << 2;
897     QTest::newRow("10") << 10;
898 }
899
900 void tst_qdeclarativexmllistmodel::propertyChanges()
901 {
902     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/propertychanges.qml"));
903     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
904     QVERIFY(model != 0);
905     QTRY_COMPARE(model->count(), 9);
906
907     QDeclarativeXmlListModelRole *role = model->findChild<QDeclarativeXmlListModelRole*>("role");
908     QVERIFY(role);
909
910     QSignalSpy nameSpy(role, SIGNAL(nameChanged()));
911     QSignalSpy querySpy(role, SIGNAL(queryChanged()));
912     QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged()));
913
914     role->setName("size");
915     role->setQuery("size/string()");
916     role->setIsKey(true);
917
918     QCOMPARE(role->name(), QString("size"));
919     QCOMPARE(role->query(), QString("size/string()"));
920     QVERIFY(role->isKey());
921
922     QCOMPARE(nameSpy.count(),1);
923     QCOMPARE(querySpy.count(),1);
924     QCOMPARE(isKeySpy.count(),1);
925
926     role->setName("size");
927     role->setQuery("size/string()");
928     role->setIsKey(true);
929
930     QCOMPARE(nameSpy.count(),1);
931     QCOMPARE(querySpy.count(),1);
932     QCOMPARE(isKeySpy.count(),1);
933
934     QSignalSpy sourceSpy(model, SIGNAL(sourceChanged()));
935     QSignalSpy xmlSpy(model, SIGNAL(xmlChanged()));
936     QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged()));
937     QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged()));
938
939     model->setSource(QUrl(""));
940     model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
941     model->setQuery("/Pets");
942     model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
943
944     QCOMPARE(model->source(), QUrl(""));
945     QCOMPARE(model->xml(), QString("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>"));
946     QCOMPARE(model->query(), QString("/Pets"));
947     QCOMPARE(model->namespaceDeclarations(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";"));
948
949     QTRY_VERIFY(model->count() == 1);
950
951     QCOMPARE(sourceSpy.count(),1);
952     QCOMPARE(xmlSpy.count(),1);
953     QCOMPARE(modelQuerySpy.count(),1);
954     QCOMPARE(namespaceDeclarationsSpy.count(),1);
955
956     model->setSource(QUrl(""));
957     model->setXml("<Pets><Pet><name>Polly</name><type>Parrot</type><age>12</age><size>Small</size></Pet></Pets>");
958     model->setQuery("/Pets");
959     model->setNamespaceDeclarations("declare namespace media=\"http://search.yahoo.com/mrss/\";");
960
961     QCOMPARE(sourceSpy.count(),1);
962     QCOMPARE(xmlSpy.count(),1);
963     QCOMPARE(modelQuerySpy.count(),1);
964     QCOMPARE(namespaceDeclarationsSpy.count(),1);
965
966     QTRY_VERIFY(model->count() == 1);
967     delete model;
968 }
969
970 void tst_qdeclarativexmllistmodel::roleCrash()
971 {
972     // don't crash
973     QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleCrash.qml"));
974     QDeclarativeXmlListModel *model = qobject_cast<QDeclarativeXmlListModel*>(component.create());
975     QVERIFY(model != 0);
976     delete model;
977 }
978
979 QTEST_MAIN(tst_qdeclarativexmllistmodel)
980
981 #include "tst_qdeclarativexmllistmodel.moc"
982
983 #else
984 QTEST_NOOP_MAIN
985 #endif