From 2df9abf7047afbe20b19d156ac37b58d9b047575 Mon Sep 17 00:00:00 2001 From: Bea Lam Date: Thu, 9 Feb 2012 17:59:44 +1000 Subject: Built-in transition support for ListView & GridView ListView and GridView can now be assigned transitions to be run when: - Populating the view (when initially setting the model / resetting) - Adding items - Removing items - Moving items The ViewTransition attached object can be used from within a transition declaration to access various information about the items that are being transitioned. Task-number: QTBUG-21504 Change-Id: Ie5c75ea511c8b15acc3f06fccf19abe34d3677f9 Reviewed-by: Martin Jones --- .../qquickgridview/data/addTransitions.qml | 129 +++ .../qquickgridview/data/moveTransitions.qml | 143 +++ .../qquickgridview/data/multipleTransitions.qml | 123 +++ .../qquickgridview/data/populateTransitions.qml | 103 +++ .../qquickgridview/data/removeTransitions.qml | 146 +++ .../qtquick2/qquickgridview/tst_qquickgridview.cpp | 977 ++++++++++++++++++++- .../qquicklistview/data/addTransitions.qml | 134 +++ .../qquicklistview/data/moveTransitions.qml | 141 +++ .../qquicklistview/data/multipleTransitions.qml | 121 +++ .../qquicklistview/data/populateTransitions.qml | 102 +++ .../qquicklistview/data/removeTransitions.qml | 144 +++ .../qtquick2/qquicklistview/tst_qquicklistview.cpp | 950 +++++++++++++++++++- tests/auto/qtquick2/shared/viewtestutil.cpp | 148 ++++ tests/auto/qtquick2/shared/viewtestutil.h | 48 +- tests/auto/qtquick2/shared/visualtestutil.h | 10 + 15 files changed, 3407 insertions(+), 12 deletions(-) create mode 100644 tests/auto/qtquick2/qquickgridview/data/addTransitions.qml create mode 100644 tests/auto/qtquick2/qquickgridview/data/moveTransitions.qml create mode 100644 tests/auto/qtquick2/qquickgridview/data/multipleTransitions.qml create mode 100644 tests/auto/qtquick2/qquickgridview/data/populateTransitions.qml create mode 100644 tests/auto/qtquick2/qquickgridview/data/removeTransitions.qml create mode 100644 tests/auto/qtquick2/qquicklistview/data/addTransitions.qml create mode 100644 tests/auto/qtquick2/qquicklistview/data/moveTransitions.qml create mode 100644 tests/auto/qtquick2/qquicklistview/data/multipleTransitions.qml create mode 100644 tests/auto/qtquick2/qquicklistview/data/populateTransitions.qml create mode 100644 tests/auto/qtquick2/qquicklistview/data/removeTransitions.qml (limited to 'tests') diff --git a/tests/auto/qtquick2/qquickgridview/data/addTransitions.qml b/tests/auto/qtquick2/qquickgridview/data/addTransitions.qml new file mode 100644 index 0000000000..faea02a50d --- /dev/null +++ b/tests/auto/qtquick2/qquickgridview/data/addTransitions.qml @@ -0,0 +1,129 @@ +import QtQuick 2.0 + +Rectangle { + id: root + width: 550 + height: 600 + + property int duration: 10 + property int count: grid.count + + Component { + id: myDelegate + + Rectangle { + id: wrapper + + property string nameData: name + + objectName: "wrapper" + width: 80 + height: 60 + border.width: 1 + Column { + Text { text: index } + Text { + text: wrapper.x + ", " + wrapper.y + } + Text { + id: textName + objectName: "textName" + text: name + } + } + color: GridView.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, "") + } + } + } + + GridView { + id: grid + + 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: "grid" + width: 240 + height: 320 + cellWidth: 80 + cellHeight: 60 + anchors.centerIn: parent + model: testModel + delegate: myDelegate + + // for QDeclarativeListProperty types + function copyList(propList) { + var temp = new Array() + for (var i=0; i #include #include +#include #include +#include #include "../../shared/util.h" #include "../shared/viewtestutil.h" #include "../shared/visualtestutil.h" @@ -127,6 +129,23 @@ private slots: void cacheBuffer(); 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: + QList toIntList(const QVariantList &list); + void matchIndexLists(const QVariantList &indexLists, const QList &expectedIndexes); + void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList &expectedIndexes); + void matchItemLists(const QVariantList &itemLists, const QList &expectedItems); }; tst_QQuickGridView::tst_QQuickGridView() @@ -500,13 +519,15 @@ void tst_QQuickGridView::insertBeforeVisible() QTRY_VERIFY(contentItem != 0); gridview->setCacheBuffer(cacheBuffer); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); // trigger a refill (not just setting contentY) so that the visibleItems grid is updated int firstVisibleIndex = 12; // move to an index where the top item is not visible gridview->setContentY(firstVisibleIndex/3 * 60.0); gridview->setCurrentIndex(firstVisibleIndex); - qApp->processEvents(); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + QTRY_COMPARE(gridview->currentIndex(), firstVisibleIndex); QQuickItem *item = findItem(contentItem, "wrapper", firstVisibleIndex); QVERIFY(item); @@ -521,6 +542,7 @@ void tst_QQuickGridView::insertBeforeVisible() // now, moving to the top of the view should position the inserted items correctly int itemsOffsetAfterMove = (insertCount / 3) * -60.0; gridview->setCurrentIndex(0); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); QTRY_COMPARE(gridview->currentIndex(), 0); QTRY_COMPARE(gridview->contentY(), 0.0 + itemsOffsetAfterMove); @@ -1283,12 +1305,19 @@ void tst_QQuickGridView::multipleChanges() } case ListChange::Removed: model.removeItems(changes[i].index, changes[i].count); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); break; case ListChange::Moved: model.moveItems(changes[i].index, changes[i].to, changes[i].count); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); break; case ListChange::SetCurrent: gridview->setCurrentIndex(changes[i].index); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + break; + case ListChange::SetContentY: + gridview->setContentY(changes[i].pos); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); break; } } @@ -2005,10 +2034,10 @@ void tst_QQuickGridView::componentChanges() QTRY_VERIFY(gridView); QDeclarativeComponent component(canvas->engine()); - component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile("")); + component.setData("import QtQuick 1.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile("")); QDeclarativeComponent delegateComponent(canvas->engine()); - delegateComponent.setData("import QtQuick 2.0; Text { text: 'Name: ' + name }", QUrl::fromLocalFile("")); + delegateComponent.setData("import QtQuick 1.0; Text { text: 'Name: ' + name }", QUrl::fromLocalFile("")); QSignalSpy highlightSpy(gridView, SIGNAL(highlightChanged())); QSignalSpy delegateSpy(gridView, SIGNAL(delegateChanged())); @@ -3769,6 +3798,898 @@ void tst_QQuickGridView::unaligned() delete canvas; } +void tst_QQuickGridView::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("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(); + + QQuickGridView *gridview = findItem(canvas->rootObject(), "grid"); + QVERIFY(gridview); + QQuickItem *contentItem = gridview->contentItem(); + QVERIFY(contentItem); + + if (staticallyPopulate || dynamicallyPopulate) { + // check the populate transition is run + if (usePopulateTransition) { + QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), 19); + } else { + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), 0); + } + QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 0); + } else { + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + } + + int itemCount = findItems(contentItem, "wrapper").count(); + if (usePopulateTransition) + QCOMPARE(itemCount, gridview->property("countPopulateTransitions").toInt()); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), (i/3)*60.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + // add an item and check this is done with add transition, not populate + model.insertItem(0, "another item", ""); + QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 1); + QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), + (usePopulateTransition && (staticallyPopulate || dynamicallyPopulate)) ? 19 : 0); + + // clear the model + canvas->rootContext()->setContextProperty("testModel", QVariant()); + QTRY_COMPARE(gridview->count(), 0); + QTRY_COMPARE(findItems(contentItem, "wrapper").count(), 0); + gridview->setProperty("countPopulateTransitions", 0); + gridview->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(QQuickItemPrivate::get(gridview)->polishScheduled, false); + + QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 19 : 0); + QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 0); + + itemCount = findItems(contentItem, "wrapper").count(); + if (usePopulateTransition) + QCOMPARE(itemCount, gridview->property("countPopulateTransitions").toInt()); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), (i/3)*60.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + // reset model and check populate transition is run again + gridview->setProperty("countPopulateTransitions", 0); + gridview->setProperty("countAddTransitions", 0); + model.reset(); + QTRY_COMPARE(gridview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 19 : 0); + QTRY_COMPARE(gridview->property("countAddTransitions").toInt(), 0); + + itemCount = findItems(contentItem, "wrapper").count(); + if (usePopulateTransition) + QCOMPARE(itemCount, gridview->property("countPopulateTransitions").toInt()); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), (i/3)*60.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; +} + +void tst_QQuickGridView::populateTransitions_data() +{ + QTest::addColumn("staticallyPopulate"); + QTest::addColumn("dynamicallyPopulate"); + QTest::addColumn("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_QQuickGridView::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(); + 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); + canvas->setSource(testFileUrl("addTransitions.qml")); + canvas->show(); + + QQuickGridView *gridview = findItem(canvas->rootObject(), "grid"); + QTRY_VERIFY(gridview != 0); + QQuickItem *contentItem = gridview->contentItem(); + QVERIFY(contentItem != 0); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + + if (contentY != 0) { + gridview->setContentY(contentY); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + } + + QList > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model); + + // only target items that will become visible should be animated + QList > newData; + QList > expectedTargetData; + QList targetIndexes; + if (shouldAnimateTargets) { + for (int i=insertionIndex; i= (contentY / 60)*3 && i < qCeil((contentY + gridview->height()) / 60.0)*3) { + expectedTargetData << newData.last(); + targetIndexes << i; + } + } + QVERIFY(expectedTargetData.count() > 0); + } + + // start animation + if (!newData.isEmpty()) { + model.insertItems(insertionIndex, newData); + QTRY_COMPARE(model.count(), gridview->count()); + } + + QList targetItems = findItems(contentItem, "wrapper", targetIndexes); + + if (shouldAnimateTargets) { + QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count()); + QTRY_COMPARE(gridview->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(gridview->property("targetTrans_items").toMap(), model, targetIndexes); + matchIndexLists(gridview->property("targetTrans_targetIndexes").toList(), targetIndexes); + matchItemLists(gridview->property("targetTrans_targetItems").toList(), targetItems); + if (expectedDisplacedIndexes.isValid()) { + // adjust expectedDisplacedIndexes to their final values after the move + QList displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount); + matchItemsAndIndexes(gridview->property("displacedTrans_items").toMap(), model, displacedIndexes); + matchIndexLists(gridview->property("displacedTrans_targetIndexes").toList(), targetIndexes); + matchItemLists(gridview->property("displacedTrans_targetItems").toList(), targetItems); + } + } else { + QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0); + QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0); + } + + QList items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + for (int i=0; iy() >= 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(contentItem, "wrapper").count(); + for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), (i/3)*60.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QCOMPARE(name->text(), model.name(i)); + } + + delete canvas; +} + +void tst_QQuickGridView::addTransitions_data() +{ + QTest::addColumn("initialItemCount"); + QTest::addColumn("contentY"); + QTest::addColumn("shouldAnimateTargets"); + QTest::addColumn("insertionIndex"); + QTest::addColumn("insertionCount"); + QTest::addColumn("expectedDisplacedIndexes"); + + // if inserting a full row before visible index, items don't 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 (< 1 row), just before start") + << 30 << 100.0 << false + << 0 << 2 << 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, 17); + QTest::newRow("insert multiple at start") + << 30 << 0.0 << true + << 0 << 3 << ListRange(0, 17); + QTest::newRow("insert multiple (> 1 row) at start") + << 30 << 0.0 << true + << 0 << 5 << ListRange(0, 17); + QTest::newRow("insert 1 at start, content y not 0") + << 30 << 60.0 << true // first visible is index 3 + << 3 << 1 << ListRange(0 + 3, 17 + 3); + QTest::newRow("insert multiple at start, content y not 0") + << 30 << 60.0 << true // first visible is index 3 + << 3 << 3 << ListRange(0 + 3, 17 + 3); + QTest::newRow("insert multiple (> 1 row) at start, content y not 0") + << 30 << 60.0 << true // first visible is index 3 + << 3 << 5 << ListRange(0 + 3, 17 + 3); + + QTest::newRow("insert 1 at start, to empty grid") + << 0 << 0.0 << true + << 0 << 1 << ListRange(); + QTest::newRow("insert multiple at start, to empty grid") + << 0 << 0.0 << true + << 0 << 3 << ListRange(); + + QTest::newRow("insert 1 at middle") + << 30 << 0.0 << true + << 7 << 1 << ListRange(7, 17); + QTest::newRow("insert multiple at middle") + << 30 << 0.0 << true + << 7 << 3 << ListRange(7, 17); + QTest::newRow("insert multiple (> 1 row) at middle") + << 30 << 0.0 << true + << 7 << 5 << ListRange(7, 17); + + QTest::newRow("insert 1 at bottom") + << 30 << 0.0 << true + << 17 << 1 << ListRange(17, 17); + QTest::newRow("insert multiple at bottom") + << 30 << 0.0 << true + << 17 << 3 << ListRange(17, 17); + QTest::newRow("insert 1 at bottom, content y not 0") + << 30 << 20.0 * 3 << true + << 17 + 3 << 1 << ListRange(17 + 3, 17 + 3); + QTest::newRow("insert multiple at bottom, content y not 0") + << 30 << 20.0 * 3 << true + << 17 + 3 << 3 << ListRange(17 + 3, 17 + 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 + << 18 << 1 << ListRange(); + QTest::newRow("insert multiple after end") + << 30 << 0.0 << false + << 18 << 3 << ListRange(); +} + +void tst_QQuickGridView::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(); + 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); + canvas->setSource(testFileUrl("moveTransitions.qml")); + canvas->show(); + + QQuickGridView *gridview = findItem(canvas->rootObject(), "grid"); + QTRY_VERIFY(gridview != 0); + QQuickItem *contentItem = gridview->contentItem(); + QVERIFY(contentItem != 0); + QQuickText *name; + + if (contentY != 0) { + gridview->setContentY(contentY); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + } + + QList > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model); + + // Items moving to *or* from visible positions should be animated. + // Otherwise, they should not be animated. + QList > expectedTargetData; + QList targetIndexes; + for (int i=moveFrom; iheight()) / 60.0)*3) - 1; + if ((i >= firstVisibleIndex && i <= lastVisibleIndex) + || (toIndex >= firstVisibleIndex && toIndex <= lastVisibleIndex)) { + 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(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count()); + QTRY_COMPARE(gridview->property("displaceTransitionsDone").toInt(), + expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0); + + QList targetItems = findItems(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(gridview->property("targetTrans_items").toMap(), model, targetIndexes); + matchIndexLists(gridview->property("targetTrans_targetIndexes").toList(), targetIndexes); + matchItemLists(gridview->property("targetTrans_targetItems").toList(), targetItems); + if (expectedDisplacedIndexes.isValid()) { + // adjust expectedDisplacedIndexes to their final values after the move + QList displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, moveFrom, moveTo, moveCount); + matchItemsAndIndexes(gridview->property("displacedTrans_items").toMap(), model, displacedIndexes); + matchIndexLists(gridview->property("displacedTrans_targetIndexes").toList(), targetIndexes); + matchItemLists(gridview->property("displacedTrans_targetItems").toList(), targetItems); + } + + QList items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + for (int i=0; iy() >= 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(contentItem, "wrapper").count(); + for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), (i/3)*60.0 + itemsOffsetAfterMove); + name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; +} + +void tst_QQuickGridView::moveTransitions_data() +{ + QTest::addColumn("initialItemCount"); + QTest::addColumn("contentY"); + QTest::addColumn("itemsOffsetAfterMove"); + QTest::addColumn("moveFrom"); + QTest::addColumn("moveTo"); + QTest::addColumn("moveCount"); + QTest::addColumn("expectedDisplacedIndexes"); + + QTest::newRow("move from above view, outside visible items, move 1") << 30 << 120.0 << 0.0 + << 1 << 10 << 1 << ListRange(6, 10); + QTest::newRow("move from above view, outside visible items, move 1 (first item)") << 30 << 120.0 << 0.0 + << 0 << 10 << 1 << ListRange(6, 10); + QTest::newRow("move from above view, outside visible items, move multiple") << 30 << 120.0 << 60.0 + << 1 << 10 << 3 << ListRange(13, 23); + QTest::newRow("move from above view, mix of visible/non-visible") << 30 << 120.0 << 60.0 + << 1 << 10 << 6 << (ListRange(7, 15) + ListRange(16, 23)); + QTest::newRow("move from above view, mix of visible/non-visible (move first)") << 30 << 120.0 << 120.0 + << 0 << 10 << 6 << ListRange(16, 23); + + 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 << 120.0 << 0.0 + << 0+6 << 10+6 << 1 << ListRange(1+6, 10+6); + QTest::newRow("move within view, move 1 down, to last item") << 30 << 0.0 << 0.0 + << 10 << 17 << 1 << ListRange(11, 17); + QTest::newRow("move within view, move first->last") << 30 << 0.0 << 0.0 + << 0 << 17 << 1 << ListRange(1, 17); + + 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 << 60.0 << 0.0 + << 0+3 << 10+3 << 3 << ListRange(3+3, 12+3); + QTest::newRow("move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0 + << 5 << 15 << 3 << ListRange(8, 17); + QTest::newRow("move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0 + << 0 << 15 << 3 << ListRange(3, 17); + + 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 << 120.0 << 0.0 + << 10+6 << 0+6 << 1 << ListRange(0+6, 9+6); + QTest::newRow("move within view, move 1 up, move to first index, contentY not on item border") << 30 << 80.0 << 0.0 + << 10+3 << 0+3 << 1 << ListRange(0+3, 9+3); + QTest::newRow("move within view, move 1 up, move last item") << 30 << 0.0 << 0.0 + << 17 << 10 << 1 << ListRange(10, 16); + QTest::newRow("move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0 + << 17 << 0 << 1 << ListRange(0, 16); + + 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 (> 1 row) up") << 30 << 0.0 << 0.0 + << 10 << 1 << 5 << 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 << 60.0 << 0.0 + << 10+3 << 0+3 << 3 << ListRange(0+3, 9+3); + QTest::newRow("move within view, move multiple up (> 1 row), move to first index, contentY not on border") << 30 << 80.0 << 0.0 + << 10+3 << 0+3 << 5 << ListRange(0+3, 9+3); + QTest::newRow("move within view, move multiple up, move last item") << 30 << 0.0 << 0.0 + << 15 << 5 << 3 << ListRange(5, 14); + QTest::newRow("move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0 + << 15 << 0 << 3 << ListRange(0, 14); + + QTest::newRow("move from below view, move 1 up") << 30 << 0.0 << 0.0 + << 20 << 5 << 1 << ListRange(5, 17); + QTest::newRow("move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0 + << 20 << 0 << 1 << ListRange(0, 17); + QTest::newRow("move from below view, move 1 up, move to top, contentY not 0") << 30 << 60.0 << 0.0 + << 25 << 3 << 1 << ListRange(0+3, 17+3); + QTest::newRow("move from below view, move multiple (> 1 row) up") << 30 << 0.0 << 0.0 + << 20 << 5 << 5 << ListRange(5, 17); + QTest::newRow("move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0 + << 20 << 0 << 3 << ListRange(0, 17); + QTest::newRow("move from below view, move multiple up, move to top, contentY not 0") << 30 << 60.0 << 0.0 + << 25 << 3 << 3 << ListRange(0+3, 17+3); + + QTest::newRow("move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0 + << 20 << 17 << 1 << ListRange(17, 17); + QTest::newRow("move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 60.0 << 0.0 + << 25 << 17+3 << 1 << ListRange(17+3, 17+3); + QTest::newRow("move from below view, move multiple up, move to to bottom") << 30 << 0.0 << 0.0 + << 20 << 17 << 3 << ListRange(17, 17); + QTest::newRow("move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 60.0 << 0.0 + << 25 << 17+3 << 3 << ListRange(17+3, 17+3); +} + +void tst_QQuickGridView::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(); + 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); + canvas->setSource(testFileUrl("removeTransitions.qml")); + canvas->show(); + + QQuickGridView *gridview = findItem(canvas->rootObject(), "grid"); + QTRY_VERIFY(gridview != 0); + QQuickItem *contentItem = gridview->contentItem(); + QVERIFY(contentItem != 0); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + + if (contentY != 0) { + gridview->setContentY(contentY); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + } + + QList > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model); + + // only target items that are visible should be animated + QList > expectedTargetData; + QList targetIndexes; + if (shouldAnimateTargets) { + for (int i=removalIndex; iheight()) / 60.0)*3) - 1; + if (i >= firstVisibleIndex && i <= lastVisibleIndex) { + expectedTargetData << qMakePair(model.name(i), model.number(i)); + targetIndexes << i; + } + } + QVERIFY(expectedTargetData.count() > 0); + } + + // calculate targetItems and expectedTargets before model changes + QList targetItems = findItems(contentItem, "wrapper", targetIndexes); + QVariantMap expectedTargets; + for (int i=0; icount()); + + if (shouldAnimateTargets || expectedDisplacedIndexes.isValid()) { + QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count()); + QTRY_COMPARE(gridview->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(gridview->property("targetTrans_items").toMap(), expectedTargets); + matchIndexLists(gridview->property("targetTrans_targetIndexes").toList(), targetIndexes); + matchItemLists(gridview->property("targetTrans_targetItems").toList(), targetItems); + if (expectedDisplacedIndexes.isValid()) { + // adjust expectedDisplacedIndexes to their final values after the move + QList displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, removalIndex, removalCount); + matchItemsAndIndexes(gridview->property("displacedTrans_items").toMap(), model, displacedIndexes); + matchIndexLists(gridview->property("displacedTrans_targetIndexes").toList(), targetIndexes); + matchItemLists(gridview->property("displacedTrans_targetItems").toList(), targetItems); + } + } else { + QTRY_COMPARE(model_targetItems_transitionTo.count(), 0); + QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0); + } + + QList items = findItems(contentItem, "wrapper"); + int itemCount = items.count(); + int firstVisibleIndex = -1; + for (int i=0; iy() >= contentY) + firstVisibleIndex = index; + else 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(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), contentY + ((i-firstVisibleIndex)/3) * 60.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; +} + +void tst_QQuickGridView::removeTransitions_data() +{ + QTest::addColumn("initialItemCount"); + QTest::addColumn("contentY"); + QTest::addColumn("shouldAnimateTargets"); + QTest::addColumn("removalIndex"); + QTest::addColumn("removalCount"); + QTest::addColumn("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. + + // For a GridView, removing any number of items other than a full row before the start + // should displace all items in the view + QTest::newRow("remove 1 before start") + << 30 << 120.0 << false + << 2 << 1 << ListRange(6, 24); // 6-24 are displaced + QTest::newRow("remove 1 row, before start") + << 30 << 120.0 << false + << 3 << 3 << ListRange(); + QTest::newRow("remove between 1-2 rows, before start") + << 30 << 120.0 << false + << 0 << 5 << ListRange(6, 25); + QTest::newRow("remove 2 rows, before start") + << 30 << 120.0 << false + << 0 << 6 << ListRange(); + QTest::newRow("remove mix of before and after start") + << 30 << 60.0 << true + << 2 << 3 << ListRange(5, 23); // 5-23 are displaced into view + + + QTest::newRow("remove 1 from start") + << 30 << 0.0 << true + << 0 << 1 << ListRange(1, 18); // 1-18 are displaced into view + QTest::newRow("remove multiple from start") + << 30 << 0.0 << true + << 0 << 3 << ListRange(3, 20); // 3-18 are displaced into view + QTest::newRow("remove 1 from start, content y not 0") + << 30 << 60.0 << true + << 3 << 1 << ListRange(1 + 3, 18 + 3); + QTest::newRow("remove multiple from start, content y not 0") + << 30 << 60.0 << true + << 3 << 3 << ListRange(3 + 3, 20 + 3); + + + QTest::newRow("remove 1 from middle") + << 30 << 0.0 << true + << 5 << 1 << ListRange(6, 18); + QTest::newRow("remove multiple from middle") + << 30 << 0.0 << true + << 5 << 3 << ListRange(8, 20); + + + QTest::newRow("remove 1 from bottom") + << 30 << 0.0 << true + << 17 << 1 << ListRange(18, 18); + QTest::newRow("remove multiple (1 row) from bottom") + << 30 << 0.0 << true + << 15 << 3 << ListRange(18, 20); + QTest::newRow("remove multiple (> 1 row) from bottom") + << 30 << 0.0 << true + << 15 << 5 << ListRange(20, 22); + QTest::newRow("remove 1 from bottom, content y not 0") + << 30 << 60.0 << true + << 17 + 3 << 1 << ListRange(18 + 3, 18 + 3); + QTest::newRow("remove multiple (1 row) from bottom, content y not 0") + << 30 << 60.0 << true + << 15 + 3 << 3 << ListRange(18 + 3, 20 + 3); + + + QTest::newRow("remove 1 after end") + << 30 << 0.0 << false + << 18 << 1 << ListRange(); + QTest::newRow("remove multiple after end") + << 30 << 0.0 << false + << 18 << 3 << ListRange(); +} + +void tst_QQuickGridView::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, 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(); + ctxt->setContextProperty("testModel", &model); + 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(); + + QQuickGridView *gridview = findItem(canvas->rootObject(), "grid"); + QTRY_VERIFY(gridview != 0); + QQuickItem *contentItem = gridview->contentItem(); + QVERIFY(contentItem != 0); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + + int timeBetweenActions = canvas->rootObject()->property("timeBetweenActions").toInt(); + + QList > targetItems; + for (int i=0; icount()); + QTRY_VERIFY(gridview->property("runningAddTargets").toBool()); + QTRY_VERIFY(gridview->property("runningAddDisplaced").toBool()); + if (i == changes.count() - 1) { + QTRY_VERIFY(!gridview->property("runningAddTargets").toBool()); + QTRY_VERIFY(!gridview->property("runningAddDisplaced").toBool()); + } else { + QTest::qWait(timeBetweenActions); + } + break; + } + case ListChange::Removed: + for (int j=changes[i].index; jcount()); + QTRY_VERIFY(gridview->property("runningRemoveTargets").toBool()); + QTRY_VERIFY(gridview->property("runningRemoveDisplaced").toBool()); + if (i == changes.count() - 1) { + QTRY_VERIFY(!gridview->property("runningRemoveTargets").toBool()); + QTRY_VERIFY(!gridview->property("runningRemoveDisplaced").toBool()); + } else { + QTest::qWait(timeBetweenActions); + } + break; + case ListChange::Moved: + for (int j=changes[i].index; jproperty("runningMoveTargets").toBool()); + QTRY_VERIFY(gridview->property("runningMoveDisplaced").toBool()); + if (i == changes.count() - 1) { + QTRY_VERIFY(!gridview->property("runningMoveTargets").toBool()); + QTRY_VERIFY(!gridview->property("runningMoveDisplaced").toBool()); + } else { + QTest::qWait(timeBetweenActions); + } + break; + case ListChange::SetCurrent: + gridview->setCurrentIndex(changes[i].index); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + break; + case ListChange::SetContentY: + gridview->setContentY(changes[i].pos); + QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false); + break; + } + } + QCOMPARE(gridview->count(), model.count()); + + QList items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + for (int i=0; iy() >= 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(contentItem, "wrapper").count(); + for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QCOMPARE(item->x(), (i%3)*80.0); + QCOMPARE(item->y(), (i/3)*60.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; +} + +void tst_QQuickGridView::multipleTransitions_data() +{ + QTest::addColumn("initialCount"); + QTest::addColumn("contentY"); + QTest::addColumn >("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::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::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::insert(0, 1) + << ListChange::setContentY(160.0) + << ListChange::setContentY(0.0) + << ListChange::insert(0, 1) + ); +} + void tst_QQuickGridView::cacheBuffer() { QQuickView *canvas = createView(); @@ -4085,6 +5006,56 @@ void tst_QQuickGridView::unrequestedVisibility() delete canvas; } +QList tst_QQuickGridView::toIntList(const QVariantList &list) +{ + QList ret; + bool ok = true; + for (int i=0; i &expectedIndexes) +{ + for (int i=0; i current = indexLists[i].value >().toSet(); + if (current != expectedIndexes.toSet()) + qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes; + QCOMPARE(current, expectedIndexes.toSet()); + } +} + +void tst_QQuickGridView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList &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_QQuickGridView::matchItemLists(const QVariantList &itemLists, const QList &expectedItems) +{ + for (int i=0; i(current[j].value()); + 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_QQuickGridView) #include "tst_qquickgridview.moc" 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 #include #include -#include #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 void items(const QUrl &source, bool forceLayout); template void changed(const QUrl &source, bool forceLayout); @@ -182,6 +192,11 @@ private: template void clear(const QUrl &source); template void sections(const QUrl &source); + QList toIntList(const QVariantList &list); + void matchIndexLists(const QVariantList &indexLists, const QList &expectedIndexes); + void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList &expectedIndexes); + void matchItemLists(const QVariantList &itemLists, const QList &expectedItems); + void inserted_more_data(); void removed_more_data(); void moved_data(); @@ -1356,18 +1371,26 @@ void tst_QQuickListView::multipleChanges() { QList > items; for (int j=changes[i].index; jpolishScheduled, 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(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(contentItem, "wrapper").count(); + if (usePopulateTransition) + QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt()); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(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(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(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(contentItem, "wrapper").count(); + if (usePopulateTransition) + QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt()); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(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(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(contentItem, "wrapper").count(); + if (usePopulateTransition) + QCOMPARE(itemCount, listview->property("countPopulateTransitions").toInt()); + for (int i=0; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(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(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; +} + +void tst_QQuickListView::populateTransitions_data() +{ + QTest::addColumn("staticallyPopulate"); + QTest::addColumn("dynamicallyPopulate"); + QTest::addColumn("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(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 > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model); + + // only target items that will become visible should be animated + QList > newData; + QList > expectedTargetData; + QList targetIndexes; + if (shouldAnimateTargets) { + for (int i=insertionIndex; 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 targetItems = findItems(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 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 items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + int itemCount = items.count(); + for (int i=0; iy() >= 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(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QTRY_COMPARE(item->y(), i*20.0); + QQuickText *name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; + delete testObject; +} + +void tst_QQuickListView::addTransitions_data() +{ + QTest::addColumn("initialItemCount"); + QTest::addColumn("contentY"); + QTest::addColumn("shouldAnimateTargets"); + QTest::addColumn("insertionIndex"); + QTest::addColumn("insertionCount"); + QTest::addColumn("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(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 > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model); + + // Items moving to *or* from visible positions should be animated. + // Otherwise, they should not be animated. + QList > expectedTargetData; + QList targetIndexes; + for (int i=moveFrom; iheight()) / 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 targetItems = findItems(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 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 items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + for (int i=0; iy() >= 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(contentItem, "wrapper").count(); + for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(contentItem, "wrapper", i); + QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i))); + QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove); + name = findItem(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; + delete testObject; +} + +void tst_QQuickListView::moveTransitions_data() +{ + QTest::addColumn("initialItemCount"); + QTest::addColumn("contentY"); + QTest::addColumn("itemsOffsetAfterMove"); + QTest::addColumn("moveFrom"); + QTest::addColumn("moveTo"); + QTest::addColumn("moveCount"); + QTest::addColumn("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(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 > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model); + + // only target items that are visible should be animated + QList > expectedTargetData; + QList targetIndexes; + if (shouldAnimateTargets) { + for (int i=removalIndex; 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 targetItems = findItems(contentItem, "wrapper", targetIndexes); + QVariantMap expectedTargets; + for (int i=0; icount()); + + 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 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 items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + int itemCount = items.count(); + + for (int i=0; iy() >= 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(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(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; + delete testObject; +} + +void tst_QQuickListView::removeTransitions_data() +{ + QTest::addColumn("initialItemCount"); + QTest::addColumn("contentY"); + QTest::addColumn("shouldAnimateTargets"); + QTest::addColumn("removalIndex"); + QTest::addColumn("removalCount"); + QTest::addColumn("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, 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(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 > targetItems; + for (int i=0; icount()); + 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; jcount()); + 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; jproperty("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 items = findItems(contentItem, "wrapper"); + int firstVisibleIndex = -1; + for (int i=0; iy() >= 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(contentItem, "wrapper").count(); + for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) { + QQuickItem *item = findItem(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(contentItem, "textName", i); + QVERIFY(name != 0); + QTRY_COMPARE(name->text(), model.name(i)); + } + + delete canvas; + delete testObject; +} + +void tst_QQuickListView::multipleTransitions_data() +{ + QTest::addColumn("initialCount"); + QTest::addColumn("contentY"); + QTest::addColumn >("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::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::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::insert(0, 1) + << ListChange::setContentY(80.0) + << ListChange::setContentY(0.0) + << ListChange::insert(0, 1) + ); +} + +QList tst_QQuickListView::toIntList(const QVariantList &list) +{ + QList ret; + bool ok = true; + for (int i=0; i &expectedIndexes) +{ + for (int i=0; i current = indexLists[i].value >().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 &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 &expectedItems) +{ + for (int i=0; i(current[j].value()); + 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) diff --git a/tests/auto/qtquick2/shared/viewtestutil.cpp b/tests/auto/qtquick2/shared/viewtestutil.cpp index ed2066d6aa..eeef23001e 100644 --- a/tests/auto/qtquick2/shared/viewtestutil.cpp +++ b/tests/auto/qtquick2/shared/viewtestutil.cpp @@ -99,6 +99,53 @@ void QQuickViewTestUtil::flick(QQuickView *canvas, const QPoint &from, const QPo QTest::qWait(50); } +QList QQuickViewTestUtil::adjustIndexesForAddDisplaced(const QList &indexes, int index, int count) +{ + QList result; + for (int i=0; i= index) { + num += count; + } + result << num; + } + return result; +} + +QList QQuickViewTestUtil::adjustIndexesForMove(const QList &indexes, int from, int to, int count) +{ + QList result; + for (int i=0; i= from && num < from + count) + num += (to - from); // target + else if (num >= from && num < to + count) + num -= count; // displaced + } else if (from > to) { + if (num >= from && num < from + count) + num -= (from - to); // target + else if (num >= to && num < from + count) + num += count; // displaced + } + result << num; + } + return result; +} + +QList QQuickViewTestUtil::adjustIndexesForRemoveDisplaced(const QList &indexes, int index, int count) +{ + QList result; + for (int i=0; i= index) + num -= count; + result << num; + } + return result; +} + + QQuickViewTestUtil::QmlListModel::QmlListModel(QObject *parent) : QListModelInterface(parent) { @@ -228,6 +275,17 @@ void QQuickViewTestUtil::QmlListModel::clear() { emit itemsRemoved(0, count); } +void QQuickViewTestUtil::QmlListModel::matchAgainst(const QList > &other, const QString &error1, const QString &error2) { + for (int i=0; i > &other, const QString &error1, const QString &error2) { + for (int i=0; i > QQuickViewTestUtil::ListRange::getModelDataValues(const QmlListModel &model) +{ + QList > data; + if (!valid) + return data; + for (int i=0; i > QQuickViewTestUtil::ListRange::getModelDataValues(const QaimModel &model) +{ + QList > data; + if (!valid) + return data; + for (int i=0; i adjustIndexesForAddDisplaced(const QList &indexes, int index, int count); + QList adjustIndexesForMove(const QList &indexes, int from, int to, int count); + QList adjustIndexesForRemoveDisplaced(const QList &indexes, int index, int count); + struct ListChange { - enum { Inserted, Removed, Moved, SetCurrent } type; + enum { Inserted, Removed, Moved, SetCurrent, SetContentY } type; int index; int count; int to; // Move + qreal pos; // setContentY - static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; } - static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; } - static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; } - static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; } + static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1, 0.0 }; return c; } + static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1, 0.0 }; return c; } + static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to, 0.0 }; return c; } + static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1, 0.0 }; return c; } + static ListChange setContentY(qreal pos) { ListChange c = { SetContentY, -1, -1, -1, pos }; return c; } }; class QmlListModel : public QListModelInterface @@ -87,7 +93,7 @@ namespace QQuickViewTestUtil QVariant data(int index, int role) const; QHash data(int index, const QList &roles) const; - void addItem(const QString &name, const QString &number); + Q_INVOKABLE void addItem(const QString &name, const QString &number); void insertItem(int index, const QString &name, const QString &number); void insertItems(int index, const QList > &items); @@ -101,6 +107,8 @@ namespace QQuickViewTestUtil void clear(); + void matchAgainst(const QList > &other, const QString &error1, const QString &error2); + private: QList > list; }; @@ -120,7 +128,7 @@ namespace QQuickViewTestUtil QString name(int index) const; QString number(int index) const; - void addItem(const QString &name, const QString &number); + Q_INVOKABLE void addItem(const QString &name, const QString &number); void addItems(const QList > &items); void insertItem(int index, const QString &name, const QString &number); void insertItems(int index, const QList > &items); @@ -134,13 +142,39 @@ namespace QQuickViewTestUtil void modifyItem(int idx, const QString &name, const QString &number); void clear(); + void reset(); + + void matchAgainst(const QList > &other, const QString &error1, const QString &error2); private: QList > list; }; + class ListRange + { + public: + ListRange(); + ListRange(const ListRange &other); + ListRange(int start, int end); + + ~ListRange(); + + ListRange operator+(const ListRange &other) const; + bool operator==(const ListRange &other) const; + bool operator!=(const ListRange &other) const; + + bool isValid() const; + int count() const; + + QList > getModelDataValues(const QmlListModel &model); + QList > getModelDataValues(const QaimModel &model); + + QList indexes; + bool valid; + }; } Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QQuickViewTestUtil::ListRange) #endif // QQUICKVIEWTESTUTIL_H diff --git a/tests/auto/qtquick2/shared/visualtestutil.h b/tests/auto/qtquick2/shared/visualtestutil.h index ceed4b04a7..09bb03c002 100644 --- a/tests/auto/qtquick2/shared/visualtestutil.h +++ b/tests/auto/qtquick2/shared/visualtestutil.h @@ -97,6 +97,16 @@ namespace QQuickVisualTestUtil return items; } + + template + QList findItems(QQuickItem *parent, const QString &objectName, const QList &indexes) + { + QList items; + for (int i=0; i(findItem(parent, objectName, indexes[i])); + return items; + } + } #endif // QQUICKVISUALTESTUTIL_H -- cgit v1.2.3