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