aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp')
-rw-r--r--tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp1255
1 files changed, 1255 insertions, 0 deletions
diff --git a/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp
new file mode 100644
index 0000000000..bdac2112b6
--- /dev/null
+++ b/tests/auto/quick/qquicklistview2/tst_qquicklistview2.cpp
@@ -0,0 +1,1255 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <QtQuick/qquickview.h>
+#include <QtQuick/private/qquickitemview_p_p.h>
+#include <QtQuick/private/qquicklistview_p.h>
+#include <QtQuickTest/QtQuickTest>
+#include <QStringListModel>
+#include <QQmlApplicationEngine>
+
+#include <QtQuickTestUtils/private/viewtestutils_p.h>
+#include <QtQuickTestUtils/private/visualtestutils_p.h>
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+
+Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
+
+using namespace QQuickViewTestUtils;
+using namespace QQuickVisualTestUtils;
+
+class tst_QQuickListView2 : public QQmlDataTest
+{
+ Q_OBJECT
+public:
+ tst_QQuickListView2();
+
+private slots:
+ void urlListModel();
+ void dragDelegateWithMouseArea_data();
+ void dragDelegateWithMouseArea();
+ void delegateChooserEnumRole();
+ void QTBUG_92809();
+ void footerUpdate();
+ void singletonModelLifetime();
+ void delegateModelRefresh();
+ void wheelSnap();
+ void wheelSnap_data();
+
+ void sectionsNoOverlap();
+ void metaSequenceAsModel();
+ void noCrashOnIndexChange();
+ void innerRequired();
+ void boundDelegateComponent();
+ void tapDelegateDuringFlicking_data();
+ void tapDelegateDuringFlicking();
+ void flickDuringFlicking_data();
+ void flickDuringFlicking();
+ void maxExtent_data();
+ void maxExtent();
+ void isCurrentItem_DelegateModel();
+ void isCurrentItem_NoRegressionWithDelegateModelGroups();
+
+ void pullbackSparseList();
+ void highlightWithBound();
+ void sectionIsCompatibleWithBoundComponents();
+ void sectionGeometryChange();
+ void areaZeroviewDoesNotNeedlesslyPopulateWholeModel();
+ void viewportAvoidUndesiredMovementOnSetCurrentIndex();
+
+ void delegateContextHandling();
+ void fetchMore_data();
+ void fetchMore();
+
+ void changingOrientationResetsPreviousAxisValues_data();
+ void changingOrientationResetsPreviousAxisValues();
+ void bindingDirectlyOnPositionInHeaderAndFooterDelegates_data();
+ void bindingDirectlyOnPositionInHeaderAndFooterDelegates();
+
+ void clearObjectListModel();
+
+private:
+ void flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to);
+ QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice());
+};
+
+tst_QQuickListView2::tst_QQuickListView2()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
+{
+}
+
+void tst_QQuickListView2::urlListModel()
+{
+ QScopedPointer<QQuickView> window(createView());
+ QVERIFY(window);
+
+ QList<QUrl> model = { QUrl::fromLocalFile("abc"), QUrl::fromLocalFile("123") };
+ window->setInitialProperties({{ "model", QVariant::fromValue(model) }});
+
+ window->setSource(testFileUrl("urlListModel.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *view = window->rootObject()->property("view").value<QQuickListView*>();
+ QVERIFY(view);
+ if (QQuickTest::qIsPolishScheduled(view))
+ QVERIFY(QQuickTest::qWaitForPolish(view));
+ QCOMPARE(view->count(), model.size());
+}
+
+static void dragListView(QWindow *window, QPoint *startPos, const QPoint &delta)
+{
+ auto drag_helper = [&](QWindow *window, QPoint *startPos, const QPoint &d) {
+ QPoint pos = *startPos;
+ const int dragDistance = d.manhattanLength();
+ const QPoint unitVector(qBound(-1, d.x(), 1), qBound(-1, d.y(), 1));
+ for (int i = 0; i < dragDistance; ++i) {
+ QTest::mouseMove(window, pos);
+ pos += unitVector;
+ }
+ // Move to the final position
+ pos = *startPos + d;
+ QTest::mouseMove(window, pos);
+ *startPos = pos;
+ };
+
+ if (delta.manhattanLength() == 0)
+ return;
+ const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
+ const QPoint unitVector(qBound(-1, delta.x(), 1), qBound(-1, delta.y(), 1));
+ // go just beyond the drag theshold
+ drag_helper(window, startPos, unitVector * (dragThreshold + 1));
+ drag_helper(window, startPos, unitVector);
+
+ // next drag will actually scroll the listview
+ drag_helper(window, startPos, delta);
+}
+
+void tst_QQuickListView2::dragDelegateWithMouseArea_data()
+{
+ QTest::addColumn<QQuickItemView::LayoutDirection>("layoutDirection");
+
+ for (int layDir = QQuickItemView::LeftToRight; layDir <= (int)QQuickItemView::VerticalBottomToTop; layDir++) {
+ const char *enumValueName = QMetaEnum::fromType<QQuickItemView::LayoutDirection>().valueToKey(layDir);
+ QTest::newRow(enumValueName) << static_cast<QQuickItemView::LayoutDirection>(layDir);
+ }
+}
+
+void tst_QQuickListView2::viewportAvoidUndesiredMovementOnSetCurrentIndex()
+{
+ QScopedPointer<QQuickView> window(createView());
+ QVERIFY(window);
+ window->setFlag(Qt::FramelessWindowHint);
+ window->setSource(testFileUrl("viewportAvoidUndesiredMovementOnSetCurrentIndex.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+ QVERIFY(window->rootObject());
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QVERIFY(listview);
+ listview->setCurrentIndex(2); // change current item
+ // partially obscure first item
+ QCOMPARE(listview->contentY(), 0);
+ listview->setContentY(50);
+ QTRY_COMPARE(listview->contentY(), 50);
+ listview->setCurrentIndex(0); // change current item back to first one
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ // that shouldn't have caused any movement
+ QCOMPARE(listview->contentY(), 50);
+
+ // that even applies to the case where the current item is completely out of the viewport
+ listview->setCurrentIndex(25);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ QCOMPARE(listview->contentY(), 50);
+}
+
+void tst_QQuickListView2::dragDelegateWithMouseArea()
+{
+ QFETCH(QQuickItemView::LayoutDirection, layoutDirection);
+
+ QScopedPointer<QQuickView> window(createView());
+ QVERIFY(window);
+ window->setFlag(Qt::FramelessWindowHint);
+ window->setSource(testFileUrl("delegateWithMouseArea.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QVERIFY(listview != nullptr);
+
+ const bool horizontal = layoutDirection < QQuickItemView::VerticalTopToBottom;
+ listview->setOrientation(horizontal ? QQuickListView::Horizontal : QQuickListView::Vertical);
+
+ if (horizontal)
+ listview->setLayoutDirection(static_cast<Qt::LayoutDirection>(layoutDirection));
+ else
+ listview->setVerticalLayoutDirection(static_cast<QQuickItemView::VerticalLayoutDirection>(layoutDirection));
+
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+
+ auto contentPosition = [&](QQuickListView *listview) {
+ return (listview->orientation() == QQuickListView::Horizontal ? listview->contentX(): listview->contentY());
+ };
+
+ qreal expectedContentPosition = contentPosition(listview);
+ QPoint startPos = (QPointF(listview->width(), listview->height())/2).toPoint();
+ QTest::mousePress(window.data(), Qt::LeftButton, Qt::NoModifier, startPos, 200);
+
+ QPoint dragDelta(0, -10);
+
+ if (layoutDirection == QQuickItemView::RightToLeft || layoutDirection == QQuickItemView::VerticalBottomToTop)
+ dragDelta = -dragDelta;
+ expectedContentPosition -= dragDelta.y();
+ if (horizontal)
+ dragDelta = dragDelta.transposed();
+
+ dragListView(window.data(), &startPos, dragDelta);
+
+ QTest::mouseRelease(window.data(), Qt::LeftButton, Qt::NoModifier, startPos, 200); // Wait 200 ms before we release to avoid trigger a flick
+
+ // wait for the "fixup" animation to finish
+ QVERIFY(QTest::qWaitFor([&]()
+ { return !listview->isMoving();}
+ ));
+
+ QCOMPARE(contentPosition(listview), expectedContentPosition);
+}
+
+
+void tst_QQuickListView2::delegateChooserEnumRole()
+{
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl("delegateChooserEnumRole.qml")));
+ QQuickListView *listview = qobject_cast<QQuickListView*>(window.rootObject());
+ QVERIFY(listview);
+ QTRY_COMPARE(listview->count(), 3);
+ QCOMPARE(listview->itemAtIndex(0)->property("delegateType").toInt(), 0);
+ QCOMPARE(listview->itemAtIndex(1)->property("delegateType").toInt(), 1);
+ QCOMPARE(listview->itemAtIndex(2)->property("delegateType").toInt(), 2);
+}
+
+void tst_QQuickListView2::QTBUG_92809()
+{
+ QScopedPointer<QQuickView> window(createView());
+ QTRY_VERIFY(window);
+ window->setSource(testFileUrl("qtbug_92809.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QTRY_VERIFY(listview != nullptr);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ listview->setCurrentIndex(1);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ listview->setCurrentIndex(2);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ listview->setCurrentIndex(3);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ QTest::qWait(500);
+ listview->setCurrentIndex(10);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ QTest::qWait(500);
+ int currentIndex = listview->currentIndex();
+ QTRY_COMPARE(currentIndex, 9);
+}
+
+void tst_QQuickListView2::footerUpdate()
+{
+ QScopedPointer<QQuickView> window(createView());
+ QTRY_VERIFY(window);
+ window->setSource(testFileUrl("footerUpdate.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QTRY_VERIFY(listview != nullptr);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+ QQuickItem *footer = listview->footerItem();
+ QTRY_VERIFY(footer);
+ QVERIFY(QQuickTest::qWaitForPolish(footer));
+ QTRY_COMPARE(footer->y(), 0);
+}
+
+void tst_QQuickListView2::sectionsNoOverlap()
+{
+ QScopedPointer<QQuickView> window(createView());
+ QTRY_VERIFY(window);
+ window->setSource(testFileUrl("sectionsNoOverlap.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QTRY_VERIFY(listview != nullptr);
+
+ QQuickItem *contentItem = listview->contentItem();
+ QTRY_VERIFY(contentItem != nullptr);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+
+ const unsigned int sectionCount = 2, normalDelegateCount = 2;
+ const unsigned int expectedSectionHeight = 48;
+ const unsigned int expectedNormalDelegateHeight = 40;
+
+ unsigned int normalDelegateCounter = 0;
+ for (unsigned int sectionIndex = 0; sectionIndex < sectionCount; ++sectionIndex) {
+ QQuickItem *sectionDelegate =
+ findItem<QQuickItem>(contentItem, "section" + QString::number(sectionIndex + 1));
+ QVERIFY(sectionDelegate);
+
+ QCOMPARE(sectionDelegate->height(), expectedSectionHeight);
+ QVERIFY(sectionDelegate->isVisible());
+ QCOMPARE(sectionDelegate->y(),
+ qreal(sectionIndex * expectedSectionHeight
+ + (sectionIndex * normalDelegateCount * expectedNormalDelegateHeight)));
+
+ for (; normalDelegateCounter < ((sectionIndex + 1) * normalDelegateCount);
+ ++normalDelegateCounter) {
+ QQuickItem *normalDelegate = findItem<QQuickItem>(
+ contentItem, "element" + QString::number(normalDelegateCounter + 1));
+ QVERIFY(normalDelegate);
+
+ QCOMPARE(normalDelegate->height(), expectedNormalDelegateHeight);
+ QVERIFY(normalDelegate->isVisible());
+ QCOMPARE(normalDelegate->y(),
+ qreal((sectionIndex + 1) * expectedSectionHeight
+ + normalDelegateCounter * expectedNormalDelegateHeight
+ + listview->spacing() * normalDelegateCounter));
+ }
+ }
+}
+
+void tst_QQuickListView2::metaSequenceAsModel()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("metaSequenceAsModel.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ QStringList strings = qvariant_cast<QStringList>(o->property("texts"));
+ QCOMPARE(strings.size(), 2);
+ QCOMPARE(strings[0], QStringLiteral("1/2"));
+ QCOMPARE(strings[1], QStringLiteral("5/6"));
+}
+
+void tst_QQuickListView2::noCrashOnIndexChange()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("noCrashOnIndexChange.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QObject *delegateModel = qmlContext(o.data())->objectForName("displayDelegateModel");
+ QVERIFY(delegateModel);
+
+ QObject *items = qvariant_cast<QObject *>(delegateModel->property("items"));
+ QCOMPARE(items->property("name").toString(), QStringLiteral("items"));
+ QCOMPARE(items->property("count").toInt(), 4);
+}
+
+void tst_QQuickListView2::innerRequired()
+{
+ QQmlEngine engine;
+ const QUrl url(testFileUrl("innerRequired.qml"));
+ QQmlComponent component(&engine, url);
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+
+ QScopedPointer<QObject> o(component.create());
+ QVERIFY2(!o.isNull(), qPrintable(component.errorString()));
+
+ QQuickListView *a = qobject_cast<QQuickListView *>(
+ qmlContext(o.data())->objectForName(QStringLiteral("listView")));
+ QVERIFY(a);
+
+ QCOMPARE(a->count(), 2);
+ QCOMPARE(a->itemAtIndex(0)->property("age").toInt(), 8);
+ QCOMPARE(a->itemAtIndex(0)->property("text").toString(), u"meow");
+ QCOMPARE(a->itemAtIndex(1)->property("age").toInt(), 5);
+ QCOMPARE(a->itemAtIndex(1)->property("text").toString(), u"woof");
+}
+
+void tst_QQuickListView2::boundDelegateComponent()
+{
+ QQmlEngine engine;
+ const QUrl url(testFileUrl("boundDelegateComponent.qml"));
+ QQmlComponent c(&engine, url);
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QTest::ignoreMessage(
+ QtWarningMsg, qPrintable(QLatin1String("%1:14: ReferenceError: index is not defined")
+ .arg(url.toString())));
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QQmlContext *context = qmlContext(o.data());
+
+ QObject *inner = context->objectForName(QLatin1String("listView"));
+ QVERIFY(inner != nullptr);
+ QQuickListView *listView = qobject_cast<QQuickListView *>(inner);
+ QVERIFY(listView != nullptr);
+ QObject *item = listView->itemAtIndex(0);
+ QVERIFY(item);
+ QCOMPARE(item->objectName(), QLatin1String("fooouterundefined"));
+
+ QObject *inner2 = context->objectForName(QLatin1String("listView2"));
+ QVERIFY(inner2 != nullptr);
+ QQuickListView *listView2 = qobject_cast<QQuickListView *>(inner2);
+ QVERIFY(listView2 != nullptr);
+ QObject *item2 = listView2->itemAtIndex(0);
+ QVERIFY(item2);
+ QCOMPARE(item2->objectName(), QLatin1String("fooouter0"));
+
+ QQmlComponent *comp = qobject_cast<QQmlComponent *>(
+ context->objectForName(QLatin1String("outerComponent")));
+ QVERIFY(comp != nullptr);
+
+ for (int i = 0; i < 3; ++i) {
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ qPrintable(QLatin1String("%1:51:21: ReferenceError: model is not defined")
+ .arg(url.toString())));
+ }
+
+ QScopedPointer<QObject> outerItem(comp->create(context));
+ QVERIFY(!outerItem.isNull());
+ QQuickListView *innerListView = qobject_cast<QQuickListView *>(
+ qmlContext(outerItem.data())->objectForName(QLatin1String("innerListView")));
+ QVERIFY(innerListView != nullptr);
+ QCOMPARE(innerListView->count(), 3);
+ for (int i = 0; i < 3; ++i)
+ QVERIFY(innerListView->itemAtIndex(i)->objectName().isEmpty());
+}
+
+void tst_QQuickListView2::tapDelegateDuringFlicking_data()
+{
+ QTest::addColumn<QByteArray>("qmlFile");
+ QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior");
+ QTest::addColumn<bool>("expectCanceled");
+
+ QTest::newRow("Button StopAtBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds) << false;
+ QTest::newRow("MouseArea StopAtBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds) << true;
+ QTest::newRow("Button DragOverBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds) << false;
+ QTest::newRow("MouseArea DragOverBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds) << true;
+ QTest::newRow("Button OvershootBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds) << false;
+ QTest::newRow("MouseArea OvershootBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds) << true;
+ QTest::newRow("Button DragAndOvershootBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds) << false;
+ QTest::newRow("MouseArea DragAndOvershootBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds) << true;
+}
+
+void tst_QQuickListView2::tapDelegateDuringFlicking() // QTBUG-103832
+{
+ QFETCH(QByteArray, qmlFile);
+ QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior);
+ QFETCH(bool, expectCanceled);
+
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFile.constData())));
+ QQuickListView *listView = qobject_cast<QQuickListView*>(window.rootObject());
+ QVERIFY(listView);
+ listView->setBoundsBehavior(boundsBehavior);
+
+ flickWithTouch(&window, {100, 400}, {100, 100});
+ QTRY_VERIFY(listView->contentY() > 501); // let it flick some distance
+ QVERIFY(listView->isFlicking()); // we want to test the case when it's still moving while we tap
+ // @y = 400 we pressed the 4th delegate; started flicking, and the press was canceled
+ QCOMPARE(listView->property("pressedDelegates").toList().first(), 4);
+ // At first glance one would expect MouseArea and Button would be consistent about this;
+ // but in fact, before ListView takes over the grab via filtering,
+ // Button.pressed transitions to false because QQuickAbstractButtonPrivate::handleMove
+ // sees that the touchpoint has strayed outside its bounds, but it does NOT emit the canceled signal
+ if (expectCanceled) {
+ const QVariantList canceledDelegates = listView->property("canceledDelegates").toList();
+ QCOMPARE(canceledDelegates.size(), 1);
+ QCOMPARE(canceledDelegates.first(), 4);
+ }
+ QCOMPARE(listView->property("releasedDelegates").toList().size(), 0);
+
+ // press a delegate during flicking (at y > 501 + 100, so likely delegate 6)
+ QTest::touchEvent(&window, touchDevice.data()).press(0, {100, 100});
+ QQuickTouchUtils::flush(&window);
+ QTest::touchEvent(&window, touchDevice.data()).release(0, {100, 100});
+ QQuickTouchUtils::flush(&window);
+
+ const QVariantList pressedDelegates = listView->property("pressedDelegates").toList();
+ const QVariantList releasedDelegates = listView->property("releasedDelegates").toList();
+ const QVariantList tappedDelegates = listView->property("tappedDelegates").toList();
+ const QVariantList canceledDelegates = listView->property("canceledDelegates").toList();
+
+ qCDebug(lcTests) << "pressed" << pressedDelegates; // usually [4, 6]
+ qCDebug(lcTests) << "released" << releasedDelegates;
+ qCDebug(lcTests) << "tapped" << tappedDelegates;
+ qCDebug(lcTests) << "canceled" << canceledDelegates;
+
+ // which delegate received the second press, during flicking?
+ const int lastPressed = pressedDelegates.last().toInt();
+ QVERIFY(lastPressed > 5);
+ QCOMPARE(releasedDelegates.last(), lastPressed);
+ QCOMPARE(tappedDelegates.last(), lastPressed);
+ QCOMPARE(canceledDelegates.size(), expectCanceled ? 1 : 0); // only the first press was canceled, not the second
+}
+
+void tst_QQuickListView2::flickDuringFlicking_data()
+{
+ QTest::addColumn<QByteArray>("qmlFile");
+ QTest::addColumn<QQuickFlickable::BoundsBehavior>("boundsBehavior");
+
+ QTest::newRow("Button StopAtBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds);
+ QTest::newRow("MouseArea StopAtBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::StopAtBounds);
+ QTest::newRow("Button DragOverBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds);
+ QTest::newRow("MouseArea DragOverBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragOverBounds);
+ QTest::newRow("Button OvershootBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds);
+ QTest::newRow("MouseArea OvershootBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::OvershootBounds);
+ QTest::newRow("Button DragAndOvershootBounds") << QByteArray("buttonDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds);
+ QTest::newRow("MouseArea DragAndOvershootBounds") << QByteArray("mouseAreaDelegate.qml")
+ << QQuickFlickable::BoundsBehavior(QQuickFlickable::DragAndOvershootBounds);
+}
+
+void tst_QQuickListView2::flickDuringFlicking() // QTBUG-103832
+{
+ QFETCH(QByteArray, qmlFile);
+ QFETCH(QQuickFlickable::BoundsBehavior, boundsBehavior);
+
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl(qmlFile.constData())));
+ QQuickListView *listView = qobject_cast<QQuickListView*>(window.rootObject());
+ QVERIFY(listView);
+ listView->setBoundsBehavior(boundsBehavior);
+
+ flickWithTouch(&window, {100, 400}, {100, 100});
+ // let it flick some distance
+ QTRY_COMPARE_GT(listView->contentY(), 500);
+ QVERIFY(listView->isFlicking()); // we want to test the case when it's moving and then we flick again
+ const qreal posBeforeSecondFlick = listView->contentY();
+
+ // flick again during flicking, and make sure that it doesn't jump back to the first delegate,
+ // but flicks incrementally further from the position at that time
+ QTest::touchEvent(&window, touchDevice.data()).press(0, {100, 400});
+ QQuickTouchUtils::flush(&window);
+ qCDebug(lcTests) << "second press: contentY" << posBeforeSecondFlick << "->" << listView->contentY();
+ qCDebug(lcTests) << "pressed delegates" << listView->property("pressedDelegates").toList();
+ QVERIFY(listView->contentY() >= posBeforeSecondFlick);
+
+ QTest::qWait(20);
+ QTest::touchEvent(&window, touchDevice.data()).move(0, {100, 300});
+ QQuickTouchUtils::flush(&window);
+ qCDebug(lcTests) << "first move after second press: contentY" << posBeforeSecondFlick << "->" << listView->contentY();
+ QVERIFY(listView->contentY() >= posBeforeSecondFlick);
+
+ QTest::qWait(20);
+ QTest::touchEvent(&window, touchDevice.data()).move(0, {100, 200});
+ QQuickTouchUtils::flush(&window);
+ qCDebug(lcTests) << "second move after second press: contentY" << posBeforeSecondFlick << "->" << listView->contentY();
+ QVERIFY(listView->contentY() >= posBeforeSecondFlick + 100);
+
+ QTest::touchEvent(&window, touchDevice.data()).release(0, {100, 100});
+}
+
+void tst_QQuickListView2::flickWithTouch(QQuickWindow *window, const QPoint &from, const QPoint &to)
+{
+ QTest::touchEvent(window, touchDevice.data()).press(0, from, window);
+ QQuickTouchUtils::flush(window);
+
+ QPoint diff = to - from;
+ for (int i = 1; i <= 8; ++i) {
+ QTest::touchEvent(window, touchDevice.data()).move(0, from + i * diff / 8, window);
+ QQuickTouchUtils::flush(window);
+ }
+ QTest::touchEvent(window, touchDevice.data()).release(0, to, window);
+ QQuickTouchUtils::flush(window);
+}
+
+class SingletonModel : public QStringListModel
+{
+ Q_OBJECT
+public:
+ SingletonModel(QObject* parent = nullptr) : QStringListModel(parent) { }
+};
+
+void tst_QQuickListView2::singletonModelLifetime()
+{
+ // this does not really test any functionality of listview, but we do not have a good way
+ // to unit test QQmlAdaptorModel in isolation.
+ qmlRegisterSingletonType<SingletonModel>("test", 1, 0, "SingletonModel",
+ [](QQmlEngine* , QJSEngine*) -> QObject* { return new SingletonModel; });
+
+ QQmlApplicationEngine engine(testFile("singletonModelLifetime.qml"));
+ // needs event loop iteration for callLater to execute
+ QTRY_VERIFY(engine.rootObjects().first()->property("alive").toBool());
+}
+
+void tst_QQuickListView2::delegateModelRefresh()
+{
+ // Test case originates from QTBUG-100161
+ QQmlApplicationEngine engine(testFile("delegateModelRefresh.qml"));
+ QVERIFY(!engine.rootObjects().isEmpty());
+ // needs event loop iteration for callLater to execute
+ QTRY_VERIFY(engine.rootObjects().first()->property("done").toBool());
+}
+
+void tst_QQuickListView2::wheelSnap()
+{
+ QFETCH(QQuickListView::Orientation, orientation);
+ QFETCH(Qt::LayoutDirection, layoutDirection);
+ QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
+ QFETCH(QQuickItemView::HighlightRangeMode, highlightRangeMode);
+ QFETCH(QPoint, forwardAngleDelta);
+ QFETCH(qreal, snapAlignment);
+ QFETCH(qreal, endExtent);
+ QFETCH(qreal, startExtent);
+ QFETCH(qreal, preferredHighlightBegin);
+ QFETCH(qreal, preferredHighlightEnd);
+
+ // Helpers begin
+ quint64 timestamp = 10;
+ auto sendWheelEvent = [&timestamp](QQuickView *window, const QPoint &angleDelta) {
+ QPoint pos(100, 100);
+ QWheelEvent event(pos, window->mapToGlobal(pos), QPoint(), angleDelta, Qt::NoButton,
+ Qt::NoModifier, Qt::NoScrollPhase, false);
+ event.setAccepted(false);
+ event.setTimestamp(timestamp);
+ QGuiApplication::sendEvent(window, &event);
+ timestamp += 50;
+ };
+
+ auto atEnd = [&layoutDirection, &orientation,
+ &verticalLayoutDirection](QQuickListView *listview) {
+ if (orientation == QQuickListView::Horizontal) {
+ if (layoutDirection == Qt::LeftToRight)
+ return listview->isAtXEnd();
+
+ return listview->isAtXBeginning();
+ } else {
+ if (verticalLayoutDirection == QQuickItemView::VerticalLayoutDirection::TopToBottom)
+ return listview->isAtYEnd();
+
+ return listview->isAtYBeginning();
+ }
+ };
+
+ auto atBegin = [&layoutDirection, &orientation,
+ &verticalLayoutDirection](QQuickListView *listview) {
+ if (orientation == QQuickListView::Horizontal) {
+ if (layoutDirection == Qt::LeftToRight)
+ return listview->isAtXBeginning();
+
+ return listview->isAtXEnd();
+ } else {
+ if (verticalLayoutDirection == QQuickItemView::VerticalLayoutDirection::TopToBottom)
+ return listview->isAtYBeginning();
+
+ return listview->isAtYEnd();
+ }
+ };
+ // Helpers end
+
+ QScopedPointer<QQuickView> window(createView());
+ QTRY_VERIFY(window);
+ QQuickViewTestUtils::moveMouseAway(window.data());
+ window->setSource(testFileUrl("snapOneItem.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject());
+ QTRY_VERIFY(listview);
+
+ listview->setOrientation(orientation);
+ listview->setVerticalLayoutDirection(verticalLayoutDirection);
+ listview->setLayoutDirection(layoutDirection);
+ listview->setHighlightRangeMode(highlightRangeMode);
+ listview->setPreferredHighlightBegin(preferredHighlightBegin);
+ listview->setPreferredHighlightEnd(preferredHighlightEnd);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+
+ QQuickItem *contentItem = listview->contentItem();
+ QTRY_VERIFY(contentItem);
+
+ QSignalSpy currentIndexSpy(listview, &QQuickListView::currentIndexChanged);
+
+ // confirm that a flick hits the next item boundary
+ int indexCounter = 0;
+ sendWheelEvent(window.data(), forwardAngleDelta);
+ QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
+
+ if (orientation == QQuickListView::Vertical)
+ QCOMPARE(listview->contentY(), snapAlignment);
+ else
+ QCOMPARE(listview->contentX(), snapAlignment);
+
+ if (highlightRangeMode == QQuickItemView::StrictlyEnforceRange) {
+ ++indexCounter;
+ QTRY_VERIFY(listview->currentIndex() == indexCounter);
+ }
+
+ // flick to end
+ do {
+ sendWheelEvent(window.data(), forwardAngleDelta);
+ QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
+ if (highlightRangeMode == QQuickItemView::StrictlyEnforceRange) {
+ ++indexCounter;
+ QTRY_VERIFY(listview->currentIndex() == indexCounter);
+ }
+ } while (!atEnd(listview));
+
+ if (orientation == QQuickListView::Vertical)
+ QCOMPARE(listview->contentY(), endExtent);
+ else
+ QCOMPARE(listview->contentX(), endExtent);
+
+ if (highlightRangeMode == QQuickItemView::StrictlyEnforceRange) {
+ QCOMPARE(listview->currentIndex(), listview->count() - 1);
+ QCOMPARE(currentIndexSpy.count(), listview->count() - 1);
+ }
+
+ // flick to start
+ const QPoint backwardAngleDelta(-forwardAngleDelta.x(), -forwardAngleDelta.y());
+ do {
+ sendWheelEvent(window.data(), backwardAngleDelta);
+ QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
+ if (highlightRangeMode == QQuickItemView::StrictlyEnforceRange) {
+ --indexCounter;
+ QTRY_VERIFY(listview->currentIndex() == indexCounter);
+ }
+ } while (!atBegin(listview));
+
+ if (orientation == QQuickListView::Vertical)
+ QCOMPARE(listview->contentY(), startExtent);
+ else
+ QCOMPARE(listview->contentX(), startExtent);
+
+ if (highlightRangeMode == QQuickItemView::StrictlyEnforceRange) {
+ QCOMPARE(listview->currentIndex(), 0);
+ QCOMPARE(currentIndexSpy.count(), (listview->count() - 1) * 2);
+ }
+}
+
+void tst_QQuickListView2::wheelSnap_data()
+{
+ QTest::addColumn<QQuickListView::Orientation>("orientation");
+ QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
+ QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
+ QTest::addColumn<QQuickItemView::HighlightRangeMode>("highlightRangeMode");
+ QTest::addColumn<QPoint>("forwardAngleDelta");
+ QTest::addColumn<qreal>("snapAlignment");
+ QTest::addColumn<qreal>("endExtent");
+ QTest::addColumn<qreal>("startExtent");
+ QTest::addColumn<qreal>("preferredHighlightBegin");
+ QTest::addColumn<qreal>("preferredHighlightEnd");
+
+ QTest::newRow("vertical, top to bottom")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::NoHighlightRange << QPoint(20, -240) << 200.0 << 600.0 << 0.0 << 0.0
+ << 0.0;
+
+ QTest::newRow("vertical, bottom to top")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
+ << QQuickItemView::NoHighlightRange << QPoint(20, 240) << -400.0 << -800.0 << -200.0
+ << 0.0 << 0.0;
+
+ QTest::newRow("horizontal, left to right")
+ << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::NoHighlightRange << QPoint(-240, 20) << 200.0 << 600.0 << 0.0 << 0.0
+ << 0.0;
+
+ QTest::newRow("horizontal, right to left")
+ << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
+ << QQuickItemView::NoHighlightRange << QPoint(240, 20) << -400.0 << -800.0 << -200.0
+ << 0.0 << 0.0;
+
+ QTest::newRow("vertical, top to bottom, enforce range")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::StrictlyEnforceRange << QPoint(20, -240) << 200.0 << 600.0 << 0.0
+ << 0.0 << 0.0;
+
+ QTest::newRow("vertical, bottom to top, enforce range")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
+ << QQuickItemView::StrictlyEnforceRange << QPoint(20, 240) << -400.0 << -800.0 << -200.0
+ << 0.0 << 0.0;
+
+ QTest::newRow("horizontal, left to right, enforce range")
+ << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::StrictlyEnforceRange << QPoint(-240, 20) << 200.0 << 600.0 << 0.0
+ << 0.0 << 0.0;
+
+ QTest::newRow("horizontal, right to left, enforce range")
+ << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
+ << QQuickItemView::StrictlyEnforceRange << QPoint(240, 20) << -400.0 << -800.0 << -200.0
+ << 0.0 << 0.0;
+
+ QTest::newRow("vertical, top to bottom, apply range")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::ApplyRange << QPoint(20, -240) << 200.0 << 600.0 << 0.0 << 0.0
+ << 0.0;
+
+ QTest::newRow("vertical, bottom to top, apply range")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
+ << QQuickItemView::ApplyRange << QPoint(20, 240) << -400.0 << -800.0 << -200.0 << 0.0
+ << 0.0;
+
+ QTest::newRow("horizontal, left to right, apply range")
+ << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::ApplyRange << QPoint(-240, 20) << 200.0 << 600.0 << 0.0 << 0.0
+ << 0.0;
+
+ QTest::newRow("horizontal, right to left, apply range")
+ << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
+ << QQuickItemView::ApplyRange << QPoint(240, 20) << -400.0 << -800.0 << -200.0 << 0.0
+ << 0.0;
+
+ QTest::newRow("vertical, top to bottom with highlightRange")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::NoHighlightRange << QPoint(20, -240) << 190.0 << 600.0 << 0.0 << 10.0
+ << 210.0;
+
+ QTest::newRow("vertical, bottom to top with highlightRange")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
+ << QQuickItemView::NoHighlightRange << QPoint(20, 240) << -390.0 << -800.0 << -200.0
+ << 10.0 << 210.0;
+
+ QTest::newRow("horizontal, left to right with highlightRange")
+ << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::NoHighlightRange << QPoint(-240, 20) << 190.0 << 600.0 << 0.0 << 10.0
+ << 210.0;
+
+ QTest::newRow("horizontal, right to left with highlightRange")
+ << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
+ << QQuickItemView::NoHighlightRange << QPoint(240, 20) << -390.0 << -800.0 << -200.0
+ << 10.0 << 210.0;
+
+ QTest::newRow("vertical, top to bottom, enforce range with highlightRange")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::StrictlyEnforceRange << QPoint(20, -240) << 190.0 << 590.0 << -10.0
+ << 10.0 << 210.0;
+
+ QTest::newRow("vertical, bottom to top, enforce range with highlightRange")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
+ << QQuickItemView::StrictlyEnforceRange << QPoint(20, 240) << -390.0 << -790.0 << -190.0
+ << 10.0 << 210.0;
+
+ QTest::newRow("horizontal, left to right, enforce range with highlightRange")
+ << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::StrictlyEnforceRange << QPoint(-240, 20) << 190.0 << 590.0 << -10.0
+ << 10.0 << 210.0;
+
+ QTest::newRow("horizontal, right to left, enforce range with highlightRange")
+ << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
+ << QQuickItemView::StrictlyEnforceRange << QPoint(240, 20) << -390.0 << -790.0 << -190.0
+ << 10.0 << 210.0;
+
+ QTest::newRow("vertical, top to bottom, apply range with highlightRange")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::ApplyRange << QPoint(20, -240) << 190.0 << 600.0 << 0.0 << 10.0
+ << 210.0;
+
+ QTest::newRow("vertical, bottom to top, apply range with highlightRange")
+ << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
+ << QQuickItemView::ApplyRange << QPoint(20, 240) << -390.0 << -800.0 << -200.0 << 10.0
+ << 210.0;
+
+ QTest::newRow("horizontal, left to right, apply range with highlightRange")
+ << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
+ << QQuickItemView::ApplyRange << QPoint(-240, 20) << 190.0 << 600.0 << 0.0 << 10.0
+ << 210.0;
+
+ QTest::newRow("horizontal, right to left, apply range with highlightRange")
+ << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
+ << QQuickItemView::ApplyRange << QPoint(240, 20) << -390.0 << -800.0 << -200.0 << 10.0
+ << 210.0;
+}
+
+class FriendlyItemView : public QQuickItemView
+{
+ friend class ItemViewAccessor;
+};
+
+class ItemViewAccessor
+{
+public:
+ ItemViewAccessor(QQuickItemView *itemView) :
+ mItemView(reinterpret_cast<FriendlyItemView*>(itemView))
+ {
+ }
+
+ qreal maxXExtent() const
+ {
+ return mItemView->maxXExtent();
+ }
+
+ qreal maxYExtent() const
+ {
+ return mItemView->maxYExtent();
+ }
+
+private:
+ FriendlyItemView *mItemView = nullptr;
+};
+
+void tst_QQuickListView2::maxExtent_data()
+{
+ QTest::addColumn<QString>("qmlFilePath");
+ QTest::addRow("maxXExtent") << "maxXExtent.qml";
+ QTest::addRow("maxYExtent") << "maxYExtent.qml";
+}
+
+void tst_QQuickListView2::maxExtent()
+{
+ QFETCH(QString, qmlFilePath);
+
+ QScopedPointer<QQuickView> window(createView());
+ QVERIFY(window);
+ window->setSource(testFileUrl(qmlFilePath));
+ QVERIFY2(window->status() == QQuickView::Ready, qPrintable(QDebug::toString(window->errors())));
+ window->resize(640, 480);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *view = window->rootObject()->property("view").value<QQuickListView*>();
+ QVERIFY(view);
+ ItemViewAccessor viewAccessor(view);
+ if (view->orientation() == QQuickListView::Vertical)
+ QCOMPARE(viewAccessor.maxXExtent(), 0);
+ else if (view->orientation() == QQuickListView::Horizontal)
+ QCOMPARE(viewAccessor.maxYExtent(), 0);
+}
+
+void tst_QQuickListView2::isCurrentItem_DelegateModel()
+{
+ QScopedPointer<QQuickView> window(createView());
+ window->setSource(testFileUrl("qtbug86744.qml"));
+ window->resize(640, 480);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+ QQuickListView* listView = window->rootObject()->findChild<QQuickListView*>("listView");
+ QVERIFY(listView);
+ QVariant value = listView->itemAtIndex(1)->property("isCurrent");
+ QVERIFY(value.toBool() == true);
+}
+
+void tst_QQuickListView2::isCurrentItem_NoRegressionWithDelegateModelGroups()
+{
+ QScopedPointer<QQuickView> window(createView());
+ window->setSource(testFileUrl("qtbug98315.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+ QQuickListView* listView = window->rootObject()->findChild<QQuickListView*>("listView");
+ QVERIFY(listView);
+
+ QQuickItem *item3 = listView->itemAtIndex(1);
+ QVERIFY(item3);
+ QCOMPARE(item3->property("isCurrent").toBool(), true);
+
+ QObject *item0 = listView->itemAtIndex(0);
+ QVERIFY(item0);
+ QCOMPARE(item0->property("isCurrent").toBool(), false);
+
+ // Press left arrow key -> Item 1 should become current, Item 3 should not
+ // be current anymore. After a previous fix of QTBUG-86744 it was working
+ // incorrectly - see QTBUG-98315
+ QTest::keyPress(window.get(), Qt::Key_Left);
+
+ QTRY_COMPARE(item0->property("isCurrent").toBool(), true);
+ QCOMPARE(item3->property("isCurrent").toBool(), false);
+}
+
+void tst_QQuickListView2::pullbackSparseList() // QTBUG_104679
+{
+ // check if PullbackHeader crashes
+ QScopedPointer<QQuickView> window(createView());
+ QVERIFY(window);
+ window->setSource(testFileUrl("qtbug104679_header.qml"));
+ QVERIFY2(window->status() == QQuickView::Ready, qPrintable(QDebug::toString(window->errors())));
+ window->resize(640, 480);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ // check if PullbackFooter crashes
+ window.reset(createView());
+ QVERIFY(window);
+ window->setSource(testFileUrl("qtbug104679_footer.qml"));
+ QVERIFY2(window->status() == QQuickView::Ready, qPrintable(QDebug::toString(window->errors())));
+ window->resize(640, 480);
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+}
+
+void tst_QQuickListView2::highlightWithBound()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("highlightWithBound.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ QQuickListView *listView = qobject_cast<QQuickListView *>(o.data());
+ QVERIFY(listView);
+ QQuickItem *highlight = listView->highlightItem();
+ QVERIFY(highlight);
+ QCOMPARE(highlight->objectName(), QStringLiteral("highlight"));
+}
+
+void tst_QQuickListView2::sectionIsCompatibleWithBoundComponents()
+{
+ QTest::failOnWarning(".?");
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("sectionBoundComponent.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ QQuickListView *listView = qobject_cast<QQuickListView *>(o.data());
+ QVERIFY(listView);
+ QTRY_COMPARE(listView->currentSection(), "42");
+}
+
+void tst_QQuickListView2::sectionGeometryChange()
+{
+ QScopedPointer<QQuickView> window(createView());
+ QTRY_VERIFY(window);
+ window->setSource(testFileUrl("sectionGeometryChange.qml"));
+ window->show();
+ QVERIFY(QTest::qWaitForWindowExposed(window.data()));
+
+ QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
+ QTRY_VERIFY(listview);
+
+ QQuickItem *contentItem = listview->contentItem();
+ QTRY_VERIFY(contentItem);
+ QVERIFY(QQuickTest::qWaitForPolish(listview));
+
+ QQuickItem *section1 = findItem<QQuickItem>(contentItem, "Section1");
+ QVERIFY(section1);
+ QQuickItem *element1 = findItem<QQuickItem>(contentItem, "Element1");
+ QVERIFY(element1);
+
+ QCOMPARE(element1->y(), section1->y() + section1->height());
+
+ // Update the height of the section delegate and verify that the next element is not overlapping
+ section1->setHeight(section1->height() + 10);
+ QTRY_COMPARE(element1->y(), section1->y() + section1->height());
+}
+
+void tst_QQuickListView2::areaZeroviewDoesNotNeedlesslyPopulateWholeModel()
+{
+ QTest::failOnWarning(QRegularExpression(".*"));
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("areaZeroView.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ std::unique_ptr<QObject> root(c.create());
+ QVERIFY(root);
+ auto delegateCreationCounter = [&]() {
+ return root->property("delegateCreationCounter").toInt();
+ };
+ // wait for onComplete to be settled
+ QTRY_VERIFY(delegateCreationCounter() != 0);
+ auto view = qobject_cast<QQuickListView *>(qmlContext(root.get())->objectForName("lv"));
+ QVERIFY(view);
+ QCOMPARE(view->count(), 6'000);
+ // we use 100, which is < 6000, but larger than the actual expected value
+ // that's to give the test some leniency in case the ListView implementation
+ // changes in the future to instantiate a few more items outside of the viewport
+ QVERIFY(delegateCreationCounter() < 100);
+}
+
+void tst_QQuickListView2::delegateContextHandling()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("delegateContextHandling.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ std::unique_ptr<QObject> o(c.create());
+ QVERIFY(o);
+
+ for (int i = 0; i < 10; ++i) {
+ QQuickItem *delegate = nullptr;
+ QMetaObject::invokeMethod(o.get(), "toggle", Q_RETURN_ARG(QQuickItem *, delegate));
+ QVERIFY(delegate);
+ }
+
+}
+
+class TestFetchMoreModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ QVariant data(const QModelIndex& index, int role) const override
+ {
+ if (role == Qt::DisplayRole)
+ return QString::number(index.row());
+ return {};
+ }
+
+ int columnCount(const QModelIndex&) const override { return 1; }
+
+ int rowCount(const QModelIndex& parent) const override
+ {
+ return parent.isValid() ? 0 : m_lines;
+ }
+
+ QModelIndex parent(const QModelIndex&) const override { return {}; }
+
+ bool canFetchMore(const QModelIndex &) const override { return true; }
+
+ void fetchMore(const QModelIndex & parent) override
+ {
+ if (Q_UNLIKELY(parent.isValid()))
+ return;
+ beginInsertRows(parent, m_lines, m_lines);
+ m_lines++;
+ endInsertRows();
+ }
+
+ int m_lines = 3;
+};
+
+void tst_QQuickListView2::fetchMore_data()
+{
+ QTest::addColumn<bool>("reuseItems");
+ QTest::addColumn<int>("cacheBuffer");
+
+ QTest::newRow("no reuseItems, default buffer") << false << -1;
+ QTest::newRow("reuseItems, default buffer") << true << -1;
+ QTest::newRow("no reuseItems, no buffer") << false << 0;
+ QTest::newRow("reuseItems, no buffer") << true << 0;
+ QTest::newRow("no reuseItems, buffer 100 px") << false << 100;
+ QTest::newRow("reuseItems, buffer 100 px") << true << 100;
+}
+
+void tst_QQuickListView2::fetchMore() // QTBUG-95107
+{
+ QFETCH(bool, reuseItems);
+ QFETCH(int, cacheBuffer);
+
+ TestFetchMoreModel model;
+ qmlRegisterSingletonInstance("org.qtproject.Test", 1, 0, "FetchMoreModel", &model);
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl("fetchMore.qml")));
+ auto *listView = qobject_cast<QQuickListView*>(window.rootObject());
+ QVERIFY(listView);
+ listView->setReuseItems(reuseItems);
+ if (cacheBuffer >= 0)
+ listView->setCacheBuffer(cacheBuffer);
+
+ for (int i = 0; i < 3; ++i) {
+ const int rowCount = listView->count();
+ if (lcTests().isDebugEnabled()) QTest::qWait(1000);
+ listView->flick(0, -5000);
+ QTRY_VERIFY(!listView->isMoving());
+ qCDebug(lcTests) << "after flick: contentY" << listView->contentY()
+ << "rows" << rowCount << "->" << listView->count();
+ QCOMPARE_GT(listView->count(), rowCount);
+ QCOMPARE_GE(model.m_lines, listView->count()); // fetchMore() was called
+ }
+}
+
+void tst_QQuickListView2::changingOrientationResetsPreviousAxisValues_data()
+{
+ QTest::addColumn<QByteArray>("sourceFile");
+ QTest::newRow("ObjectModel") << QByteArray("changingOrientationWithObjectModel.qml");
+ QTest::newRow("ListModel") << QByteArray("changingOrientationWithListModel.qml");
+}
+
+void tst_QQuickListView2::changingOrientationResetsPreviousAxisValues() // QTBUG-115696
+{
+ QFETCH(QByteArray, sourceFile);
+
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl(QString::fromLatin1(sourceFile))));
+ auto *listView = qobject_cast<QQuickListView *>(window.rootObject());
+ QVERIFY(listView);
+
+ // Starts of with vertical orientation. X should be 0 for all delegates, but not Y.
+ QVERIFY(listView->property("isXReset").toBool());
+ QVERIFY(!listView->property("isYReset").toBool());
+
+ listView->setOrientation(QQuickListView::Orientation::Horizontal);
+
+ // Y should be 0 for all delegates, but not X.
+ QVERIFY(!listView->property("isXReset").toBool());
+ QVERIFY(listView->property("isYReset").toBool());
+
+ listView->setOrientation(QQuickListView::Orientation::Vertical);
+
+ // X should be 0 for all delegates, but not Y.
+ QVERIFY(listView->property("isXReset").toBool());
+ QVERIFY(!listView->property("isYReset").toBool());
+}
+
+void tst_QQuickListView2::bindingDirectlyOnPositionInHeaderAndFooterDelegates_data()
+{
+ QTest::addColumn<QByteArray>("sourceFile");
+ QTest::addColumn<qreal(QQuickItem::*)()const>("pos");
+ QTest::addColumn<qreal(QQuickItem::*)()const>("size");
+ QTest::newRow("XPosition") << QByteArray("bindOnHeaderAndFooterXPosition.qml") << &QQuickItem::x << &QQuickItem::width;
+ QTest::newRow("YPosition") << QByteArray("bindOnHeaderAndFooterYPosition.qml") << &QQuickItem::y << &QQuickItem::height;
+}
+void tst_QQuickListView2::bindingDirectlyOnPositionInHeaderAndFooterDelegates()
+{
+
+ typedef qreal (QQuickItem::*position_func_t)() const;
+ QFETCH(QByteArray, sourceFile);
+ QFETCH(position_func_t, pos);
+ QFETCH(position_func_t, size);
+
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl(QString::fromLatin1(sourceFile))));
+ auto *listView = qobject_cast<QQuickListView *>(window.rootObject());
+ QVERIFY(listView);
+
+ const qreal widthOrHeight = (listView->*size)();
+
+ QCOMPARE((listView->headerItem()->*pos)(), (widthOrHeight - 50) / 2);
+ QCOMPARE((listView->footerItem()->*pos)(), (widthOrHeight - 50) / 2);
+
+ // Verify that the "regular" delegate items, don't honor x and y bindings.
+ // This should only be allowed for header and footer delegates.
+ for (int i = 0; i < listView->count(); ++i)
+ QCOMPARE((listView->itemAtIndex(i)->*pos)(), 0);
+}
+
+void tst_QQuickListView2::clearObjectListModel()
+{
+ QQmlEngine engine;
+ QQmlComponent delegate(&engine);
+
+ // Need one required property to trigger the incremental rebuilding of metaobjects.
+ delegate.setData("import QtQuick\nItem { required property int index }", QUrl());
+
+ QQuickListView list;
+ engine.setContextForObject(&list, engine.rootContext());
+ list.setDelegate(&delegate);
+ list.setWidth(640);
+ list.setHeight(480);
+
+ QScopedPointer modelObject(new QObject);
+
+ // Use a list that might also carry something non-QObject
+
+ list.setModel(QVariantList {
+ QVariant::fromValue(modelObject.data()),
+ QVariant::fromValue(modelObject.data())
+ });
+
+ QVERIFY(list.itemAtIndex(0));
+
+ modelObject.reset();
+
+ // list should not access dangling pointer from old model data anymore.
+ list.setModel(QVariantList());
+
+ QVERIFY(!list.itemAtIndex(0));
+}
+
+QTEST_MAIN(tst_QQuickListView2)
+
+#include "tst_qquicklistview2.moc"