1194 lines
48 KiB
C++
1194 lines
48 KiB
C++
// Copyright (C) 2021 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#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 delegateContextHandling();
|
|
void fetchMore_data();
|
|
void fetchMore();
|
|
|
|
void changingOrientationResetsPreviousAxisValues_data();
|
|
void changingOrientationResetsPreviousAxisValues();
|
|
|
|
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::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 = [×tamp](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::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"
|