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