summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/declarative/util/qmlxmllistmodel.cpp175
-rw-r--r--tests/auto/declarative/qmlxmllistmodel/tst_qmlxmllistmodel.cpp254
2 files changed, 400 insertions, 29 deletions
diff --git a/src/declarative/util/qmlxmllistmodel.cpp b/src/declarative/util/qmlxmllistmodel.cpp
index df2102a..5de4d6f 100644
--- a/src/declarative/util/qmlxmllistmodel.cpp
+++ b/src/declarative/util/qmlxmllistmodel.cpp
@@ -63,6 +63,8 @@ QT_BEGIN_NAMESPACE
QML_DEFINE_TYPE(Qt,4,6,XmlRole,QmlXmlListModelRole)
QML_DEFINE_TYPE(Qt,4,6,XmlListModel,QmlXmlListModel)
+typedef QPair<int, int> QmlXmlListRange;
+
/*!
\qmlclass XmlRole QmlXmlListModelRole
\brief The XmlRole element allows you to specify a role for an XmlListModel.
@@ -94,14 +96,26 @@ QML_DEFINE_TYPE(Qt,4,6,XmlListModel,QmlXmlListModel)
\endqml
*/
+/*!
+ \qmlproperty bool XmlRole::isKey
+ Defines whether this is a key role.
+
+ Key roles are used to to determine whether a set of values should
+ be updated or added to the XML list model when XmlListModel::reload()
+ is called.
+
+ \sa XmlListModel
+*/
+
class Q_DECLARATIVE_EXPORT QmlXmlListModelRole : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString query READ query WRITE setQuery)
+ Q_PROPERTY(bool isKey READ isKey WRITE setIsKey)
public:
- QmlXmlListModelRole() {}
+ QmlXmlListModelRole() : m_isKey(false) {}
~QmlXmlListModelRole() {}
QString name() const { return m_name; }
@@ -117,6 +131,9 @@ public:
m_query = query;
}
+ bool isKey() const { return m_isKey; }
+ void setIsKey(bool b) { m_isKey = b; }
+
bool isValid() {
return !m_name.isEmpty() && !m_query.isEmpty();
}
@@ -124,6 +141,7 @@ public:
private:
QString m_name;
QString m_query;
+ bool m_isKey;
};
QT_END_NAMESPACE
QML_DECLARE_TYPE(QmlXmlListModelRole)
@@ -166,7 +184,6 @@ public:
int doQuery(QString query, QString namespaces, QByteArray data, QmlXmlRoleList *roleObjects) {
QMutexLocker locker(&m_mutex);
- m_modelData.clear();
m_size = 0;
m_data = data;
m_query = QLatin1String("doc($src)") + query;
@@ -188,6 +205,16 @@ public:
return m_modelData;
}
+ QList<QmlXmlListRange> insertedItemRanges() {
+ QMutexLocker locker(&m_mutex);
+ return m_insertedItemRanges;
+ }
+
+ QList<QmlXmlListRange> removedItemRanges() {
+ QMutexLocker locker(&m_mutex);
+ return m_removedItemRanges;
+ }
+
Q_SIGNALS:
void queryCompleted(int queryId, int size);
@@ -197,13 +224,12 @@ protected:
m_mutex.lock();
int queryId = m_queryId;
doQueryJob();
- if (m_size > 0)
- doSubQueryJob();
+ doSubQueryJob();
m_data.clear(); // no longer needed
m_mutex.unlock();
m_mutex.lock();
- if (!m_abort && m_size > 0)
+ if (!m_abort)
emit queryCompleted(queryId, m_size);
if (!m_restart)
m_condition.wait(&m_mutex);
@@ -216,6 +242,8 @@ protected:
private:
void doQueryJob();
void doSubQueryJob();
+ void getValuesOfKeyRoles(QStringList *values, QXmlQuery *query) const;
+ void addIndexToRangeList(QList<QmlXmlListRange> *ranges, int index) const;
private:
QMutex m_mutex;
@@ -231,6 +259,9 @@ private:
int m_queryId;
const QmlXmlRoleList *m_roleObjects;
QList<QList<QVariant> > m_modelData;
+ QStringList m_keysValues;
+ QList<QmlXmlListRange> m_insertedItemRanges;
+ QList<QmlXmlListRange> m_removedItemRanges;
};
void QmlXmlQuery::doQueryJob()
@@ -275,6 +306,40 @@ void QmlXmlQuery::doQueryJob()
m_size = count;
}
+void QmlXmlQuery::getValuesOfKeyRoles(QStringList *values, QXmlQuery *query) const
+{
+ QStringList keysQueries;
+ for (int i=0; i<m_roleObjects->count(); i++) {
+ if (m_roleObjects->at(i)->isKey())
+ keysQueries << m_roleObjects->at(i)->query();
+ }
+ QString keysQuery;
+ if (keysQueries.count() == 1)
+ keysQuery = m_prefix + keysQueries[0];
+ else if (keysQueries.count() > 1)
+ keysQuery = m_prefix + QLatin1String("concat(") + keysQueries.join(QLatin1String(",")) + QLatin1String(")");
+
+ if (!keysQuery.isEmpty()) {
+ query->setQuery(keysQuery);
+ QXmlResultItems resultItems;
+ query->evaluateTo(&resultItems);
+ QXmlItem item(resultItems.next());
+ while (!item.isNull()) {
+ values->append(item.toAtomicValue().toString());
+ item = resultItems.next();
+ }
+ }
+}
+
+void QmlXmlQuery::addIndexToRangeList(QList<QmlXmlListRange> *ranges, int index) const {
+ if (ranges->isEmpty())
+ ranges->append(qMakePair(index, 1));
+ else if (ranges->last().first + ranges->last().second == index)
+ ranges->last().second += 1;
+ else
+ ranges->append(qMakePair(index, 1));
+}
+
void QmlXmlQuery::doSubQueryJob()
{
m_modelData.clear();
@@ -285,6 +350,35 @@ void QmlXmlQuery::doSubQueryJob()
QXmlQuery subquery;
subquery.bindVariable(QLatin1String("inputDocument"), &b);
+ QStringList keysValues;
+ getValuesOfKeyRoles(&keysValues, &subquery);
+
+ // See if any values of key roles have been inserted or removed.
+ m_insertedItemRanges.clear();
+ m_removedItemRanges.clear();
+ if (m_keysValues.isEmpty()) {
+ m_insertedItemRanges << qMakePair(0, m_size);
+ } else {
+ if (keysValues != m_keysValues) {
+ QStringList temp;
+ for (int i=0; i<m_keysValues.count(); i++) {
+ if (!keysValues.contains(m_keysValues[i]))
+ addIndexToRangeList(&m_removedItemRanges, i);
+ else
+ temp << m_keysValues[i];
+ }
+
+ for (int i=0; i<keysValues.count(); i++) {
+ if (temp.count() == i || keysValues[i] != temp[i]) {
+ temp.insert(i, keysValues[i]);
+ addIndexToRangeList(&m_insertedItemRanges, i);
+ }
+ }
+ }
+ }
+ m_keysValues = keysValues;
+
+ // Get the new values for each role.
//### we might be able to condense even further (query for everything in one go)
for (int i = 0; i < m_roleObjects->size(); ++i) {
QmlXmlListModelRole *role = m_roleObjects->at(i);
@@ -296,13 +390,13 @@ void QmlXmlQuery::doSubQueryJob()
continue;
}
subquery.setQuery(m_prefix + QLatin1String("(let $v := ") + role->query() + QLatin1String(" return if ($v) then ") + role->query() + QLatin1String(" else \"\")"));
- QXmlResultItems output3;
- subquery.evaluateTo(&output3);
- QXmlItem item(output3.next());
+ QXmlResultItems resultItems;
+ subquery.evaluateTo(&resultItems);
+ QXmlItem item(resultItems.next());
QList<QVariant> resultList;
while (!item.isNull()) {
resultList << item.toAtomicValue(); //### we used to trim strings
- item = output3.next();
+ item = resultItems.next();
}
//### should warn here if things have gone wrong.
while (resultList.count() < m_size)
@@ -408,25 +502,40 @@ void QmlXmlRoleList::insert(int i, QmlXmlListModelRole *role)
/*!
\qmlclass XmlListModel QmlXmlListModel
- \brief The XmlListModel element allows you to specify a model using XPath expressions.
+ \brief The XmlListModel element is used to specify a model using XPath expressions.
- XmlListModel allows you to construct a model from XML data that can then be used as a data source
- for the view classes (ListView, PathView, GridView) and any other classes that interact with model
- data (like Repeater).
+ XmlListModel is used to create a model from XML data that can be used as a data source
+ for the view classes (such as ListView, PathView, GridView) and other classes that interact with model
+ data (such as Repeater).
- The following is an example of a model containing news from a Yahoo RSS feed:
+ Here is an example of a model containing news from a Yahoo RSS feed:
\qml
XmlListModel {
id: feedModel
source: "http://rss.news.yahoo.com/rss/oceania"
query: "/rss/channel/item"
XmlRole { name: "title"; query: "title/string()" }
- XmlRole { name: "link"; query: "link/string()" }
+ XmlRole { name: "pubDate"; query: "pubDate/string()" }
XmlRole { name: "description"; query: "description/string()" }
}
\endqml
- \note The model is currently static, so the above is really just a snapshot of an RSS feed. To force a
- reload of the entire model, you can call the reload function.
+
+ You can also define certain roles as "keys" so that the model only adds data
+ that contains new values for these keys when reload() is called.
+
+ For example, if the roles above were defined like this:
+
+ \qml
+ XmlRole { name: "title"; query: "title/string()"; isKey: true }
+ XmlRole { name: "pubDate"; query: "pubDate/string()"; isKey: true }
+ \endqml
+
+ Then when reload() is called, the model will only add new items with a
+ "title" and "pubDate" value combination that is not already present in
+ the model.
+
+ This is useful to provide incremental updates and avoid repainting an
+ entire model in a view.
*/
QmlXmlListModel::QmlXmlListModel(QObject *parent)
@@ -632,8 +741,13 @@ void QmlXmlListModel::componentComplete()
/*!
\qmlmethod XmlListModel::reload()
- Reloads the model. All the existing model data will be removed, and the model
- will be rebuilt from scratch.
+ Reloads the model.
+
+ If no key roles have been specified, all existing model
+ data is removed, and the model is rebuilt from scratch.
+
+ Otherwise, items are only added if the model does not already
+ contain items with matching key role values.
*/
void QmlXmlListModel::reload()
{
@@ -645,12 +759,8 @@ void QmlXmlListModel::reload()
d->qmlXmlQuery.abort();
d->queryId = -1;
- //clear existing data
- int count = d->size;
- d->size = 0;
- d->data.clear();
- if (count > 0)
- emit itemsRemoved(0, count);
+ if (d->size < 0)
+ d->size = 0;
if (d->src.isEmpty() && d->xml.isEmpty())
return;
@@ -717,12 +827,19 @@ void QmlXmlListModel::queryCompleted(int id, int size)
Q_D(QmlXmlListModel);
if (id != d->queryId)
return;
+ bool sizeChanged = size != d->size;
d->size = size;
- if (size > 0) {
- d->data = d->qmlXmlQuery.modelData();
- emit itemsInserted(0, d->size);
+ d->data = d->qmlXmlQuery.modelData();
+
+ QList<QmlXmlListRange> removed = d->qmlXmlQuery.removedItemRanges();
+ for (int i=0; i<removed.count(); i++)
+ emit itemsRemoved(removed[i].first, removed[i].second);
+ QList<QmlXmlListRange> inserted = d->qmlXmlQuery.insertedItemRanges();
+ for (int i=0; i<inserted.count(); i++)
+ emit itemsInserted(inserted[i].first, inserted[i].second);
+
+ if (sizeChanged)
emit countChanged();
- }
}
QT_END_NAMESPACE
diff --git a/tests/auto/declarative/qmlxmllistmodel/tst_qmlxmllistmodel.cpp b/tests/auto/declarative/qmlxmllistmodel/tst_qmlxmllistmodel.cpp
index 966d7de..5d4682b 100644
--- a/tests/auto/declarative/qmlxmllistmodel/tst_qmlxmllistmodel.cpp
+++ b/tests/auto/declarative/qmlxmllistmodel/tst_qmlxmllistmodel.cpp
@@ -39,6 +39,8 @@
**
****************************************************************************/
#include <qtest.h>
+#include <QtTest/qsignalspy.h>
+#include <QtCore/qtimer.h>
#ifdef QTEST_XMLPATTERNS
#include <QtDeclarative/qmlengine.h>
@@ -46,6 +48,12 @@
#include <private/qmlxmllistmodel_p.h>
#include "../../../shared/util.h"
+typedef QPair<int, int> QmlXmlListRange;
+typedef QList<QVariantList> QmlXmlModelData;
+
+Q_DECLARE_METATYPE(QList<QmlXmlListRange>)
+Q_DECLARE_METATYPE(QmlXmlModelData)
+
class tst_qmlxmllistmodel : public QObject
{
@@ -61,8 +69,47 @@ private slots:
void roles();
void roleErrors();
void uniqueRoleNames();
+ void useKeys();
+ void useKeys_data();
+ void noKeysValueChanges();
+ void keysChanged();
private:
+ QString makeItemXmlAndData(const QString &data, QmlXmlModelData *modelData = 0) const
+ {
+ if (modelData)
+ modelData->clear();
+ QString xml;
+
+ if (!data.isEmpty()) {
+ QStringList items = data.split(";");
+ foreach(const QString &item, items) {
+ QVariantList variants;
+ xml += QLatin1String("<item>");
+ QStringList fields = item.split(",");
+ foreach(const QString &field, fields) {
+ QStringList values = field.split("=");
+ Q_ASSERT(values.count() == 2);
+ xml += QString("<%1>%2</%1>").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("</item>");
+ if (modelData)
+ modelData->append(variants);
+ }
+ }
+
+ QString decl = "<?xml version=\"1.0\" encoding=\"iso-8859-1\" ?>";
+ return decl + QLatin1String("<data>") + xml + QLatin1String("</data>");
+ }
+
QmlEngine engine;
};
@@ -194,6 +241,213 @@ void tst_qmlxmllistmodel::uniqueRoleNames()
delete listModel;
}
+void tst_qmlxmllistmodel::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(QmlXmlModelData, newData);
+ QFETCH(QList<QmlXmlListRange>, insertRanges);
+ QFETCH(QList<QmlXmlListRange>, removeRanges);
+
+ QmlComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
+ QmlXmlListModel *model = qobject_cast<QmlXmlListModel*>(component.create());
+ QVERIFY(model != 0);
+
+ model->setXml(oldXml);
+ QTRY_COMPARE(model->count(), oldCount);
+
+ QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+
+ model->setXml(newXml);
+
+ if (oldCount != newData.count()) {
+ QTRY_COMPARE(model->count(), newData.count());
+ QCOMPARE(spyCount.count(), 1);
+ } else {
+ QTRY_VERIFY(spyInsert.count() > 0 || spyRemove.count() > 0);
+ QCOMPARE(spyCount.count(), 0);
+ }
+
+ QList<int> roles = model->roles();
+ for (int i=0; i<model->count(); i++) {
+ for (int j=0; j<roles.count(); j++)
+ QCOMPARE(model->data(i, roles[j]), newData[i][j]);
+ }
+
+ QCOMPARE(spyInsert.count(), insertRanges.count());
+ for (int i=0; i<spyInsert.count(); i++) {
+ QCOMPARE(spyInsert[i][0].toInt(), insertRanges[i].first);
+ QCOMPARE(spyInsert[i][1].toInt(), insertRanges[i].second);
+ }
+
+ QCOMPARE(spyRemove.count(), removeRanges.count());
+ for (int i=0; i<spyRemove.count(); i++) {
+ QCOMPARE(spyRemove[i][0].toInt(), removeRanges[i].first);
+ QCOMPARE(spyRemove[i][1].toInt(), removeRanges[i].second);
+ }
+}
+
+void tst_qmlxmllistmodel::useKeys_data()
+{
+ QTest::addColumn<QString>("oldXml");
+ QTest::addColumn<int>("oldCount");
+ QTest::addColumn<QString>("newXml");
+ QTest::addColumn<QmlXmlModelData>("newData");
+ QTest::addColumn<QList<QmlXmlListRange> >("insertRanges");
+ QTest::addColumn<QList<QmlXmlListRange> >("removeRanges");
+
+ QmlXmlModelData 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<QmlXmlListRange>() << qMakePair(1, 1))
+ << QList<QmlXmlListRange>();
+
+ 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<QmlXmlListRange>() << qMakePair(1, 2))
+ << QList<QmlXmlListRange>();
+
+ 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<QmlXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2))
+ << QList<QmlXmlListRange>();
+
+ 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<QmlXmlListRange>() << qMakePair(1, 2))
+ << QList<QmlXmlListRange>();
+
+ 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<QmlXmlListRange>()
+ << (QList<QmlXmlListRange>() << 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<QmlXmlListRange>()
+ << (QList<QmlXmlListRange>() << 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<QmlXmlListRange>()
+ << (QList<QmlXmlListRange>() << 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<QmlXmlListRange>()
+ << (QList<QmlXmlListRange>() << 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<QmlXmlListRange>() << qMakePair(0, 1))
+ << (QList<QmlXmlListRange>() << qMakePair(0, 1));
+
+ QTest::newRow("add and remove simultaneously")
+ << 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<QmlXmlListRange>() << qMakePair(1, 1))
+ << (QList<QmlXmlListRange>() << qMakePair(0, 1) << qMakePair(2,2));
+}
+
+void tst_qmlxmllistmodel::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.
+
+ QmlComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
+ QmlXmlListModel *model = qobject_cast<QmlXmlListModel*>(component.create());
+ QVERIFY(model != 0);
+
+ QString xml;
+
+ xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
+ model->setXml(xml);
+ QTRY_COMPARE(model->count(), 2);
+
+ QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+
+ xml = makeItemXmlAndData("name=A,age=25,sport=AussieRules;name=B,age=35,sport=Athletics");
+ model->setXml(xml);
+
+ // wait for the new xml data to be set, and verify no signals were emitted
+ for (int i=0; i<50; i++) {
+ QTest::qWait(100);
+ if (model->data(0, model->roles()[2]).toString() != QLatin1String("AussieRules"))
+ break;
+ }
+ QCOMPARE(model->data(0, model->roles()[2]).toString(), QLatin1String("AussieRules"));
+
+ QVERIFY(spyInsert.count() == 0);
+ QVERIFY(spyRemove.count() == 0);
+ QVERIFY(spyCount.count() == 0);
+
+ QCOMPARE(model->count(), 2);
+}
+
+void tst_qmlxmllistmodel::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).
+
+ QmlComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/roleKeys.qml"));
+ QmlXmlListModel *model = qobject_cast<QmlXmlListModel*>(component.create());
+ QVERIFY(model != 0);
+
+ QString xml = makeItemXmlAndData("name=A,age=25,sport=Football;name=B,age=35,sport=Athletics");
+ model->setXml(xml);
+ QTRY_COMPARE(model->count(), 2);
+
+ QSignalSpy spyInsert(model, SIGNAL(itemsInserted(int,int)));
+ QSignalSpy spyRemove(model, SIGNAL(itemsRemoved(int,int)));
+ QSignalSpy spyCount(model, SIGNAL(countChanged()));
+
+ QVERIFY(QMetaObject::invokeMethod(model, "disableNameKey"));
+ model->setXml(xml);
+
+ QTRY_VERIFY(spyInsert.count() > 0 && spyRemove.count() > 0);
+
+ QCOMPARE(spyInsert.count(), 1);
+ QCOMPARE(spyInsert[0][0].toInt(), 0);
+ QCOMPARE(spyInsert[0][1].toInt(), 2);
+
+ QCOMPARE(spyRemove.count(), 1);
+ QCOMPARE(spyRemove[0][0].toInt(), 0);
+ QCOMPARE(spyRemove[0][1].toInt(), 2);
+
+ QCOMPARE(spyCount.count(), 0);
+}
+
QTEST_MAIN(tst_qmlxmllistmodel)
#include "tst_qmlxmllistmodel.moc"