aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qtquick2/qquicklistview
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qtquick2/qquicklistview')
-rw-r--r--tests/auto/qtquick2/qquicklistview/data/addTransitions.qml134
-rw-r--r--tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml141
-rw-r--r--tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml121
-rw-r--r--tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml102
-rw-r--r--tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml144
-rw-r--r--tests/auto/qtquick2/qquicklistview/tst_qquicklistview.cpp950
6 files changed, 1590 insertions, 2 deletions
diff --git a/tests/auto/qtquick2/qquicklistview/data/addTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/addTransitions.qml
new file mode 100644
index 0000000000..ff90ead8a6
--- /dev/null
+++ b/tests/auto/qtquick2/qquicklistview/data/addTransitions.qml
@@ -0,0 +1,134 @@
+import QtQuick 2.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 600
+
+ property int duration: 10
+ property int count: list.count
+
+ Component {
+ id: myDelegate
+ Rectangle {
+ id: wrapper
+
+ property string nameData: name
+
+ objectName: "wrapper"
+ height: 20
+ width: 240
+ Text { text: index }
+ Text {
+ x: 30
+ id: textName
+ objectName: "textName"
+ text: name
+ }
+ Text {
+ x: 200
+ text: wrapper.y
+ }
+ color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+ onXChanged: checkPos()
+ onYChanged: checkPos()
+
+ function checkPos() {
+ if (Qt.point(x, y) == targetItems_transitionFrom)
+ model_targetItems_transitionFrom.addItem(name, "")
+ if (Qt.point(x, y) == displacedItems_transitionVia)
+ model_displacedItems_transitionVia.addItem(name, "")
+ }
+ }
+ }
+
+ ListView {
+ id: list
+
+ property int targetTransitionsDone
+ property int displaceTransitionsDone
+
+ property var targetTrans_items: new Object()
+ property var targetTrans_targetIndexes: new Array()
+ property var targetTrans_targetItems: new Array()
+
+ property var displacedTrans_items: new Object()
+ property var displacedTrans_targetIndexes: new Array()
+ property var displacedTrans_targetItems: new Array()
+
+ objectName: "list"
+ focus: true
+ anchors.centerIn: parent
+ width: 240
+ height: 320
+ model: testModel
+ delegate: myDelegate
+
+ // for QDeclarativeListProperty types
+ function copyList(propList) {
+ var temp = new Array()
+ for (var i=0; i<propList.length; i++)
+ temp.push(propList[i])
+ return temp
+ }
+
+ add: Transition {
+ id: targetTransition
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ list.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+ list.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+ list.targetTrans_targetItems.push(list.copyList(targetTransition.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: targetItems_transitionFrom.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: targetItems_transitionFrom.y; duration: root.duration }
+ }
+
+ ScriptAction { script: list.targetTransitionsDone += 1 }
+ }
+ }
+
+ addDisplaced: Transition {
+ id: displaced
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ list.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+ list.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+ list.displacedTrans_targetItems.push(list.copyList(displaced.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; duration: root.duration; to: displacedItems_transitionVia.x }
+ NumberAnimation { properties: "y"; duration: root.duration; to: displacedItems_transitionVia.y }
+ }
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+
+ ScriptAction { script: list.displaceTransitionsDone += 1 }
+ }
+
+ }
+ }
+
+ Rectangle {
+ anchors.fill: list
+ color: "lightsteelblue"
+ opacity: 0.2
+ }
+
+ // XXX will it pass without these if I just wait for polish?
+ // check all of these tests - if not, then mark this bit with the bug number!
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: 20; height: 20
+ color: "white"
+ NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+ }
+}
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml
new file mode 100644
index 0000000000..744db3110e
--- /dev/null
+++ b/tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml
@@ -0,0 +1,141 @@
+import QtQuick 2.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 600
+
+ property int duration: 10
+ property int count: list.count
+
+ Component {
+ id: myDelegate
+ Rectangle {
+ id: wrapper
+
+ property string nameData: name
+
+ objectName: "wrapper"
+ height: 20
+ width: 240
+ Text { text: index }
+ Text {
+ x: 30
+ id: textName
+ objectName: "textName"
+ text: name
+ }
+ Text {
+ x: 200
+ text: wrapper.y
+ }
+ color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+ onXChanged: checkPos()
+ onYChanged: checkPos()
+
+ function checkPos() {
+ if (Qt.point(x, y) == targetItems_transitionVia)
+ model_targetItems_transitionVia.addItem(name, "")
+ if (Qt.point(x, y) == displacedItems_transitionVia)
+ model_displacedItems_transitionVia.addItem(name, "")
+ }
+ }
+ }
+
+ ListView {
+ id: list
+
+ property int targetTransitionsDone
+ property int displaceTransitionsDone
+
+ property var targetTrans_items: new Object()
+ property var targetTrans_targetIndexes: new Array()
+ property var targetTrans_targetItems: new Array()
+
+ property var displacedTrans_items: new Object()
+ property var displacedTrans_targetIndexes: new Array()
+ property var displacedTrans_targetItems: new Array()
+
+ objectName: "list"
+ focus: true
+ anchors.centerIn: parent
+ width: 240
+ height: 320
+ model: testModel
+ delegate: myDelegate
+
+ // for QDeclarativeListProperty types
+ function copyList(propList) {
+ var temp = new Array()
+ for (var i=0; i<propList.length; i++)
+ temp.push(propList[i])
+ return temp
+ }
+
+ move: Transition {
+ id: targetTransition
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ list.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+ list.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+ list.targetTrans_targetItems.push(list.copyList(targetTransition.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; to: targetItems_transitionVia.x; duration: root.duration }
+ NumberAnimation { properties: "y"; to: targetItems_transitionVia.y; duration: root.duration }
+ }
+
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+
+ ScriptAction { script: list.targetTransitionsDone += 1 }
+ }
+ }
+
+ moveDisplaced: Transition {
+ id: displaced
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ list.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+ list.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+ list.displacedTrans_targetItems.push(list.copyList(displaced.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation {
+ properties: "x"; duration: root.duration
+ to: displacedItems_transitionVia.x
+ }
+ NumberAnimation {
+ properties: "y"; duration: root.duration
+ to: displacedItems_transitionVia.y
+ }
+ }
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+
+ ScriptAction { script: list.displaceTransitionsDone += 1 }
+ }
+
+ }
+ }
+
+ Rectangle {
+ anchors.fill: list
+ color: "lightsteelblue"
+ opacity: 0.2
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: 20; height: 20
+ color: "white"
+ NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 10000 }
+ }
+}
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml
new file mode 100644
index 0000000000..50ffbc53c3
--- /dev/null
+++ b/tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml
@@ -0,0 +1,121 @@
+import QtQuick 2.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 600
+
+ // time to pause between each add, remove, etc.
+ // (obviously, must be less than 'duration' value to actually test that
+ // interrupting transitions will still produce the correct result)
+ property int timeBetweenActions: duration / 2
+
+ property int duration: 100
+
+ property int count: list.count
+
+ Component {
+ id: myDelegate
+ Rectangle {
+ id: wrapper
+ objectName: "wrapper"
+ height: 20
+ width: 240
+ Text { text: index }
+ Text {
+ x: 30
+ id: textName
+ objectName: "textName"
+ text: name
+ }
+ Text {
+ x: 200
+ text: wrapper.y
+ }
+ color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+ }
+ }
+
+ ListView {
+ id: list
+
+ property bool populateDone
+
+ property bool runningAddTargets: false
+ property bool runningAddDisplaced: false
+ property bool runningMoveTargets: false
+ property bool runningMoveDisplaced: false
+
+ objectName: "list"
+ focus: true
+ anchors.centerIn: parent
+ width: 240
+ height: 320
+ model: testModel
+ delegate: myDelegate
+
+ add: Transition {
+ id: addTargets
+ SequentialAnimation {
+ ScriptAction { script: list.runningAddTargets = true }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: addTargets_transitionFrom.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: addTargets_transitionFrom.y; duration: root.duration }
+ }
+ ScriptAction { script: list.runningAddTargets = false }
+ }
+ }
+
+ addDisplaced: Transition {
+ id: addDisplaced
+ SequentialAnimation {
+ ScriptAction { script: list.runningAddDisplaced = true }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: addDisplaced_transitionFrom.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: addDisplaced_transitionFrom.y; duration: root.duration }
+ }
+ ScriptAction { script: list.runningAddDisplaced = false }
+ }
+ }
+
+ move: Transition {
+ id: moveTargets
+ SequentialAnimation {
+ ScriptAction { script: list.runningMoveTargets = true }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: moveTargets_transitionFrom.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: moveTargets_transitionFrom.y; duration: root.duration }
+ }
+ ScriptAction { script: list.runningMoveTargets = false }
+ }
+ }
+
+ moveDisplaced: Transition {
+ id: moveDisplaced
+ SequentialAnimation {
+ ScriptAction { script: list.runningMoveDisplaced = true }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: moveDisplaced_transitionFrom.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: moveDisplaced_transitionFrom.y; duration: root.duration }
+ }
+ ScriptAction { script: list.runningMoveDisplaced = false }
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.fill: list
+ color: "lightsteelblue"
+ opacity: 0.2
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: 20; height: 20
+ color: "white"
+ NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+ }
+}
+
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml
new file mode 100644
index 0000000000..0994e0943d
--- /dev/null
+++ b/tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml
@@ -0,0 +1,102 @@
+import QtQuick 2.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 600
+
+ property int duration: 10
+ property int count: list.count
+
+ Component {
+ id: myDelegate
+ Rectangle {
+ id: wrapper
+ objectName: "wrapper"
+ height: 20
+ width: 240
+ Text { text: index }
+ Text {
+ x: 30
+ id: textName
+ objectName: "textName"
+ text: name
+ }
+ Text {
+ x: 200
+ text: wrapper.y
+ }
+ color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+ onXChanged: checkPos()
+ onYChanged: checkPos()
+
+ function checkPos() {
+ if (Qt.point(x, y) == transitionFrom)
+ model_transitionFrom.addItem(name, "")
+ if (Qt.point(x, y) == transitionVia) {
+ model_transitionVia.addItem(name, "")
+ }
+ }
+ }
+ }
+
+ ListView {
+ id: list
+
+ property int countPopulateTransitions
+ property int countAddTransitions
+
+ objectName: "list"
+ focus: true
+ anchors.centerIn: parent
+ width: 240
+ height: 320
+ model: testModel
+ delegate: myDelegate
+
+ populate: usePopulateTransition ? popTransition : null
+
+ add: Transition {
+ SequentialAnimation {
+ ScriptAction { script: list.countAddTransitions += 1 }
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+ }
+ }
+ }
+
+ Transition {
+ id: popTransition
+ SequentialAnimation {
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; from: transitionFrom.x; to: transitionVia.x; duration: root.duration }
+ NumberAnimation { properties: "y"; from: transitionFrom.y; to: transitionVia.y; duration: root.duration }
+ }
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+ ScriptAction { script: list.countPopulateTransitions += 1 }
+ }
+ }
+
+
+ Rectangle {
+ anchors.fill: list
+ color: "lightsteelblue"
+ opacity: 0.2
+ }
+
+ Component.onCompleted: {
+ if (dynamicallyPopulate) {
+ for (var i=0; i<30; i++)
+ testModel.addItem("item " + i, "")
+ }
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: 20; height: 20
+ color: "white"
+ NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 100000 }
+ }
+}
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml b/tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml
new file mode 100644
index 0000000000..95f76f0200
--- /dev/null
+++ b/tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml
@@ -0,0 +1,144 @@
+import QtQuick 2.0
+
+Rectangle {
+ id: root
+ width: 500
+ height: 600
+
+ property int duration: 10
+ property int count: list.count
+
+ Component {
+ id: myDelegate
+ Rectangle {
+ id: wrapper
+
+ property string nameData: name
+
+ objectName: "wrapper"
+ height: 20
+ width: 240
+ Text { text: index }
+ Text {
+ x: 30
+ id: textName
+ objectName: "textName"
+ text: name
+ }
+ Text {
+ x: 200
+ text: wrapper.y
+ }
+ color: ListView.isCurrentItem ? "lightsteelblue" : "white"
+
+ onXChanged: checkPos()
+ onYChanged: checkPos()
+
+ function checkPos() {
+ if (Qt.point(x, y) == targetItems_transitionTo) {
+ model_targetItems_transitionTo.addItem(nameData, "") // name is invalid once model removes the item
+ }
+ if (Qt.point(x, y) == displacedItems_transitionVia) {
+ model_displacedItems_transitionVia.addItem(name, "")
+ }
+ }
+ }
+ }
+
+ ListView {
+ id: list
+
+ property int targetTransitionsDone
+ property int displaceTransitionsDone
+
+ property var targetTrans_items: new Object()
+ property var targetTrans_targetIndexes: new Array()
+ property var targetTrans_targetItems: new Array()
+
+ property var displacedTrans_items: new Object()
+ property var displacedTrans_targetIndexes: new Array()
+ property var displacedTrans_targetItems: new Array()
+
+ objectName: "list"
+ focus: true
+ anchors.centerIn: parent
+ width: 240
+ height: 320
+ model: testModel
+ delegate: myDelegate
+
+ // for QDeclarativeListProperty types
+ function copyList(propList) {
+ var temp = new Array()
+ for (var i=0; i<propList.length; i++)
+ temp.push(propList[i])
+ return temp
+ }
+
+ remove: Transition {
+ id: targetTransition
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ list.targetTrans_items[targetTransition.ViewTransition.item.nameData] = targetTransition.ViewTransition.index
+ list.targetTrans_targetIndexes.push(targetTransition.ViewTransition.targetIndexes)
+ list.targetTrans_targetItems.push(list.copyList(targetTransition.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation { properties: "x"; to: targetItems_transitionTo.x; duration: root.duration }
+ NumberAnimation { properties: "y"; to: targetItems_transitionTo.y; duration: root.duration }
+ }
+ ScriptAction { script: list.targetTransitionsDone += 1 }
+
+ // delay deleting this item so that it stays valid for the tests
+ // (this doesn't delay the test itself)
+ PauseAnimation { duration: 10000 }
+ }
+ }
+
+ removeDisplaced: Transition {
+ id: displaced
+
+ SequentialAnimation {
+ ScriptAction {
+ script: {
+ list.displacedTrans_items[displaced.ViewTransition.item.nameData] = displaced.ViewTransition.index
+ list.displacedTrans_targetIndexes.push(displaced.ViewTransition.targetIndexes)
+ list.displacedTrans_targetItems.push(list.copyList(displaced.ViewTransition.targetItems))
+ }
+ }
+ ParallelAnimation {
+ NumberAnimation {
+ properties: "x"; duration: root.duration
+ to: displacedItems_transitionVia.x
+ }
+ NumberAnimation {
+ properties: "y"; duration: root.duration
+ to: displacedItems_transitionVia.y
+ }
+ }
+ NumberAnimation { properties: "x,y"; duration: root.duration }
+
+ ScriptAction { script: list.displaceTransitionsDone += 1 }
+ }
+
+ }
+ }
+
+ Rectangle {
+ anchors.fill: list
+ color: "lightsteelblue"
+ opacity: 0.2
+ }
+
+ Rectangle {
+ anchors.bottom: parent.bottom
+ width: 20; height: 20
+ color: "white"
+ NumberAnimation on x { loops: Animation.Infinite; from: 0; to: 300; duration: 10000 }
+ }
+}
+
+
diff --git a/tests/auto/qtquick2/qquicklistview/tst_qquicklistview.cpp b/tests/auto/qtquick2/qquicklistview/tst_qquicklistview.cpp
index e809f95eed..393dd09d36 100644
--- a/tests/auto/qtquick2/qquicklistview/tst_qquicklistview.cpp
+++ b/tests/auto/qtquick2/qquicklistview/tst_qquicklistview.cpp
@@ -51,7 +51,6 @@
#include <QtQuick/private/qquicktext_p.h>
#include <QtQuick/private/qquickvisualitemmodel_p.h>
#include <QtDeclarative/private/qdeclarativelistmodel_p.h>
-#include <QtQuick/private/qdeclarativechangeset_p.h>
#include "../../shared/util.h"
#include "../shared/viewtestutil.h"
#include "../shared/visualtestutil.h"
@@ -171,6 +170,17 @@ private slots:
void asynchronous();
void unrequestedVisibility();
+ void populateTransitions();
+ void populateTransitions_data();
+ void addTransitions();
+ void addTransitions_data();
+ void moveTransitions();
+ void moveTransitions_data();
+ void removeTransitions();
+ void removeTransitions_data();
+ void multipleTransitions();
+ void multipleTransitions_data();
+
private:
template <class T> void items(const QUrl &source, bool forceLayout);
template <class T> void changed(const QUrl &source, bool forceLayout);
@@ -182,6 +192,11 @@ private:
template <class T> void clear(const QUrl &source);
template <class T> void sections(const QUrl &source);
+ QList<int> toIntList(const QVariantList &list);
+ void matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes);
+ void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes);
+ void matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems);
+
void inserted_more_data();
void removed_more_data();
void moved_data();
@@ -1356,18 +1371,26 @@ void tst_QQuickListView::multipleChanges()
{
QList<QPair<QString, QString> > items;
for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
- items << qMakePair(QString("new item " + j), QString::number(j));
+ items << qMakePair(QString("new item %1").arg(j), QString::number(j));
model.insertItems(changes[i].index, items);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
break;
}
case ListChange::Removed:
model.removeItems(changes[i].index, changes[i].count);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
break;
case ListChange::Moved:
model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
break;
case ListChange::SetCurrent:
listview->setCurrentIndex(changes[i].index);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ break;
+ case ListChange::SetContentY:
+ listview->setContentY(changes[i].pos);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
break;
}
}
@@ -4729,6 +4752,929 @@ void tst_QQuickListView::unrequestedVisibility()
delete canvas;
}
+void tst_QQuickListView::populateTransitions()
+{
+ QFETCH(bool, staticallyPopulate);
+ QFETCH(bool, dynamicallyPopulate);
+ QFETCH(bool, usePopulateTransition);
+
+ QPointF transitionFrom(-50, -50);
+ QPointF transitionVia(100, 100);
+ QaimModel model_transitionFrom;
+ QaimModel model_transitionVia;
+
+ QaimModel model;
+ if (staticallyPopulate) {
+ for (int i = 0; i < 30; i++)
+ model.addItem("item" + QString::number(i), "");
+ }
+
+ QQuickView *canvas = createView();
+ canvas->rootContext()->setContextProperty("testModel", &model);
+ canvas->rootContext()->setContextProperty("testObject", new TestObject(canvas->rootContext()));
+ canvas->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition);
+ canvas->rootContext()->setContextProperty("dynamicallyPopulate", dynamicallyPopulate);
+ canvas->rootContext()->setContextProperty("transitionFrom", transitionFrom);
+ canvas->rootContext()->setContextProperty("transitionVia", transitionVia);
+ canvas->rootContext()->setContextProperty("model_transitionFrom", &model_transitionFrom);
+ canvas->rootContext()->setContextProperty("model_transitionVia", &model_transitionVia);
+ canvas->setSource(testFileUrl("populateTransitions.qml"));
+ canvas->show();
+
+ QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+ QVERIFY(listview);
+ QQuickItem *contentItem = listview->contentItem();
+ QVERIFY(contentItem);
+
+ if (staticallyPopulate || dynamicallyPopulate) {
+ // check the populate transition is run
+ if (usePopulateTransition) {
+ QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 17);
+ } else {
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
+ }
+ QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
+ } else {
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ }
+
+ int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+ if (usePopulateTransition)
+ QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt());
+ for (int i=0; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QTRY_COMPARE(item->x(), 0.0);
+ QTRY_COMPARE(item->y(), i*20.0);
+ QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ // add an item and check this is done with add trantion, not populate
+ model.insertItem(0, "another item", "");
+ QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 1);
+ QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(),
+ (usePopulateTransition && (staticallyPopulate || dynamicallyPopulate)) ? 17 : 0);
+
+ // clear the model
+ canvas->rootContext()->setContextProperty("testModel", QVariant());
+ QTRY_COMPARE(listview->count(), 0);
+ QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
+ listview->setProperty("countPopulateTransitions", 0);
+ listview->setProperty("countAddTransitions", 0);
+
+ // set to a valid model and check populate transition is run a second time
+ model.clear();
+ for (int i = 0; i < 30; i++)
+ model.addItem("item" + QString::number(i), "");
+ canvas->rootContext()->setContextProperty("testModel", &model);
+ QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 17 : 0);
+ QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
+
+ itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+ if (usePopulateTransition)
+ QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt());
+ for (int i=0; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QTRY_COMPARE(item->x(), 0.0);
+ QTRY_COMPARE(item->y(), i*20.0);
+ QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ // reset model and check populate transition is run again
+ listview->setProperty("countPopulateTransitions", 0);
+ listview->setProperty("countAddTransitions", 0);
+ model.reset();
+ QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 17 : 0);
+ QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
+
+ itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+ if (usePopulateTransition)
+ QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt());
+ for (int i=0; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QTRY_COMPARE(item->x(), 0.0);
+ QTRY_COMPARE(item->y(), i*20.0);
+ QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ delete canvas;
+}
+
+void tst_QQuickListView::populateTransitions_data()
+{
+ QTest::addColumn<bool>("staticallyPopulate");
+ QTest::addColumn<bool>("dynamicallyPopulate");
+ QTest::addColumn<bool>("usePopulateTransition");
+
+ QTest::newRow("static") << true << false << true;
+ QTest::newRow("static, no populate") << true << false << false;
+
+ QTest::newRow("dynamic") << false << true << true;
+ QTest::newRow("dynamic, no populate") << false << true << false;
+
+ QTest::newRow("empty to start with") << false << false << true;
+ QTest::newRow("empty to start with, no populate") << false << false << false;
+}
+
+void tst_QQuickListView::addTransitions()
+{
+ QFETCH(int, initialItemCount);
+ QFETCH(bool, shouldAnimateTargets);
+ QFETCH(qreal, contentY);
+ QFETCH(int, insertionIndex);
+ QFETCH(int, insertionCount);
+ QFETCH(ListRange, expectedDisplacedIndexes);
+
+ // added items should start here
+ QPointF targetItems_transitionFrom(-50, -50);
+
+ // displaced items should pass through this point
+ QPointF displacedItems_transitionVia(100, 100);
+
+ QaimModel model;
+ for (int i = 0; i < initialItemCount; i++)
+ model.addItem("Original item" + QString::number(i), "");
+ QaimModel model_targetItems_transitionFrom;
+ QaimModel model_displacedItems_transitionVia;
+
+ QQuickView *canvas = createView();
+ QDeclarativeContext *ctxt = canvas->rootContext();
+ TestObject *testObject = new TestObject;
+ ctxt->setContextProperty("testModel", &model);
+ ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
+ ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+ ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
+ ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+ ctxt->setContextProperty("testObject", testObject);
+ canvas->setSource(testFileUrl("addTransitions.qml"));
+ canvas->show();
+
+ QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+ QTRY_VERIFY(listview != 0);
+ QQuickItem *contentItem = listview->contentItem();
+ QVERIFY(contentItem != 0);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
+ if (contentY != 0) {
+ listview->setContentY(contentY);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ }
+
+ QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+ // only target items that will become visible should be animated
+ QList<QPair<QString, QString> > newData;
+ QList<QPair<QString, QString> > expectedTargetData;
+ QList<int> targetIndexes;
+ if (shouldAnimateTargets) {
+ for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
+ newData << qMakePair(QString("New item %1").arg(i), QString(""));
+
+ if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) { // only grab visible items
+ expectedTargetData << newData.last();
+ targetIndexes << i;
+ }
+ }
+ QVERIFY(expectedTargetData.count() > 0);
+ }
+
+ // start animation
+ if (!newData.isEmpty()) {
+ model.insertItems(insertionIndex, newData);
+ QTRY_COMPARE(model.count(), listview->count());
+ }
+
+ QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+
+ if (shouldAnimateTargets) {
+ QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+ QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
+ expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+ // check the target and displaced items were animated
+ model_targetItems_transitionFrom.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+ model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+ // check attached properties
+ matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
+ matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
+ if (expectedDisplacedIndexes.isValid()) {
+ // adjust expectedDisplacedIndexes to their final values after the move
+ QList<int> displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount);
+ matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+ matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
+ }
+
+ } else {
+ QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0);
+ QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
+ }
+
+ QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+ int firstVisibleIndex = -1;
+ int itemCount = items.count();
+ for (int i=0; i<items.count(); i++) {
+ if (items[i]->y() >= contentY) {
+ QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+ firstVisibleIndex = e.evaluate().toInt();
+ break;
+ }
+ }
+ QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+ // verify all items moved to the correct final positions
+ for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QTRY_COMPARE(item->y(), i*20.0);
+ QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ delete canvas;
+ delete testObject;
+}
+
+void tst_QQuickListView::addTransitions_data()
+{
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<qreal>("contentY");
+ QTest::addColumn<bool>("shouldAnimateTargets");
+ QTest::addColumn<int>("insertionIndex");
+ QTest::addColumn<int>("insertionCount");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ // if inserting before visible index, items should not appear or animate in, even if there are > 1 new items
+ QTest::newRow("insert 1, just before start")
+ << 30 << 20.0 << false
+ << 0 << 1 << ListRange();
+ QTest::newRow("insert 1, way before start")
+ << 30 << 20.0 << false
+ << 0 << 1 << ListRange();
+ QTest::newRow("insert multiple, just before start")
+ << 30 << 100.0 << false
+ << 0 << 3 << ListRange();
+ QTest::newRow("insert multiple, way before start")
+ << 30 << 100.0 << false
+ << 0 << 3 << ListRange();
+
+ QTest::newRow("insert 1 at start")
+ << 30 << 0.0 << true
+ << 0 << 1 << ListRange(0, 15);
+ QTest::newRow("insert multiple at start")
+ << 30 << 0.0 << true
+ << 0 << 3 << ListRange(0, 15);
+ QTest::newRow("insert 1 at start, content y not 0")
+ << 30 << 40.0 << true // first visible is index 2, so translate the displaced indexes by 2
+ << 2 << 1 << ListRange(0 + 2, 15 + 2);
+ QTest::newRow("insert multiple at start, content y not 0")
+ << 30 << 40.0 << true // first visible is index 2
+ << 2 << 3 << ListRange(0 + 2, 15 + 2);
+
+ QTest::newRow("insert 1 at start, to empty list")
+ << 0 << 0.0 << true
+ << 0 << 1 << ListRange();
+ QTest::newRow("insert multiple at start, to empty list")
+ << 0 << 0.0 << true
+ << 0 << 3 << ListRange();
+
+ QTest::newRow("insert 1 at middle")
+ << 30 << 0.0 << true
+ << 5 << 1 << ListRange(5, 15);
+ QTest::newRow("insert multiple at middle")
+ << 30 << 0.0 << true
+ << 5 << 3 << ListRange(5, 15);
+
+ QTest::newRow("insert 1 at bottom")
+ << 30 << 0.0 << true
+ << 15 << 1 << ListRange(15, 15);
+ QTest::newRow("insert multiple at bottom")
+ << 30 << 0.0 << true
+ << 15 << 3 << ListRange(15, 15);
+ QTest::newRow("insert 1 at bottom, content y not 0")
+ << 30 << 20.0 * 3 << true
+ << 15 + 3 << 1 << ListRange(15 + 3, 15 + 3);
+ QTest::newRow("insert multiple at bottom, content y not 0")
+ << 30 << 20.0 * 3 << true
+ << 15 + 3 << 3 << ListRange(15 + 3, 15 + 3);
+
+ // items added after the last visible will not be animated in, since they
+ // do not appear in the final view
+ QTest::newRow("insert 1 after end")
+ << 30 << 0.0 << false
+ << 17 << 1 << ListRange();
+ QTest::newRow("insert multiple after end")
+ << 30 << 0.0 << false
+ << 17 << 3 << ListRange();
+}
+
+void tst_QQuickListView::moveTransitions()
+{
+ QFETCH(int, initialItemCount);
+ QFETCH(qreal, contentY);
+ QFETCH(qreal, itemsOffsetAfterMove);
+ QFETCH(int, moveFrom);
+ QFETCH(int, moveTo);
+ QFETCH(int, moveCount);
+ QFETCH(ListRange, expectedDisplacedIndexes);
+
+ // target and displaced items should pass through these points
+ QPointF targetItems_transitionVia(-50, 50);
+ QPointF displacedItems_transitionVia(100, 100);
+
+ QaimModel model;
+ for (int i = 0; i < initialItemCount; i++)
+ model.addItem("Original item" + QString::number(i), "");
+ QaimModel model_targetItems_transitionVia;
+ QaimModel model_displacedItems_transitionVia;
+
+ QQuickView *canvas = createView();
+ QDeclarativeContext *ctxt = canvas->rootContext();
+ TestObject *testObject = new TestObject;
+ ctxt->setContextProperty("testModel", &model);
+ ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia);
+ ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+ ctxt->setContextProperty("targetItems_transitionVia", targetItems_transitionVia);
+ ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+ ctxt->setContextProperty("testObject", testObject);
+ canvas->setSource(testFileUrl("moveTransitions.qml"));
+ canvas->show();
+
+ QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+ QTRY_VERIFY(listview != 0);
+ QQuickItem *contentItem = listview->contentItem();
+ QVERIFY(contentItem != 0);
+ QQuickText *name;
+
+ if (contentY != 0) {
+ listview->setContentY(contentY);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ }
+
+ QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+ // Items moving to *or* from visible positions should be animated.
+ // Otherwise, they should not be animated.
+ QList<QPair<QString, QString> > expectedTargetData;
+ QList<int> targetIndexes;
+ for (int i=moveFrom; i<moveFrom+moveCount; i++) {
+ int toIndex = moveTo + (i - moveFrom);
+ if (i <= (contentY + listview->height()) / 20
+ || toIndex < (contentY + listview->height()) / 20) {
+ expectedTargetData << qMakePair(model.name(i), model.number(i));
+ targetIndexes << i;
+ }
+ }
+ // ViewTransition.index provides the indices that items are moving to, not from
+ targetIndexes = adjustIndexesForMove(targetIndexes, moveFrom, moveTo, moveCount);
+
+ // start animation
+ model.moveItems(moveFrom, moveTo, moveCount);
+
+ QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+ QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
+ expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+ QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+
+ // check the target and displaced items were animated
+ model_targetItems_transitionVia.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
+ model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+ // check attached properties
+ matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
+ matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
+ if (expectedDisplacedIndexes.isValid()) {
+ // adjust expectedDisplacedIndexes to their final values after the move
+ QList<int> displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, moveFrom, moveTo, moveCount);
+ matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+ matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
+ }
+
+ QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+ int firstVisibleIndex = -1;
+ for (int i=0; i<items.count(); i++) {
+ if (items[i]->y() >= contentY) {
+ QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+ firstVisibleIndex = e.evaluate().toInt();
+ break;
+ }
+ }
+ QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+ // verify all items moved to the correct final positions
+ int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+ for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
+ name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ delete canvas;
+ delete testObject;
+}
+
+void tst_QQuickListView::moveTransitions_data()
+{
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<qreal>("contentY");
+ QTest::addColumn<qreal>("itemsOffsetAfterMove");
+ QTest::addColumn<int>("moveFrom");
+ QTest::addColumn<int>("moveTo");
+ QTest::addColumn<int>("moveCount");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ // when removing from above the visible, all items shift down depending on how many
+ // items have been removed from above the visible
+ QTest::newRow("move from above view, outside visible items, move 1") << 30 << 4*20.0 << 20.0
+ << 1 << 10 << 1 << ListRange(11, 15+4);
+ QTest::newRow("move from above view, outside visible items, move 1 (first item)") << 30 << 4*20.0 << 20.0
+ << 0 << 10 << 1 << ListRange(11, 15+4);
+ QTest::newRow("move from above view, outside visible items, move multiple") << 30 << 4*20.0 << 2*20.0
+ << 1 << 10 << 2 << ListRange(12, 15+4);
+ QTest::newRow("move from above view, outside visible items, move multiple (first item)") << 30 << 4*20.0 << 3*20.0
+ << 0 << 10 << 3 << ListRange(13, 15+4);
+ QTest::newRow("move from above view, mix of visible/non-visible") << 30 << 4*20.0 << 3*20.0
+ << 1 << 10 << 5 << ListRange(6, 14) + ListRange(15, 15+4);
+ QTest::newRow("move from above view, mix of visible/non-visible (move first)") << 30 << 4*20.0 << 4*20.0
+ << 0 << 10 << 5 << ListRange(5, 14) + ListRange(15, 15+4);
+
+ QTest::newRow("move within view, move 1 down") << 30 << 0.0 << 0.0
+ << 1 << 10 << 1 << ListRange(2, 10);
+ QTest::newRow("move within view, move 1 down, move first item") << 30 << 0.0 << 0.0
+ << 0 << 10 << 1 << ListRange(1, 10);
+ QTest::newRow("move within view, move 1 down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 0+4 << 10+4 << 1 << ListRange(1+4, 10+4);
+ QTest::newRow("move within view, move 1 down, to last item") << 30 << 0.0 << 0.0
+ << 10 << 15 << 1 << ListRange(11, 15);
+ QTest::newRow("move within view, move first->last") << 30 << 0.0 << 0.0
+ << 0 << 15 << 1 << ListRange(1, 15);
+
+ QTest::newRow("move within view, move multiple down") << 30 << 0.0 << 0.0
+ << 1 << 10 << 3 << ListRange(4, 12);
+ QTest::newRow("move within view, move multiple down, move first item") << 30 << 0.0 << 0.0
+ << 0 << 10 << 3 << ListRange(3, 12);
+ QTest::newRow("move within view, move multiple down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 0+4 << 10+4 << 3 << ListRange(3+4, 12+4);
+ QTest::newRow("move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0
+ << 5 << 13 << 3 << ListRange(8, 15);
+ QTest::newRow("move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0
+ << 0 << 13 << 3 << ListRange(3, 15);
+
+ QTest::newRow("move within view, move 1 up") << 30 << 0.0 << 0.0
+ << 10 << 1 << 1 << ListRange(1, 9);
+ QTest::newRow("move within view, move 1 up, move to first index") << 30 << 0.0 << 0.0
+ << 10 << 0 << 1 << ListRange(0, 9);
+ QTest::newRow("move within view, move 1 up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
+ QTest::newRow("move within view, move 1 up, move to first index, contentY not on item border") << 30 << 4*20.0 - 10 << 0.0
+ << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
+ QTest::newRow("move within view, move 1 up, move last item") << 30 << 0.0 << 0.0
+ << 15 << 10 << 1 << ListRange(10, 14);
+ QTest::newRow("move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0
+ << 15 << 0 << 1 << ListRange(0, 14);
+
+ QTest::newRow("move within view, move multiple up") << 30 << 0.0 << 0.0
+ << 10 << 1 << 3 << ListRange(1, 9);
+ QTest::newRow("move within view, move multiple up, move to first index") << 30 << 0.0 << 0.0
+ << 10 << 0 << 3 << ListRange(0, 9);
+ QTest::newRow("move within view, move multiple up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 10+4 << 0+4 << 3 << ListRange(0+4, 9+4);
+ QTest::newRow("move within view, move multiple up, move last item") << 30 << 0.0 << 0.0
+ << 13 << 5 << 3 << ListRange(5, 12);
+ QTest::newRow("move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0
+ << 13 << 0 << 3 << ListRange(0, 12);
+
+ QTest::newRow("move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0
+ << 20 << 0 << 1 << ListRange(0, 15);
+ QTest::newRow("move from below view, move 1 up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 25 << 4 << 1 << ListRange(0+4, 15+4);
+ QTest::newRow("move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0
+ << 20 << 0 << 3 << ListRange(0, 15);
+ QTest::newRow("move from below view, move multiple up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 25 << 4 << 3 << ListRange(0+4, 15+4);
+
+ QTest::newRow("move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0
+ << 20 << 15 << 1 << ListRange(15, 15);
+ QTest::newRow("move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 25 << 15+4 << 1 << ListRange(15+4, 15+4);
+ QTest::newRow("move from below view, move multiple up, move to to bottom") << 30 << 0.0 << 0.0
+ << 20 << 15 << 3 << ListRange(15, 15);
+ QTest::newRow("move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
+ << 25 << 15+4 << 3 << ListRange(15+4, 15+4);
+}
+
+void tst_QQuickListView::removeTransitions()
+{
+ QFETCH(int, initialItemCount);
+ QFETCH(bool, shouldAnimateTargets);
+ QFETCH(qreal, contentY);
+ QFETCH(int, removalIndex);
+ QFETCH(int, removalCount);
+ QFETCH(ListRange, expectedDisplacedIndexes);
+
+ // added items should end here
+ QPointF targetItems_transitionTo(-50, -50);
+
+ // displaced items should pass through this points
+ QPointF displacedItems_transitionVia(100, 100);
+
+ QaimModel model;
+ for (int i = 0; i < initialItemCount; i++)
+ model.addItem("Original item" + QString::number(i), "");
+ QaimModel model_targetItems_transitionTo;
+ QaimModel model_displacedItems_transitionVia;
+
+ QQuickView *canvas = createView();
+ QDeclarativeContext *ctxt = canvas->rootContext();
+ TestObject *testObject = new TestObject;
+ ctxt->setContextProperty("testModel", &model);
+ ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo);
+ ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
+ ctxt->setContextProperty("targetItems_transitionTo", targetItems_transitionTo);
+ ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
+ ctxt->setContextProperty("testObject", testObject);
+ canvas->setSource(testFileUrl("removeTransitions.qml"));
+ canvas->show();
+
+ QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+ QTRY_VERIFY(listview != 0);
+ QQuickItem *contentItem = listview->contentItem();
+ QVERIFY(contentItem != 0);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
+ if (contentY != 0) {
+ listview->setContentY(contentY);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ }
+
+ QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
+
+ // only target items that are visible should be animated
+ QList<QPair<QString, QString> > expectedTargetData;
+ QList<int> targetIndexes;
+ if (shouldAnimateTargets) {
+ for (int i=removalIndex; i<removalIndex+removalCount; i++) {
+ if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {
+ expectedTargetData << qMakePair(model.name(i), model.number(i));
+ targetIndexes << i;
+ }
+ }
+ QVERIFY(expectedTargetData.count() > 0);
+ }
+
+ // calculate targetItems and expectedTargets before model changes
+ QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
+ QVariantMap expectedTargets;
+ for (int i=0; i<targetIndexes.count(); i++)
+ expectedTargets[model.name(targetIndexes[i])] = targetIndexes[i];
+
+ // start animation
+ model.removeItems(removalIndex, removalCount);
+ QTRY_COMPARE(model.count(), listview->count());
+
+ if (shouldAnimateTargets) {
+ QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
+ QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
+ expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
+
+ // check the target and displaced items were animated
+ model_targetItems_transitionTo.matchAgainst(expectedTargetData, "wasn't animated to target 'to' pos", "shouldn't have been animated to target 'to' pos");
+ model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
+
+ // check attached properties
+ QCOMPARE(listview->property("targetTrans_items").toMap(), expectedTargets);
+ matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
+ if (expectedDisplacedIndexes.isValid()) {
+ // adjust expectedDisplacedIndexes to their final values after the move
+ QList<int> displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, removalIndex, removalCount);
+ matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
+ matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
+ matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
+ }
+ } else {
+ QTRY_COMPARE(model_targetItems_transitionTo.count(), 0);
+ QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
+ }
+
+ QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+ int firstVisibleIndex = -1;
+ int itemCount = items.count();
+
+ for (int i=0; i<items.count(); i++) {
+ QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+ int index = e.evaluate().toInt();
+ if (firstVisibleIndex < 0 && items[i]->y() >= contentY)
+ firstVisibleIndex = index;
+ if (index < 0)
+ itemCount--; // exclude deleted items
+ }
+ QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+ // verify all items moved to the correct final positions
+ for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QCOMPARE(item->x(), 0.0);
+ QCOMPARE(item->y(), contentY + (i-firstVisibleIndex) * 20.0);
+ QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ delete canvas;
+ delete testObject;
+}
+
+void tst_QQuickListView::removeTransitions_data()
+{
+ QTest::addColumn<int>("initialItemCount");
+ QTest::addColumn<qreal>("contentY");
+ QTest::addColumn<bool>("shouldAnimateTargets");
+ QTest::addColumn<int>("removalIndex");
+ QTest::addColumn<int>("removalCount");
+ QTest::addColumn<ListRange>("expectedDisplacedIndexes");
+
+ // All items that are visible following the remove operation should be animated.
+ // Remove targets that are outside of the view should not be animated.
+
+ QTest::newRow("remove 1 before start")
+ << 30 << 20.0 * 3 << false
+ << 2 << 1 << ListRange();
+ QTest::newRow("remove multiple, all before start")
+ << 30 << 20.0 * 3 << false
+ << 0 << 3 << ListRange();
+ QTest::newRow("remove mix of before and after start")
+ << 30 << 20.0 * 3 << true
+ << 2 << 3 << ListRange(5, 20); // 5-20 are visible after the remove
+
+ QTest::newRow("remove 1 from start")
+ << 30 << 0.0 << true
+ << 0 << 1 << ListRange(1, 16); // 1-16 are visible after the remove
+ QTest::newRow("remove multiple from start")
+ << 30 << 0.0 << true
+ << 0 << 3 << ListRange(3, 18); // 3-18 are visible after the remove
+ QTest::newRow("remove 1 from start, content y not 0")
+ << 30 << 20.0 * 2 << true // first visible is index 2, so translate the displaced indexes by 2
+ << 2 << 1 << ListRange(1 + 2, 16 + 2);
+ QTest::newRow("remove multiple from start, content y not 0")
+ << 30 << 20.0 * 2 << true // first visible is index 2
+ << 2 << 3 << ListRange(3 + 2, 18 + 2);
+
+ QTest::newRow("remove 1 from middle")
+ << 30 << 0.0 << true
+ << 5 << 1 << ListRange(6, 16);
+ QTest::newRow("remove multiple from middle")
+ << 30 << 0.0 << true
+ << 5 << 3 << ListRange(8, 18);
+
+
+ QTest::newRow("remove 1 from bottom")
+ << 30 << 0.0 << true
+ << 15 << 1 << ListRange(16, 16);
+
+ // remove 15, 16, 17
+ // 15 will animate as the target item, 16 & 17 won't be animated since they are outside
+ // the view, and 18 will be animated as the displaced item to replace the last item
+ QTest::newRow("remove multiple from bottom")
+ << 30 << 0.0 << true
+ << 15 << 3 << ListRange(18, 18);
+
+ QTest::newRow("remove 1 from bottom, content y not 0")
+ << 30 << 20.0 * 2 << true
+ << 15 + 2 << 1 << ListRange(16 + 2, 16 + 2);
+ QTest::newRow("remove multiple from bottom, content y not 0")
+ << 30 << 20.0 * 2 << true
+ << 15 + 2 << 3 << ListRange(18 + 2, 18 + 2);
+
+
+ QTest::newRow("remove 1 after end")
+ << 30 << 0.0 << false
+ << 17 << 1 << ListRange();
+ QTest::newRow("remove multiple after end")
+ << 30 << 0.0 << false
+ << 17 << 3 << ListRange();
+}
+
+void tst_QQuickListView::multipleTransitions()
+{
+ // Tests that if you interrupt a transition in progress with another action that
+ // cancels the previous transition, the resulting items are still placed correctly.
+
+ QFETCH(int, initialCount);
+ QFETCH(qreal, contentY);
+ QFETCH(QList<ListChange>, changes);
+
+ // add transitions on the left, moves on the right
+ QPointF addTargets_transitionFrom(-50, -50);
+ QPointF addDisplaced_transitionFrom(-50, 50);
+ QPointF moveTargets_transitionFrom(50, -50);
+ QPointF moveDisplaced_transitionFrom(50, 50);
+
+ QmlListModel model;
+ for (int i = 0; i < initialCount; i++)
+ model.addItem("Original item" + QString::number(i), "");
+
+ QQuickView *canvas = createView();
+ QDeclarativeContext *ctxt = canvas->rootContext();
+ TestObject *testObject = new TestObject;
+ ctxt->setContextProperty("testModel", &model);
+ ctxt->setContextProperty("testObject", testObject);
+ ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom);
+ ctxt->setContextProperty("addDisplaced_transitionFrom", addDisplaced_transitionFrom);
+ ctxt->setContextProperty("moveTargets_transitionFrom", moveTargets_transitionFrom);
+ ctxt->setContextProperty("moveDisplaced_transitionFrom", moveDisplaced_transitionFrom);
+ canvas->setSource(testFileUrl("multipleTransitions.qml"));
+ canvas->show();
+
+ QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
+ QTRY_VERIFY(listview != 0);
+ QQuickItem *contentItem = listview->contentItem();
+ QVERIFY(contentItem != 0);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
+ int timeBetweenActions = canvas->rootObject()->property("timeBetweenActions").toInt();
+
+ QList<QPair<QString, QString> > targetItems;
+ for (int i=0; i<changes.count(); i++) {
+ switch (changes[i].type) {
+ case ListChange::Inserted:
+ {
+ for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+ targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
+ model.insertItems(changes[i].index, targetItems);
+ QTRY_COMPARE(model.count(), listview->count());
+ QTRY_VERIFY(listview->property("runningAddTargets").toBool());
+ QTRY_VERIFY(listview->property("runningAddDisplaced").toBool());
+ if (i == changes.count() - 1) {
+ QTRY_VERIFY(!listview->property("runningAddTargets").toBool());
+ QTRY_VERIFY(!listview->property("runningAddDisplaced").toBool());
+ } else {
+ QTest::qWait(timeBetweenActions);
+ }
+ break;
+ }
+ case ListChange::Removed:
+ for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+ targetItems << qMakePair(model.name(i), model.number(i));
+ model.removeItems(changes[i].index, changes[i].count);
+ QTRY_COMPARE(model.count(), listview->count());
+ QTRY_VERIFY(listview->property("runningRemoveTargets").toBool());
+ QTRY_VERIFY(listview->property("runningRemoveDisplaced").toBool());
+ if (i == changes.count() - 1) {
+ QTRY_VERIFY(!listview->property("runningRemoveTargets").toBool());
+ QTRY_VERIFY(!listview->property("runningRemoveDisplaced").toBool());
+ } else {
+ QTest::qWait(timeBetweenActions);
+ }
+ break;
+ case ListChange::Moved:
+ for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
+ targetItems << qMakePair(model.name(i), model.number(i));
+ model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+ QTRY_VERIFY(listview->property("runningMoveTargets").toBool());
+ QTRY_VERIFY(listview->property("runningMoveDisplaced").toBool());
+ if (i == changes.count() - 1) {
+ QTRY_VERIFY(!listview->property("runningMoveTargets").toBool());
+ QTRY_VERIFY(!listview->property("runningMoveDisplaced").toBool());
+ } else {
+ QTest::qWait(timeBetweenActions);
+ }
+ break;
+ case ListChange::SetCurrent:
+ listview->setCurrentIndex(changes[i].index);
+ break;
+ case ListChange::SetContentY:
+ listview->setContentY(changes[i].pos);
+ QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+ break;
+ }
+ }
+ QCOMPARE(listview->count(), model.count());
+
+ QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
+ int firstVisibleIndex = -1;
+ for (int i=0; i<items.count(); i++) {
+ if (items[i]->y() >= contentY) {
+ QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
+ firstVisibleIndex = e.evaluate().toInt();
+ break;
+ }
+ }
+ QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
+
+ // verify all items moved to the correct final positions
+ int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
+ for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
+ QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
+ QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
+ QTRY_COMPARE(item->x(), 0.0);
+ QTRY_COMPARE(item->y(), i*20.0);
+ QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
+ QVERIFY(name != 0);
+ QTRY_COMPARE(name->text(), model.name(i));
+ }
+
+ delete canvas;
+ delete testObject;
+}
+
+void tst_QQuickListView::multipleTransitions_data()
+{
+ QTest::addColumn<int>("initialCount");
+ QTest::addColumn<qreal>("contentY");
+ QTest::addColumn<QList<ListChange> >("changes");
+
+ // the added item and displaced items should move to final dest correctly
+ QTest::newRow("add item, then move it immediately") << 10 << 0.0 << (QList<ListChange>()
+ << ListChange::insert(0, 1)
+ << ListChange::move(0, 3, 1)
+ );
+
+ // items affected by the add should change from move to add transition
+ QTest::newRow("move, then insert item before the moved item") << 20 << 0.0 << (QList<ListChange>()
+ << ListChange::move(1, 10, 3)
+ << ListChange::insert(0, 1)
+ );
+
+ // items should be placed correctly if you trigger a transition then refill for that index
+ QTest::newRow("add at 0, flick down, flick back to top and add at 0 again") << 20 << 0.0 << (QList<ListChange>()
+ << ListChange::insert(0, 1)
+ << ListChange::setContentY(80.0)
+ << ListChange::setContentY(0.0)
+ << ListChange::insert(0, 1)
+ );
+}
+
+QList<int> tst_QQuickListView::toIntList(const QVariantList &list)
+{
+ QList<int> ret;
+ bool ok = true;
+ for (int i=0; i<list.count(); i++) {
+ ret << list[i].toInt(&ok);
+ if (!ok)
+ qWarning() << "tst_QQuickListView::toIntList(): not a number:" << list[i];
+ }
+
+ return ret;
+}
+
+void tst_QQuickListView::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
+{
+ for (int i=0; i<indexLists.count(); i++) {
+ QSet<int> current = indexLists[i].value<QList<int> >().toSet();
+ if (current != expectedIndexes.toSet())
+ qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
+ QCOMPARE(current, expectedIndexes.toSet());
+ }
+}
+
+void tst_QQuickListView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
+{
+ for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
+ QVERIFY(it.value().type() == QVariant::Int);
+ QString name = it.key();
+ int itemIndex = it.value().toInt();
+ QVERIFY2(expectedIndexes.contains(itemIndex), QTest::toString(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
+ if (model.name(itemIndex) != name)
+ qDebug() << itemIndex;
+ QCOMPARE(model.name(itemIndex), name);
+ }
+ QCOMPARE(items.count(), expectedIndexes.count());
+}
+
+void tst_QQuickListView::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
+{
+ for (int i=0; i<itemLists.count(); i++) {
+ QVERIFY(itemLists[i].type() == QVariant::List);
+ QVariantList current = itemLists[i].toList();
+ for (int j=0; j<current.count(); j++) {
+ QQuickItem *o = qobject_cast<QQuickItem*>(current[j].value<QObject*>());
+ QVERIFY2(o, QTest::toString(QString("Invalid actual item at %1").arg(j)));
+ QVERIFY2(expectedItems.contains(o), QTest::toString(QString("Cannot match item %1").arg(j)));
+ }
+ QCOMPARE(current.count(), expectedItems.count());
+ }
+}
+
QTEST_MAIN(tst_QQuickListView)