/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../../shared/util.h" #include #include #include #include "../../../../src/imports/xmllistmodel/qqmlxmllistmodel_p.h" #include typedef QPair QQuickXmlListRange; typedef QList QQmlXmlModelData; Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(QQmlXmlModelData) Q_DECLARE_METATYPE(QQuickXmlListModel::Status) class tst_qquickxmllistmodel : public QQmlDataTest { Q_OBJECT public: tst_qquickxmllistmodel() {} private slots: void initTestCase() { QQmlDataTest::initTestCase(); qRegisterMetaType(); } void buildModel(); void testTypes(); void testTypes_data(); void cdata(); void attributes(); void roles(); void roleErrors(); void uniqueRoleNames(); void headers(); void xml(); void xml_data(); void source(); void source_data(); void data(); void get(); void reload(); void useKeys(); void useKeys_data(); void noKeysValueChanges(); void keysChanged(); void threading(); void threading_data(); void propertyChanges(); void selectAncestor(); void roleCrash(); void proxyCrash(); private: QString errorString(QAbstractItemModel *model) { QString ret; QMetaObject::invokeMethod(model, "errorString", Q_RETURN_ARG(QString, ret)); return ret; } QString makeItemXmlAndData(const QString &data, QQmlXmlModelData *modelData = 0) const { if (modelData) modelData->clear(); QString xml; if (!data.isEmpty()) { QStringList items = data.split(QLatin1Char(';')); foreach(const QString &item, items) { if (item.isEmpty()) continue; QVariantList variants; xml += QLatin1String(""); QStringList fields = item.split(QLatin1Char(',')); foreach(const QString &field, fields) { QStringList values = field.split(QLatin1Char('=')); if (values.count() != 2) { qWarning() << "makeItemXmlAndData: invalid field:" << field; continue; } xml += QString("<%1>%2").arg(values[0], values[1]); if (!modelData) continue; bool isNum = false; int number = values[1].toInt(&isNum); if (isNum) variants << number; else variants << values[1]; } xml += QLatin1String(""); if (modelData) modelData->append(variants); } } QString decl = ""; return decl + QLatin1String("") + xml + QLatin1String(""); } QQmlEngine engine; }; class CustomNetworkAccessManagerFactory : public QObject, public QQmlNetworkAccessManagerFactory { Q_OBJECT public: QVariantMap lastSentHeaders; protected: QNetworkAccessManager *create(QObject *parent); }; class CustomNetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: CustomNetworkAccessManager(CustomNetworkAccessManagerFactory *factory, QObject *parent) : QNetworkAccessManager(parent), m_factory(factory) {} protected: QNetworkReply *createRequest(Operation op, const QNetworkRequest &req, QIODevice * outgoingData = 0) { if (m_factory) { QVariantMap map; foreach (const QString &header, req.rawHeaderList()) map[header] = req.rawHeader(header.toUtf8()); m_factory->lastSentHeaders = map; } return QNetworkAccessManager::createRequest(op, req, outgoingData); } QPointer m_factory; }; QNetworkAccessManager *CustomNetworkAccessManagerFactory::create(QObject *parent) { return new CustomNetworkAccessManager(this, parent); } void tst_qquickxmllistmodel::buildModel() { QQmlComponent component(&engine, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 9); QModelIndex index = model->index(3, 0); QCOMPARE(model->data(index, Qt::UserRole).toString(), QLatin1String("Spot")); QCOMPARE(model->data(index, Qt::UserRole+1).toString(), QLatin1String("Dog")); QCOMPARE(model->data(index, Qt::UserRole+2).toInt(), 9); QCOMPARE(model->data(index, Qt::UserRole+3).toString(), QLatin1String("Medium")); delete model; } void tst_qquickxmllistmodel::testTypes() { QFETCH(QString, xml); QFETCH(QString, roleName); QFETCH(QVariant, expectedValue); QQmlComponent component(&engine, testFileUrl("testtypes.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); model->setProperty("xml",xml.toUtf8()); QMetaObject::invokeMethod(model, "reload"); QTRY_COMPARE(model->rowCount(), 1); int role = model->roleNames().key(roleName.toUtf8(), -1); QVERIFY(role >= 0); QModelIndex index = model->index(0, 0); if (expectedValue.toString() == "nan") QVERIFY(qIsNaN(model->data(index, role).toDouble())); else QCOMPARE(model->data(index, role), expectedValue); delete model; } void tst_qquickxmllistmodel::testTypes_data() { QTest::addColumn("xml"); QTest::addColumn("roleName"); QTest::addColumn("expectedValue"); QTest::newRow("missing string field") << "" << "stringValue" << QVariant(""); QTest::newRow("empty string") << "" << "stringValue" << QVariant(""); QTest::newRow("1-char string") << "5" << "stringValue" << QVariant("5"); QTest::newRow("string ok") << "abc def g" << "stringValue" << QVariant("abc def g"); QTest::newRow("missing number field") << "" << "numberValue" << QVariant(""); double nan = qQNaN(); QTest::newRow("empty number field") << "" << "numberValue" << QVariant(nan); QTest::newRow("number field with string") << "a string" << "numberValue" << QVariant(nan); QTest::newRow("-1") << "-1" << "numberValue" << QVariant("-1"); QTest::newRow("-1.5") << "-1.5" << "numberValue" << QVariant("-1.5"); QTest::newRow("0") << "0" << "numberValue" << QVariant("0"); QTest::newRow("+1") << "1" << "numberValue" << QVariant("1"); QTest::newRow("+1.5") << "1.5" << "numberValue" << QVariant("1.5"); } void tst_qquickxmllistmodel::cdata() { QQmlComponent component(&engine, testFileUrl("recipes.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 5); QVERIFY(model->data(model->index(2, 0), Qt::UserRole+2).toString().startsWith(QLatin1String(""))); delete model; } void tst_qquickxmllistmodel::attributes() { QQmlComponent component(&engine, testFileUrl("recipes.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 5); QCOMPARE(model->data(model->index(2, 0), Qt::UserRole).toString(), QLatin1String("Vegetable Soup")); delete model; } void tst_qquickxmllistmodel::roles() { QQmlComponent component(&engine, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 9); QHash roleNames = model->roleNames(); QCOMPARE(roleNames.count(), 4); QVERIFY(roleNames.key("name", -1) >= 0); QVERIFY(roleNames.key("type", -1) >= 0); QVERIFY(roleNames.key("age", -1) >= 0); QVERIFY(roleNames.key("size", -1) >= 0); QSet roles; roles.insert(roleNames.key("name")); roles.insert(roleNames.key("type")); roles.insert(roleNames.key("age")); roles.insert(roleNames.key("size")); QCOMPARE(roles.count(), 4); delete model; } void tst_qquickxmllistmodel::roleErrors() { QQmlComponent component(&engine, testFileUrl("roleErrors.qml")); QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":7:5: QML XmlRole: An XmlRole query must not start with '/'").toUtf8().constData()); QTest::ignoreMessage(QtWarningMsg, (testFileUrl("roleErrors.qml").toString() + ":10:5: QML XmlRole: invalid query: \"age/\"").toUtf8().constData()); //### make sure we receive all expected warning messages. QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 9); QModelIndex index = model->index(3, 0); //### should any of these return valid values? QCOMPARE(model->data(index, Qt::UserRole), QVariant()); QCOMPARE(model->data(index, Qt::UserRole+1), QVariant()); QCOMPARE(model->data(index, Qt::UserRole+2), QVariant()); QEXPECT_FAIL("", "QTBUG-10797", Continue); QCOMPARE(model->data(index, Qt::UserRole+3), QVariant()); delete model; } void tst_qquickxmllistmodel::uniqueRoleNames() { QQmlComponent component(&engine, testFileUrl("unique.qml")); QTest::ignoreMessage(QtWarningMsg, (testFileUrl("unique.qml").toString() + ":8:5: QML XmlRole: \"name\" duplicates a previous role name and will be disabled.").toUtf8().constData()); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 9); QHash roleNames = model->roleNames(); QCOMPARE(roleNames.count(), 1); delete model; } void tst_qquickxmllistmodel::xml() { QFETCH(QString, xml); QFETCH(int, count); QQmlComponent component(&engine, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QSignalSpy spy(model, SIGNAL(statusChanged(QQuickXmlListModel::Status))); QVERIFY(errorString(model).isEmpty()); QCOMPARE(model->property("progress").toDouble(), qreal(0.0)); QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Loading); QTRY_COMPARE(spy.count(), 1); spy.clear(); QTest::qWait(50); QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Ready); QVERIFY(errorString(model).isEmpty()); QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); QCOMPARE(model->rowCount(), 9); // if xml is empty (i.e. clearing) it won't have any effect if a source is set if (xml.isEmpty()) model->setProperty("source",QUrl()); model->setProperty("xml",xml); QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); // immediately goes to 1.0 if using setXml() QTRY_COMPARE(spy.count(), 1); spy.clear(); QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Loading); QTRY_COMPARE(spy.count(), 1); spy.clear(); if (xml.isEmpty()) QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Null); else QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Ready); QVERIFY(errorString(model).isEmpty()); QCOMPARE(model->rowCount(), count); delete model; } void tst_qquickxmllistmodel::xml_data() { QTest::addColumn("xml"); QTest::addColumn("count"); QTest::newRow("xml with no items") << "" << 0; QTest::newRow("empty xml") << "" << 0; QTest::newRow("one item") << "HobbesTiger7Large" << 1; } void tst_qquickxmllistmodel::headers() { // ensure the QNetworkAccessManagers created for this test are immediately deleted QQmlEngine qmlEng; CustomNetworkAccessManagerFactory factory; qmlEng.setNetworkAccessManagerFactory(&factory); QQmlComponent component(&qmlEng, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Ready); QVariantMap expectedHeaders; expectedHeaders["Accept"] = "application/xml,*/*"; QCOMPARE(factory.lastSentHeaders.count(), expectedHeaders.count()); foreach (const QString &header, expectedHeaders.keys()) { QVERIFY(factory.lastSentHeaders.contains(header)); QCOMPARE(factory.lastSentHeaders[header].toString(), expectedHeaders[header].toString()); } delete model; } void tst_qquickxmllistmodel::source() { QFETCH(QUrl, source); QFETCH(int, count); QFETCH(QQuickXmlListModel::Status, status); QQmlComponent component(&engine, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QSignalSpy spy(model, SIGNAL(statusChanged(QQuickXmlListModel::Status))); QVERIFY(errorString(model).isEmpty()); QCOMPARE(model->property("progress").toDouble(), qreal(0.0)); QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Loading); QTRY_COMPARE(spy.count(), 1); spy.clear(); QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Ready); QVERIFY(errorString(model).isEmpty()); QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); QCOMPARE(model->rowCount(), 9); model->setProperty("source",source); if (model->property("source").toString().isEmpty()) QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Null); QCOMPARE(model->property("progress").toDouble(), qreal(0.0)); QTRY_COMPARE(spy.count(), 1); spy.clear(); QCOMPARE(qvariant_cast(model->property("status")), QQuickXmlListModel::Loading); QVERIFY(errorString(model).isEmpty()); QEventLoop loop; QTimer timer; timer.setSingleShot(true); connect(model, SIGNAL(statusChanged(QQuickXmlListModel::Status)), &loop, SLOT(quit())); connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); timer.start(20000); loop.exec(); if (spy.count() == 0 && status != QQuickXmlListModel::Ready) { qWarning("QQuickXmlListModel invalid source test timed out"); } else { QCOMPARE(spy.count(), 1); spy.clear(); } QCOMPARE(qvariant_cast(model->property("status")), status); QCOMPARE(model->rowCount(), count); if (status == QQuickXmlListModel::Ready) QCOMPARE(model->property("progress").toDouble(), qreal(1.0)); QCOMPARE(errorString(model).isEmpty(), status == QQuickXmlListModel::Ready); delete model; } void tst_qquickxmllistmodel::source_data() { QTest::addColumn("source"); QTest::addColumn("count"); QTest::addColumn("status"); QTest::newRow("valid") << testFileUrl("model2.xml") << 2 << QQuickXmlListModel::Ready; QTest::newRow("invalid") << QUrl("http://blah.blah/blah.xml") << 0 << QQuickXmlListModel::Error; // empty file QTemporaryFile *temp = new QTemporaryFile(this); if (temp->open()) QTest::newRow("empty file") << QUrl::fromLocalFile(temp->fileName()) << 0 << QQuickXmlListModel::Ready; temp->close(); } void tst_qquickxmllistmodel::data() { QQmlComponent component(&engine, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); for (int i=0; i<9; i++) { QModelIndex index = model->index(i, 0); for (int j=0; jroleNames().count(); j++) { QCOMPARE(model->data(index, j), QVariant()); } } QTRY_COMPARE(model->rowCount(), 9); delete model; } void tst_qquickxmllistmodel::get() { QQmlComponent component(&engine, testFileUrl("get.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QVERIFY(QMetaObject::invokeMethod(model, "runPreTest")); QCOMPARE(model->property("preTest").toBool(), true); QTRY_COMPARE(model->rowCount(), 9); QVERIFY(QMetaObject::invokeMethod(model, "runPostTest")); QCOMPARE(model->property("postTest").toBool(), true); delete model; } void tst_qquickxmllistmodel::reload() { // If no keys are used, the model should be rebuilt from scratch when // reload() is called. QQmlComponent component(&engine, testFileUrl("model.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 9); QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spyCount(model, SIGNAL(countChanged())); //reload multiple times to test the xml query aborting QMetaObject::invokeMethod(model, "reload"); QMetaObject::invokeMethod(model, "reload"); QCoreApplication::processEvents(); QMetaObject::invokeMethod(model, "reload"); QMetaObject::invokeMethod(model, "reload"); QTRY_COMPARE(spyCount.count(), 0); QTRY_COMPARE(spyInsert.count(), 1); QTRY_COMPARE(spyRemove.count(), 1); QCOMPARE(spyInsert[0][1].toInt(), 0); QCOMPARE(spyInsert[0][2].toInt(), 8); QCOMPARE(spyRemove[0][1].toInt(), 0); QCOMPARE(spyRemove[0][2].toInt(), 8); delete model; } void tst_qquickxmllistmodel::useKeys() { // If using incremental updates through keys, the model should only // insert & remove some of the items, instead of throwing everything // away and causing the view to repaint the whole view. QFETCH(QString, oldXml); QFETCH(int, oldCount); QFETCH(QString, newXml); QFETCH(QQmlXmlModelData, newData); QFETCH(QList, insertRanges); QFETCH(QList, removeRanges); QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); model->setProperty("xml",oldXml); QTRY_COMPARE(model->rowCount(), oldCount); QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spyCount(model, SIGNAL(countChanged())); model->setProperty("xml",newXml); if (oldCount != newData.count()) { QTRY_COMPARE(model->rowCount(), newData.count()); QCOMPARE(spyCount.count(), 1); } else { QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0); QCOMPARE(spyCount.count(), 0); } QList roles = model->roleNames().keys(); std::sort(roles.begin(), roles.end()); for (int i=0; irowCount(); i++) { QModelIndex index = model->index(i, 0); for (int j=0; jdata(index, roles.at(j)), newData[i][j]); } QCOMPARE(spyInsert.count(), insertRanges.count()); for (int i=0; i("oldXml"); QTest::addColumn("oldCount"); QTest::addColumn("newXml"); QTest::addColumn("newData"); QTest::addColumn >("insertRanges"); QTest::addColumn >("removeRanges"); QQmlXmlModelData modelData; QTest::newRow("append 1") << makeItemXmlAndData("name=A,age=25,sport=Football") << 1 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics", &modelData) << modelData << (QList() << qMakePair(1, 1)) << QList(); QTest::newRow("append multiple") << makeItemXmlAndData("name=A,age=25,sport=Football") << 1 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData) << modelData << (QList() << qMakePair(1, 2)) << QList(); QTest::newRow("insert in different spots") << makeItemXmlAndData("name=B,age=35,sport=Athletics") << 1 << 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) << modelData << (QList() << qMakePair(0, 1) << qMakePair(2,2)) << QList(); QTest::newRow("insert in middle") << makeItemXmlAndData("name=A,age=25,sport=Football;name=D,age=55,sport=Golf") << 2 << 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) << modelData << (QList() << qMakePair(1, 2)) << QList(); QTest::newRow("remove first") << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2 << makeItemXmlAndData("name=B,age=35,sport=Athletics", &modelData) << modelData << QList() << (QList() << qMakePair(0, 1)); QTest::newRow("remove last") << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics") << 2 << makeItemXmlAndData("name=A,age=25,sport=Football", &modelData) << modelData << QList() << (QList() << qMakePair(1, 1)); QTest::newRow("remove from multiple spots") << 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 << makeItemXmlAndData("name=A,age=25,sport=Football;name=C,age=45,sport=Curling", &modelData) << modelData << QList() << (QList() << qMakePair(1, 1) << qMakePair(3,2)); QTest::newRow("remove all") << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3 << makeItemXmlAndData("", &modelData) << modelData << QList() << (QList() << qMakePair(0, 3)); QTest::newRow("replace item") << makeItemXmlAndData("name=A,age=25,sport=Football") << 1 << makeItemXmlAndData("name=ZZZ,age=25,sport=Football", &modelData) << modelData << (QList() << qMakePair(0, 1)) << (QList() << qMakePair(0, 1)); QTest::newRow("add and remove simultaneously, in different spots") << 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 << makeItemXmlAndData("name=B,age=35,sport=Athletics;name=E,age=65,sport=Fencing", &modelData) << modelData << (QList() << qMakePair(1, 1)) << (QList() << qMakePair(0, 1) << qMakePair(2,2)); QTest::newRow("insert at start, remove at end i.e. rss feed") << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing") << 3 << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling", &modelData) << modelData << (QList() << qMakePair(0, 2)) << (QList() << qMakePair(1, 2)); QTest::newRow("remove at start, insert at end") << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics;name=C,age=45,sport=Curling") << 3 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf;name=E,age=65,sport=Fencing", &modelData) << modelData << (QList() << qMakePair(1, 2)) << (QList() << qMakePair(0, 2)); QTest::newRow("all data has changed") << makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35") << 2 << makeItemXmlAndData("name=C,age=45,sport=Curling;name=D,age=55,sport=Golf", &modelData) << modelData << (QList() << qMakePair(0, 2)) << (QList() << qMakePair(0, 2)); } void tst_qquickxmllistmodel::noKeysValueChanges() { // The 'key' roles are 'name' and 'age', as defined in roleKeys.qml. // If a 'sport' value is changed, the model should not be reloaded, // since 'sport' is not marked as a key. QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QString xml; xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics"); model->setProperty("xml",xml); QTRY_COMPARE(model->rowCount(), 2); model->setProperty("xml",""); QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spyCount(model, SIGNAL(countChanged())); xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics"); model->setProperty("xml",xml); QList roles = model->roleNames().keys(); std::sort(roles.begin(), roles.end()); // wait for the new xml data to be set, and verify no signals were emitted QTRY_VERIFY(model->data(model->index(0, 0), roles.at(2)).toString() != QLatin1String("Football")); QCOMPARE(model->data(model->index(0, 0), roles.at(2)).toString(), QLatin1String("AussieRules")); QCOMPARE(spyInsert.count(), 0); QCOMPARE(spyRemove.count(), 0); QCOMPARE(spyCount.count(), 0); QCOMPARE(model->rowCount(), 2); delete model; } void tst_qquickxmllistmodel::keysChanged() { // If the key roles change, the next time the data is reloaded, it should // delete all its data and build a clean model (i.e. same behaviour as // if no keys are set). QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics"); model->setProperty("xml",xml); QTRY_COMPARE(model->rowCount(), 2); model->setProperty("xml",""); QSignalSpy spyInsert(model, SIGNAL(rowsInserted(QModelIndex,int,int))); QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(QModelIndex,int,int))); QSignalSpy spyCount(model, SIGNAL(countChanged())); QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey")); model->setProperty("xml",xml); QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0); QCOMPARE(spyInsert.count(), 1); QCOMPARE(spyInsert[0][1].toInt(), 0); QCOMPARE(spyInsert[0][2].toInt(), 1); QCOMPARE(spyRemove.count(), 1); QCOMPARE(spyRemove[0][1].toInt(), 0); QCOMPARE(spyRemove[0][2].toInt(), 1); QCOMPARE(spyCount.count(), 0); delete model; } void tst_qquickxmllistmodel::threading() { QFETCH(int, xmlDataCount); QQmlComponent component(&engine, testFileUrl("roleKeys.qml")); QAbstractItemModel *m1 = qobject_cast(component.create()); QVERIFY(m1 != 0); QAbstractItemModel *m2 = qobject_cast(component.create()); QVERIFY(m2 != 0); QAbstractItemModel *m3 = qobject_cast(component.create()); QVERIFY(m3 != 0); for (int dataCount=0; dataCountsetProperty("xml",makeItemXmlAndData(data1)); m2->setProperty("xml",makeItemXmlAndData(data2)); m3->setProperty("xml",makeItemXmlAndData(data3)); QCoreApplication::processEvents(); m2->setProperty("xml",makeItemXmlAndData(data2)); m1->setProperty("xml",makeItemXmlAndData(data1)); m2->setProperty("xml",makeItemXmlAndData(data2)); QCoreApplication::processEvents(); m3->setProperty("xml",makeItemXmlAndData(data3)); QCoreApplication::processEvents(); m2->setProperty("xml",makeItemXmlAndData(data2)); m1->setProperty("xml",makeItemXmlAndData(data1)); m2->setProperty("xml",makeItemXmlAndData(data2)); m3->setProperty("xml",makeItemXmlAndData(data3)); QCoreApplication::processEvents(); m2->setProperty("xml",makeItemXmlAndData(data2)); m3->setProperty("xml",makeItemXmlAndData(data3)); m3->setProperty("xml",makeItemXmlAndData(data3)); QCoreApplication::processEvents(); QTRY_VERIFY(m1->rowCount() == dataCount && m2->rowCount() == dataCount && m3->rowCount() == dataCount); for (int i=0; iindex(i, 0); QList roles = m1->roleNames().keys(); std::sort(roles.begin(), roles.end()); QCOMPARE(m1->data(index, roles.at(0)).toString(), QLatin1Char('A') + QString::number(i)); QCOMPARE(m1->data(index, roles.at(1)).toString(), QLatin1Char('1') + QString::number(i)); QCOMPARE(m1->data(index, roles.at(2)).toString(), QString("Football")); index = m2->index(i, 0); roles = m2->roleNames().keys(); std::sort(roles.begin(), roles.end()); QCOMPARE(m2->data(index, roles.at(0)).toString(), QLatin1Char('B') + QString::number(i)); QCOMPARE(m2->data(index, roles.at(1)).toString(), QLatin1Char('2') + QString::number(i)); QCOMPARE(m2->data(index, roles.at(2)).toString(), QString("Athletics")); index = m3->index(i, 0); roles = m3->roleNames().keys(); std::sort(roles.begin(), roles.end()); QCOMPARE(m3->data(index, roles.at(0)).toString(), QLatin1Char('C') + QString::number(i)); QCOMPARE(m3->data(index, roles.at(1)).toString(), QLatin1Char('3') + QString::number(i)); QCOMPARE(m3->data(index, roles.at(2)).toString(), QString("Curling")); } } delete m1; delete m2; delete m3; } void tst_qquickxmllistmodel::threading_data() { QTest::addColumn("xmlDataCount"); QTest::newRow("1") << 1; QTest::newRow("2") << 2; QTest::newRow("10") << 10; } void tst_qquickxmllistmodel::propertyChanges() { QQmlComponent component(&engine, testFileUrl("propertychanges.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 9); QObject *role = model->findChild("role"); QVERIFY(role); QSignalSpy nameSpy(role, SIGNAL(nameChanged())); QSignalSpy querySpy(role, SIGNAL(queryChanged())); QSignalSpy isKeySpy(role, SIGNAL(isKeyChanged())); role->setProperty("name","size"); role->setProperty("query","size/string()"); role->setProperty("isKey",true); QCOMPARE(role->property("name").toString(), QString("size")); QCOMPARE(role->property("query").toString(), QString("size/string()")); QVERIFY(role->property("isKey").toBool()); QCOMPARE(nameSpy.count(),1); QCOMPARE(querySpy.count(),1); QCOMPARE(isKeySpy.count(),1); role->setProperty("name","size"); role->setProperty("query","size/string()"); role->setProperty("isKey",true); QCOMPARE(nameSpy.count(),1); QCOMPARE(querySpy.count(),1); QCOMPARE(isKeySpy.count(),1); QSignalSpy sourceSpy(model, SIGNAL(sourceChanged())); QSignalSpy xmlSpy(model, SIGNAL(xmlChanged())); QSignalSpy modelQuerySpy(model, SIGNAL(queryChanged())); QSignalSpy namespaceDeclarationsSpy(model, SIGNAL(namespaceDeclarationsChanged())); model->setProperty("source",QUrl("")); model->setProperty("xml","PollyParrot12Small"); model->setProperty("query","/Pets"); model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";"); QCOMPARE(model->property("source").toUrl(), QUrl("")); QCOMPARE(model->property("xml").toString(), QString("PollyParrot12Small")); QCOMPARE(model->property("query").toString(), QString("/Pets")); QCOMPARE(model->property("namespaceDeclarations").toString(), QString("declare namespace media=\"http://search.yahoo.com/mrss/\";")); QTRY_COMPARE(model->rowCount(), 1); QCOMPARE(sourceSpy.count(),1); QCOMPARE(xmlSpy.count(),1); QCOMPARE(modelQuerySpy.count(),1); QCOMPARE(namespaceDeclarationsSpy.count(),1); model->setProperty("source",QUrl("")); model->setProperty("xml","PollyParrot12Small"); model->setProperty("query","/Pets"); model->setProperty("namespaceDeclarations","declare namespace media=\"http://search.yahoo.com/mrss/\";"); QCOMPARE(sourceSpy.count(),1); QCOMPARE(xmlSpy.count(),1); QCOMPARE(modelQuerySpy.count(),1); QCOMPARE(namespaceDeclarationsSpy.count(),1); QTRY_COMPARE(model->rowCount(), 1); delete model; } void tst_qquickxmllistmodel::selectAncestor() { QQmlComponent component(&engine, testFileUrl("groups.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); QTRY_COMPARE(model->rowCount(), 1); QModelIndex index = model->index(0, 0); QCOMPARE(model->data(index, Qt::UserRole).toInt(), 12); QCOMPARE(model->data(index, Qt::UserRole+1).toString(), QLatin1String("cats")); } void tst_qquickxmllistmodel::roleCrash() { // don't crash QQmlComponent component(&engine, testFileUrl("roleCrash.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); delete model; } class SortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QObject *source READ source WRITE setSource) public: SortFilterProxyModel(QObject *parent = 0) : QSortFilterProxyModel(parent) { sort(0); } QObject *source() const { return sourceModel(); } void setSource(QObject *source) { setSourceModel(qobject_cast(source)); } }; void tst_qquickxmllistmodel::proxyCrash() { qmlRegisterType("SortFilterProxyModel", 1, 0, "SortFilterProxyModel"); // don't crash QQmlComponent component(&engine, testFileUrl("proxyCrash.qml")); QAbstractItemModel *model = qobject_cast(component.create()); QVERIFY(model != 0); delete model; } QTEST_MAIN(tst_qquickxmllistmodel) #include "tst_qquickxmllistmodel.moc"