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