1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtTest/QtTest>
43 #include <QtWidgets/QStringListModel>
44 #include <QtDeclarative/qsgview.h>
45 #include <QtDeclarative/qdeclarativeengine.h>
46 #include <QtDeclarative/qdeclarativecontext.h>
47 #include <QtDeclarative/qdeclarativeexpression.h>
48 #include <QtDeclarative/private/qsgitem_p.h>
49 #include <QtDeclarative/private/qsglistview_p.h>
50 #include <QtDeclarative/private/qsgtext_p.h>
51 #include <QtDeclarative/private/qsgvisualitemmodel_p.h>
52 #include <QtDeclarative/private/qdeclarativelistmodel_p.h>
53 #include <QtDeclarative/private/qlistmodelinterface_p.h>
54 #include <QtDeclarative/private/qdeclarativechangeset_p.h>
55 #include "../shared/util.h"
56 #include "../../../shared/util.h"
57 #include "incrementalmodel.h"
60 Q_DECLARE_METATYPE(Qt::LayoutDirection)
61 Q_DECLARE_METATYPE(QSGListView::Orientation)
63 class tst_QSGListView : public QObject
71 void cleanupTestCase();
72 // Test both QListModelInterface and QAbstractItemModel model types
73 void qListModelInterface_items();
74 void qAbstractItemModel_items();
76 void qListModelInterface_changed();
77 void qAbstractItemModel_changed();
79 void qListModelInterface_inserted();
80 void qListModelInterface_inserted_more();
81 void qListModelInterface_inserted_more_data();
82 void qAbstractItemModel_inserted();
83 void qAbstractItemModel_inserted_more();
84 void qAbstractItemModel_inserted_more_data();
86 void qListModelInterface_removed();
87 void qAbstractItemModel_removed();
89 void qListModelInterface_moved();
90 void qListModelInterface_moved_data();
91 void qAbstractItemModel_moved();
92 void qAbstractItemModel_moved_data();
94 void multipleChanges();
95 void multipleChanges_data();
97 void qListModelInterface_clear();
98 void qAbstractItemModel_clear();
100 void swapWithFirstItem();
102 void currentIndex_delayedItemCreation();
103 void currentIndex_delayedItemCreation_data();
105 void noCurrentIndex();
107 void enforceRange_withoutHighlight();
110 void sectionsPositioning();
111 void sectionsDelegate();
113 void positionViewAtIndex();
115 void propertyChanges();
116 void componentChanges();
119 void manualHighlight();
123 void header_delayItemCreation();
128 void resizeViewAndRepaint();
129 void sizeLessThan1();
131 void resizeDelegate();
132 void resizeFirstDelegate();
135 void incrementalModel();
139 void onRemove_data();
141 void test_mirroring();
143 void creationContext();
144 void snapToItem_data();
148 template <class T> void items();
149 template <class T> void changed();
150 template <class T> void inserted();
151 template <class T> void inserted_more();
152 template <class T> void removed(bool animated);
153 template <class T> void moved();
154 template <class T> void clear();
155 QSGView *createView();
156 void flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration);
157 QSGItem *findVisibleChild(QSGItem *parent, const QString &objectName);
159 T *findItem(QSGItem *parent, const QString &id, int index=-1);
161 QList<T*> findItems(QSGItem *parent, const QString &objectName);
162 void dumpTree(QSGItem *parent, int depth = 0);
164 void inserted_more_data();
168 void tst_QSGListView::initTestCase()
172 void tst_QSGListView::cleanupTestCase()
176 class TestObject : public QObject
180 Q_PROPERTY(bool error READ error WRITE setError NOTIFY changedError)
181 Q_PROPERTY(bool animate READ animate NOTIFY changedAnim)
182 Q_PROPERTY(bool invalidHighlight READ invalidHighlight NOTIFY changedHl)
183 Q_PROPERTY(int cacheBuffer READ cacheBuffer NOTIFY changedCacheBuffer)
186 TestObject(QObject *parent = 0)
187 : QObject(parent), mError(true), mAnimate(false), mInvalidHighlight(false)
190 bool error() const { return mError; }
191 void setError(bool err) { mError = err; emit changedError(); }
193 bool animate() const { return mAnimate; }
194 void setAnimate(bool anim) { mAnimate = anim; emit changedAnim(); }
196 bool invalidHighlight() const { return mInvalidHighlight; }
197 void setInvalidHighlight(bool invalid) { mInvalidHighlight = invalid; emit changedHl(); }
199 int cacheBuffer() const { return mCacheBuffer; }
200 void setCacheBuffer(int buffer) { mCacheBuffer = buffer; emit changedCacheBuffer(); }
206 void changedCacheBuffer();
211 bool mInvalidHighlight;
216 void tst_qsglistview_move(int from, int to, int n, T *items)
219 // Only move forwards - flip if backwards moving
227 items->move(from, to);
231 typename T::ConstIterator it=items->begin(); it += from+n;
232 for (; i<to-from; ++i,++it)
233 replaced.append(*it);
235 it=items->begin(); it += from;
236 for (; i<n; ++i,++it)
237 replaced.append(*it);
238 typename T::ConstIterator f=replaced.begin();
239 typename T::Iterator t=items->begin(); t += from;
240 for (; f != replaced.end(); ++f, ++t)
245 class TestModel : public QListModelInterface
249 TestModel(QObject *parent = 0) : QListModelInterface(parent) {}
252 enum Roles { Name, Number };
254 QString name(int index) const { return list.at(index).first; }
255 QString number(int index) const { return list.at(index).second; }
257 int count() const { return list.count(); }
259 QList<int> roles() const { return QList<int>() << Name << Number; }
260 QString toString(int role) const {
271 QVariant data(int index, int role) const
274 return list.at(index).first;
276 return list.at(index).second;
279 QHash<int, QVariant> data(int index, const QList<int> &roles) const {
280 QHash<int,QVariant> returnHash;
282 for (int i = 0; i < roles.size(); ++i) {
283 int role = roles.at(i);
287 info = list.at(index).first;
290 info = list.at(index).second;
295 returnHash.insert(role, info);
300 void addItem(const QString &name, const QString &number) {
301 list.append(QPair<QString,QString>(name, number));
302 emit itemsInserted(list.count()-1, 1);
305 void insertItem(int index, const QString &name, const QString &number) {
306 list.insert(index, QPair<QString,QString>(name, number));
307 emit itemsInserted(index, 1);
310 void insertItems(int index, const QList<QPair<QString, QString> > &items) {
311 for (int i=0; i<items.count(); i++)
312 list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
313 emit itemsInserted(index, items.count());
316 void removeItem(int index) {
317 list.removeAt(index);
318 emit itemsRemoved(index, 1);
321 void removeItems(int index, int count) {
324 list.removeAt(index);
325 emit itemsRemoved(index, count);
328 void moveItem(int from, int to) {
330 emit itemsMoved(from, to, 1);
333 void moveItems(int from, int to, int count) {
334 tst_qsglistview_move(from, to, count, &list);
335 emit itemsMoved(from, to, count);
338 void modifyItem(int index, const QString &name, const QString &number) {
339 list[index] = QPair<QString,QString>(name, number);
340 emit itemsChanged(index, 1, roles());
344 int count = list.count();
346 emit itemsRemoved(0, count);
350 QList<QPair<QString,QString> > list;
354 class TestModel2 : public QAbstractListModel
357 enum Roles { Name = Qt::UserRole+1, Number = Qt::UserRole+2 };
359 TestModel2(QObject *parent=0) : QAbstractListModel(parent) {
360 QHash<int, QByteArray> roles;
361 roles[Name] = "name";
362 roles[Number] = "number";
366 int rowCount(const QModelIndex &parent=QModelIndex()) const { Q_UNUSED(parent); return list.count(); }
367 QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const {
370 rv = list.at(index.row()).first;
371 else if (role == Number)
372 rv = list.at(index.row()).second;
377 int count() const { return rowCount(); }
378 QString name(int index) const { return list.at(index).first; }
379 QString number(int index) const { return list.at(index).second; }
381 void addItem(const QString &name, const QString &number) {
382 emit beginInsertRows(QModelIndex(), list.count(), list.count());
383 list.append(QPair<QString,QString>(name, number));
384 emit endInsertRows();
387 void addItems(const QList<QPair<QString, QString> > &items) {
388 emit beginInsertRows(QModelIndex(), list.count(), list.count()+items.count()-1);
389 for (int i=0; i<items.count(); i++)
390 list.append(QPair<QString,QString>(items[i].first, items[i].second));
391 emit endInsertRows();
394 void insertItem(int index, const QString &name, const QString &number) {
395 emit beginInsertRows(QModelIndex(), index, index);
396 list.insert(index, QPair<QString,QString>(name, number));
397 emit endInsertRows();
400 void insertItems(int index, const QList<QPair<QString, QString> > &items) {
401 emit beginInsertRows(QModelIndex(), index, index+items.count()-1);
402 for (int i=0; i<items.count(); i++)
403 list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
404 emit endInsertRows();
407 void removeItem(int index) {
408 emit beginRemoveRows(QModelIndex(), index, index);
409 list.removeAt(index);
410 emit endRemoveRows();
413 void removeItems(int index, int count) {
414 emit beginRemoveRows(QModelIndex(), index, index+count-1);
416 list.removeAt(index);
417 emit endRemoveRows();
420 void moveItem(int from, int to) {
421 emit beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
426 void moveItems(int from, int to, int count) {
427 emit beginMoveRows(QModelIndex(), from, from+count-1, QModelIndex(), to > from ? to+count : to);
428 tst_qsglistview_move(from, to, count, &list);
432 void modifyItem(int idx, const QString &name, const QString &number) {
433 list[idx] = QPair<QString,QString>(name, number);
434 emit dataChanged(index(idx,0), index(idx,0));
438 int count = list.count();
439 emit beginRemoveRows(QModelIndex(), 0, count-1);
441 emit endRemoveRows();
445 QList<QPair<QString,QString> > list;
448 tst_QSGListView::tst_QSGListView()
453 void tst_QSGListView::items()
455 QSGView *canvas = createView();
458 model.addItem("Fred", "12345");
459 model.addItem("John", "2345");
460 model.addItem("Bob", "54321");
462 QDeclarativeContext *ctxt = canvas->rootContext();
463 ctxt->setContextProperty("testModel", &model);
465 TestObject *testObject = new TestObject;
466 ctxt->setContextProperty("testObject", testObject);
468 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
469 qApp->processEvents();
471 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
472 QTRY_VERIFY(listview != 0);
474 QSGItem *contentItem = listview->contentItem();
475 QTRY_VERIFY(contentItem != 0);
477 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
478 QTRY_VERIFY(testObject->error() == false);
480 QTRY_VERIFY(listview->highlightItem() != 0);
481 QTRY_COMPARE(listview->count(), model.count());
482 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
483 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
485 // current item should be first item
486 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
488 for (int i = 0; i < model.count(); ++i) {
489 QSGText *name = findItem<QSGText>(contentItem, "textName", i);
490 QTRY_VERIFY(name != 0);
491 QTRY_COMPARE(name->text(), model.name(i));
492 QSGText *number = findItem<QSGText>(contentItem, "textNumber", i);
493 QTRY_VERIFY(number != 0);
494 QTRY_COMPARE(number->text(), model.number(i));
497 // switch to other delegate
498 testObject->setAnimate(true);
499 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
500 QTRY_VERIFY(testObject->error() == false);
501 QTRY_VERIFY(listview->currentItem());
503 // set invalid highlight
504 testObject->setInvalidHighlight(true);
505 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
506 QTRY_VERIFY(testObject->error() == false);
507 QTRY_VERIFY(listview->currentItem());
508 QTRY_VERIFY(listview->highlightItem() == 0);
510 // back to normal highlight
511 testObject->setInvalidHighlight(false);
512 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
513 QTRY_VERIFY(testObject->error() == false);
514 QTRY_VERIFY(listview->currentItem());
515 QTRY_VERIFY(listview->highlightItem() != 0);
517 // set an empty model and confirm that items are destroyed
519 ctxt->setContextProperty("testModel", &model2);
521 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
522 QTRY_VERIFY(itemCount == 0);
524 QTRY_COMPARE(listview->highlightResizeSpeed(), 1000.0);
525 QTRY_COMPARE(listview->highlightMoveSpeed(), 1000.0);
533 void tst_QSGListView::changed()
535 QSGView *canvas = createView();
538 model.addItem("Fred", "12345");
539 model.addItem("John", "2345");
540 model.addItem("Bob", "54321");
542 QDeclarativeContext *ctxt = canvas->rootContext();
543 ctxt->setContextProperty("testModel", &model);
545 TestObject *testObject = new TestObject;
546 ctxt->setContextProperty("testObject", testObject);
548 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
549 qApp->processEvents();
551 QSGFlickable *listview = findItem<QSGFlickable>(canvas->rootObject(), "list");
552 QTRY_VERIFY(listview != 0);
554 QSGItem *contentItem = listview->contentItem();
555 QTRY_VERIFY(contentItem != 0);
557 model.modifyItem(1, "Will", "9876");
558 QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
559 QTRY_VERIFY(name != 0);
560 QTRY_COMPARE(name->text(), model.name(1));
561 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
562 QTRY_VERIFY(number != 0);
563 QTRY_COMPARE(number->text(), model.number(1));
570 void tst_QSGListView::inserted()
572 QSGView *canvas = createView();
576 model.addItem("Fred", "12345");
577 model.addItem("John", "2345");
578 model.addItem("Bob", "54321");
580 QDeclarativeContext *ctxt = canvas->rootContext();
581 ctxt->setContextProperty("testModel", &model);
583 TestObject *testObject = new TestObject;
584 ctxt->setContextProperty("testObject", testObject);
586 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
587 qApp->processEvents();
589 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
590 QTRY_VERIFY(listview != 0);
592 QSGItem *contentItem = listview->contentItem();
593 QTRY_VERIFY(contentItem != 0);
595 model.insertItem(1, "Will", "9876");
597 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
598 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
600 QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
601 QTRY_VERIFY(name != 0);
602 QTRY_COMPARE(name->text(), model.name(1));
603 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
604 QTRY_VERIFY(number != 0);
605 QTRY_COMPARE(number->text(), model.number(1));
607 // Confirm items positioned correctly
608 for (int i = 0; i < model.count(); ++i) {
609 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
610 QTRY_COMPARE(item->y(), i*20.0);
613 model.insertItem(0, "Foo", "1111"); // zero index, and current item
615 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
616 QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
618 name = findItem<QSGText>(contentItem, "textName", 0);
619 QTRY_VERIFY(name != 0);
620 QTRY_COMPARE(name->text(), model.name(0));
621 number = findItem<QSGText>(contentItem, "textNumber", 0);
622 QTRY_VERIFY(number != 0);
623 QTRY_COMPARE(number->text(), model.number(0));
625 QTRY_COMPARE(listview->currentIndex(), 1);
627 // Confirm items positioned correctly
628 for (int i = 0; i < model.count(); ++i) {
629 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
630 QTRY_COMPARE(item->y(), i*20.0);
633 for (int i = model.count(); i < 30; ++i)
634 model.insertItem(i, "Hello", QString::number(i));
636 listview->setContentY(80);
638 // Insert item outside visible area
639 model.insertItem(1, "Hello", "1324");
641 QTRY_VERIFY(listview->contentY() == 80);
643 // Confirm items positioned correctly
644 for (int i = 5; i < 5+15; ++i) {
645 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
646 if (!item) qWarning() << "Item" << i << "not found";
648 QTRY_COMPARE(item->y(), i*20.0 - 20.0);
651 // QTRY_COMPARE(listview->contentItemHeight(), model.count() * 20.0);
655 model.insertItem(0, "Hello", "1234");
656 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
658 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
660 QCOMPARE(item->y(), 0.);
661 QVERIFY(listview->contentY() == 0);
668 void tst_QSGListView::inserted_more()
670 QFETCH(qreal, contentY);
671 QFETCH(int, insertIndex);
672 QFETCH(int, insertCount);
673 QFETCH(qreal, itemsOffsetAfterMove);
677 QSGView *canvas = createView();
681 for (int i = 0; i < 30; i++)
682 model.addItem("Item" + QString::number(i), "");
684 QDeclarativeContext *ctxt = canvas->rootContext();
685 ctxt->setContextProperty("testModel", &model);
687 TestObject *testObject = new TestObject;
688 ctxt->setContextProperty("testObject", testObject);
690 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
691 qApp->processEvents();
693 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
694 QTRY_VERIFY(listview != 0);
695 QSGItem *contentItem = listview->contentItem();
696 QTRY_VERIFY(contentItem != 0);
698 listview->setContentY(contentY);
700 QList<QPair<QString, QString> > newData;
701 for (int i=0; i<insertCount; i++)
702 newData << qMakePair(QString("value %1").arg(i), QString::number(i));
703 model.insertItems(insertIndex, newData);
704 QTRY_COMPARE(listview->property("count").toInt(), model.count());
706 // check visibleItems.first() is in correct position
707 QSGItem *item0 = findItem<QSGItem>(contentItem, "wrapper", 0);
709 QCOMPARE(item0->y(), itemsOffsetAfterMove);
711 QList<QSGItem*> items = findItems<QSGItem>(contentItem, "wrapper");
712 int firstVisibleIndex = -1;
713 for (int i=0; i<items.count(); i++) {
714 if (items[i]->y() >= contentY) {
715 QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
716 firstVisibleIndex = e.evaluate().toInt();
720 QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
722 // Confirm items positioned correctly and indexes correct
723 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
724 for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
725 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
726 QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
727 QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
728 name = findItem<QSGText>(contentItem, "textName", i);
730 QTRY_COMPARE(name->text(), model.name(i));
731 number = findItem<QSGText>(contentItem, "textNumber", i);
732 QVERIFY(number != 0);
733 QTRY_COMPARE(number->text(), model.number(i));
740 void tst_QSGListView::inserted_more_data()
742 QTest::addColumn<qreal>("contentY");
743 QTest::addColumn<int>("insertIndex");
744 QTest::addColumn<int>("insertCount");
745 QTest::addColumn<qreal>("itemsOffsetAfterMove");
747 QTest::newRow("add 1, before visible items")
750 << -20.0; // insert above first visible i.e. 0 is at -20, first visible should not move
752 QTest::newRow("add multiple, before visible")
755 << -20.0 * 3; // again first visible should not move
757 QTest::newRow("add 1, at start of visible, content at start")
762 QTest::newRow("add multiple, start of visible, content at start")
767 QTest::newRow("add 1, at start of visible, content not at start")
772 QTest::newRow("add multiple, at start of visible, content not at start")
778 QTest::newRow("add 1, at end of visible, content at start")
783 QTest::newRow("add 1, at end of visible, content at start")
788 QTest::newRow("add 1, at end of visible, content not at start")
793 QTest::newRow("add multiple, at end of visible, content not at start")
799 QTest::newRow("add 1, after visible, content at start")
804 QTest::newRow("add 1, after visible, content at start")
809 QTest::newRow("add 1, after visible, content not at start")
814 QTest::newRow("add multiple, after visible, content not at start")
821 void tst_QSGListView::removed(bool animated)
823 QSGView *canvas = createView();
826 for (int i = 0; i < 50; i++)
827 model.addItem("Item" + QString::number(i), "");
829 QDeclarativeContext *ctxt = canvas->rootContext();
830 ctxt->setContextProperty("testModel", &model);
832 TestObject *testObject = new TestObject;
833 ctxt->setContextProperty("testObject", testObject);
835 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
837 qApp->processEvents();
839 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
840 QTRY_VERIFY(listview != 0);
842 QSGItem *contentItem = listview->contentItem();
843 QTRY_VERIFY(contentItem != 0);
846 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
848 QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
849 QTRY_VERIFY(name != 0);
850 QTRY_COMPARE(name->text(), model.name(1));
851 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
852 QTRY_VERIFY(number != 0);
853 QTRY_COMPARE(number->text(), model.number(1));
855 // Confirm items positioned correctly
856 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
857 for (int i = 0; i < model.count() && i < itemCount; ++i) {
858 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
859 if (!item) qWarning() << "Item" << i << "not found";
861 QTRY_VERIFY(item->y() == i*20);
864 // Remove first item (which is the current item);
865 model.removeItem(0); // post: top item starts at 20
866 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
868 name = findItem<QSGText>(contentItem, "textName", 0);
869 QTRY_VERIFY(name != 0);
870 QTRY_COMPARE(name->text(), model.name(0));
871 number = findItem<QSGText>(contentItem, "textNumber", 0);
872 QTRY_VERIFY(number != 0);
873 QTRY_COMPARE(number->text(), model.number(0));
875 // Confirm items positioned correctly
876 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
877 for (int i = 0; i < model.count() && i < itemCount; ++i) {
878 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
879 if (!item) qWarning() << "Item" << i << "not found";
881 QTRY_COMPARE(item->y(),i*20.0 + 20.0);
884 // Remove items not visible
885 model.removeItem(18);
886 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
888 // Confirm items positioned correctly
889 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
890 for (int i = 0; i < model.count() && i < itemCount; ++i) {
891 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
892 if (!item) qWarning() << "Item" << i << "not found";
894 QTRY_COMPARE(item->y(),i*20.0+20.0);
897 // Remove items before visible
898 listview->setContentY(80);
899 listview->setCurrentIndex(10);
901 model.removeItem(1); // post: top item will be at 40
902 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
904 // Confirm items positioned correctly
905 for (int i = 2; i < 18; ++i) {
906 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
907 if (!item) qWarning() << "Item" << i << "not found";
909 QTRY_COMPARE(item->y(),40+i*20.0);
912 // Remove current index
913 QTRY_VERIFY(listview->currentIndex() == 9);
914 QSGItem *oldCurrent = listview->currentItem();
917 QTRY_COMPARE(listview->currentIndex(), 9);
918 QTRY_VERIFY(listview->currentItem() != oldCurrent);
920 listview->setContentY(40); // That's the top now
921 // let transitions settle.
924 // Confirm items positioned correctly
925 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
926 for (int i = 0; i < model.count() && i < itemCount; ++i) {
927 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
928 if (!item) qWarning() << "Item" << i << "not found";
930 QTRY_COMPARE(item->y(),40+i*20.0);
933 // remove current item beyond visible items.
934 listview->setCurrentIndex(20);
935 listview->setContentY(40);
936 model.removeItem(20);
938 QTRY_COMPARE(listview->currentIndex(), 20);
939 QTRY_VERIFY(listview->currentItem() != 0);
941 // remove item before current, but visible
942 listview->setCurrentIndex(8);
943 oldCurrent = listview->currentItem();
946 QTRY_COMPARE(listview->currentIndex(), 7);
947 QTRY_VERIFY(listview->currentItem() == oldCurrent);
949 listview->setContentY(80);
952 // remove all visible items
953 model.removeItems(1, 18);
954 QTRY_COMPARE(listview->count() , model.count());
956 // Confirm items positioned correctly
957 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
958 for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
959 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i+2);
960 if (!item) qWarning() << "Item" << i+2 << "not found";
962 QTRY_COMPARE(item->y(),80+i*20.0);
965 model.removeItems(1, 17);
966 QTRY_COMPARE(listview->count() , model.count());
968 model.removeItems(2, 1);
969 QTRY_COMPARE(listview->count() , model.count());
971 model.addItem("New", "1");
972 QTRY_COMPARE(listview->count() , model.count());
974 QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", model.count()-1));
975 QCOMPARE(name->text(), QString("New"));
977 // Add some more items so that we don't run out
979 for (int i = 0; i < 50; i++)
980 model.addItem("Item" + QString::number(i), "");
983 listview->setCurrentIndex(0);
984 listview->setContentY(30);
986 QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", 0));
988 // QTBUG-19198 move to end and remove all visible items one at a time.
989 listview->positionViewAtEnd();
990 for (int i = 0; i < 18; ++i)
991 model.removeItems(model.count() - 1, 1);
992 QTRY_VERIFY(findItems<QSGItem>(contentItem, "wrapper").count() > 16);
999 void tst_QSGListView::clear()
1001 QSGView *canvas = createView();
1004 for (int i = 0; i < 30; i++)
1005 model.addItem("Item" + QString::number(i), "");
1007 QDeclarativeContext *ctxt = canvas->rootContext();
1008 ctxt->setContextProperty("testModel", &model);
1010 TestObject *testObject = new TestObject;
1011 ctxt->setContextProperty("testObject", testObject);
1013 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1014 qApp->processEvents();
1016 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1017 QTRY_VERIFY(listview != 0);
1019 QSGItem *contentItem = listview->contentItem();
1020 QTRY_VERIFY(contentItem != 0);
1024 QTRY_VERIFY(listview->count() == 0);
1025 QTRY_VERIFY(listview->currentItem() == 0);
1026 QTRY_VERIFY(listview->contentY() == 0);
1027 QVERIFY(listview->currentIndex() == -1);
1029 // confirm sanity when adding an item to cleared list
1030 model.addItem("New", "1");
1031 QTRY_VERIFY(listview->count() == 1);
1032 QVERIFY(listview->currentItem() != 0);
1033 QVERIFY(listview->currentIndex() == 0);
1040 void tst_QSGListView::moved()
1042 QFETCH(qreal, contentY);
1046 QFETCH(qreal, itemsOffsetAfterMove);
1050 QSGView *canvas = createView();
1054 for (int i = 0; i < 30; i++)
1055 model.addItem("Item" + QString::number(i), "");
1057 QDeclarativeContext *ctxt = canvas->rootContext();
1058 ctxt->setContextProperty("testModel", &model);
1060 TestObject *testObject = new TestObject;
1061 ctxt->setContextProperty("testObject", testObject);
1063 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1064 qApp->processEvents();
1066 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1067 QTRY_VERIFY(listview != 0);
1069 QSGItem *contentItem = listview->contentItem();
1070 QTRY_VERIFY(contentItem != 0);
1072 QSGItem *currentItem = listview->currentItem();
1073 QTRY_VERIFY(currentItem != 0);
1075 listview->setContentY(contentY);
1076 model.moveItems(from, to, count);
1078 // wait for items to move
1081 QList<QSGItem*> items = findItems<QSGItem>(contentItem, "wrapper");
1082 int firstVisibleIndex = -1;
1083 for (int i=0; i<items.count(); i++) {
1084 if (items[i]->y() >= contentY) {
1085 QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
1086 firstVisibleIndex = e.evaluate().toInt();
1090 QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
1092 // Confirm items positioned correctly and indexes correct
1093 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1094 for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
1095 if (i >= firstVisibleIndex + 16) // index has moved out of view
1097 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1098 QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1099 QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
1100 name = findItem<QSGText>(contentItem, "textName", i);
1102 QTRY_COMPARE(name->text(), model.name(i));
1103 number = findItem<QSGText>(contentItem, "textNumber", i);
1104 QVERIFY(number != 0);
1105 QTRY_COMPARE(number->text(), model.number(i));
1107 // current index should have been updated
1108 if (item == currentItem)
1109 QTRY_COMPARE(listview->currentIndex(), i);
1116 void tst_QSGListView::moved_data()
1118 QTest::addColumn<qreal>("contentY");
1119 QTest::addColumn<int>("from");
1120 QTest::addColumn<int>("to");
1121 QTest::addColumn<int>("count");
1122 QTest::addColumn<qreal>("itemsOffsetAfterMove");
1124 // model starts with 30 items, each 20px high, in area 320px high
1125 // 16 items should be visible at a time
1126 // itemsOffsetAfterMove should be > 0 whenever items above the visible pos have moved
1128 QTest::newRow("move 1 forwards, within visible items")
1133 QTest::newRow("move 1 forwards, from non-visible -> visible")
1134 << 80.0 // show 4-19
1136 << 20.0; // removed 1 item above the first visible, so item 0 should drop down by 1 to minimize movement
1138 QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)")
1139 << 80.0 // show 4-19
1141 << 20.0; // first item has moved to below item4, everything drops down by size of 1 item
1143 QTest::newRow("move 1 forwards, from visible -> non-visible")
1148 QTest::newRow("move 1 forwards, from visible -> non-visible (move first item)")
1154 QTest::newRow("move 1 backwards, within visible items")
1159 QTest::newRow("move 1 backwards, within visible items (to first index)")
1164 QTest::newRow("move 1 backwards, from non-visible -> visible")
1169 QTest::newRow("move 1 backwards, from non-visible -> visible (move last item)")
1174 QTest::newRow("move 1 backwards, from visible -> non-visible")
1175 << 80.0 // show 4-19
1177 << -20.0; // to minimize movement, item 0 moves to -20, and other items do not move
1179 QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)")
1180 << 80.0 // show 4-19
1182 << -20.0; // to minimize movement, item 16 (now at 0) moves to -20, and other items do not move
1185 QTest::newRow("move multiple forwards, within visible items")
1190 QTest::newRow("move multiple forwards, before visible items")
1191 << 140.0 // show 7-22
1192 << 4 << 5 << 3 // 4,5,6 move to below 7
1193 << 20.0 * 3; // 4,5,6 moved down
1195 QTest::newRow("move multiple forwards, from non-visible -> visible")
1196 << 80.0 // show 4-19
1198 << 20.0 * 3; // moving 3 from above the content y should adjust y positions accordingly
1200 QTest::newRow("move multiple forwards, from non-visible -> visible (move first item)")
1201 << 80.0 // show 4-19
1203 << 20.0 * 3; // moving 3 from above the content y should adjust y positions accordingly
1205 QTest::newRow("move multiple forwards, from visible -> non-visible")
1210 QTest::newRow("move multiple forwards, from visible -> non-visible (move first item)")
1216 QTest::newRow("move multiple backwards, within visible items")
1221 QTest::newRow("move multiple backwards, from non-visible -> visible")
1226 QTest::newRow("move multiple backwards, from non-visible -> visible (move last item)")
1231 QTest::newRow("move multiple backwards, from visible -> non-visible")
1232 << 80.0 // show 4-19
1234 << -20.0 * 3; // to minimize movement, 0 moves by -60, and other items do not move
1236 QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)")
1237 << 80.0 // show 4-19
1239 << -20.0 * 3; // to minimize movement, 16,17,18 move to above item 0, and other items do not move
1244 enum { Inserted, Removed, Moved, SetCurrent } type;
1249 static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; }
1250 static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; }
1251 static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; }
1252 static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; }
1254 Q_DECLARE_METATYPE(QList<ListChange>)
1256 void tst_QSGListView::multipleChanges()
1258 QFETCH(int, startCount);
1259 QFETCH(QList<ListChange>, changes);
1260 QFETCH(int, newCount);
1261 QFETCH(int, newCurrentIndex);
1263 QSGView *canvas = createView();
1267 for (int i = 0; i < startCount; i++)
1268 model.addItem("Item" + QString::number(i), "");
1270 QDeclarativeContext *ctxt = canvas->rootContext();
1271 ctxt->setContextProperty("testModel", &model);
1273 TestObject *testObject = new TestObject;
1274 ctxt->setContextProperty("testObject", testObject);
1276 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1277 qApp->processEvents();
1279 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1280 QTRY_VERIFY(listview != 0);
1282 for (int i=0; i<changes.count(); i++) {
1283 switch (changes[i].type) {
1284 case ListChange::Inserted:
1286 QList<QPair<QString, QString> > items;
1287 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
1288 items << qMakePair(QString("new item " + j), QString::number(j));
1289 model.insertItems(changes[i].index, items);
1292 case ListChange::Removed:
1293 model.removeItems(changes[i].index, changes[i].count);
1295 case ListChange::Moved:
1296 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
1298 case ListChange::SetCurrent:
1299 listview->setCurrentIndex(changes[i].index);
1304 QTRY_COMPARE(listview->count(), newCount);
1305 QCOMPARE(listview->count(), model.count());
1306 QTRY_COMPARE(listview->currentIndex(), newCurrentIndex);
1310 QSGItem *contentItem = listview->contentItem();
1311 QTRY_VERIFY(contentItem != 0);
1312 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1313 for (int i=0; i < model.count() && i < itemCount; ++i) {
1314 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1315 QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1316 name = findItem<QSGText>(contentItem, "textName", i);
1318 QTRY_COMPARE(name->text(), model.name(i));
1319 number = findItem<QSGText>(contentItem, "textNumber", i);
1320 QVERIFY(number != 0);
1321 QTRY_COMPARE(number->text(), model.number(i));
1328 void tst_QSGListView::multipleChanges_data()
1330 QTest::addColumn<int>("startCount");
1331 QTest::addColumn<QList<ListChange> >("changes");
1332 QTest::addColumn<int>("newCount");
1333 QTest::addColumn<int>("newCurrentIndex");
1335 QList<ListChange> changes;
1337 for (int i=1; i<30; i++)
1338 changes << ListChange::remove(0);
1339 QTest::newRow("remove all but 1, first->last") << 30 << changes << 1 << 0;
1341 changes << ListChange::remove(0);
1342 QTest::newRow("remove all") << 30 << changes << 0 << -1;
1345 changes << ListChange::setCurrent(29);
1346 for (int i=29; i>0; i--)
1347 changes << ListChange::remove(i);
1348 QTest::newRow("remove last (current) -> first") << 30 << changes << 1 << 0;
1350 QTest::newRow("remove then insert at 0") << 10 << (QList<ListChange>()
1351 << ListChange::remove(0, 1)
1352 << ListChange::insert(0, 1)
1355 QTest::newRow("remove then insert at non-zero index") << 10 << (QList<ListChange>()
1356 << ListChange::setCurrent(2)
1357 << ListChange::remove(2, 1)
1358 << ListChange::insert(2, 1)
1361 QTest::newRow("remove current then insert below it") << 10 << (QList<ListChange>()
1362 << ListChange::setCurrent(1)
1363 << ListChange::remove(1, 3)
1364 << ListChange::insert(2, 2)
1367 QTest::newRow("remove current index then move it down") << 10 << (QList<ListChange>()
1368 << ListChange::setCurrent(2)
1369 << ListChange::remove(1, 3)
1370 << ListChange::move(1, 5, 1)
1373 QTest::newRow("remove current index then move it up") << 10 << (QList<ListChange>()
1374 << ListChange::setCurrent(5)
1375 << ListChange::remove(4, 3)
1376 << ListChange::move(4, 1, 1)
1380 QTest::newRow("insert multiple times") << 0 << (QList<ListChange>()
1381 << ListChange::insert(0, 2)
1382 << ListChange::insert(0, 4)
1383 << ListChange::insert(0, 6)
1386 QTest::newRow("insert multiple times with current index changes") << 0 << (QList<ListChange>()
1387 << ListChange::insert(0, 2)
1388 << ListChange::insert(0, 4)
1389 << ListChange::insert(0, 6)
1390 << ListChange::setCurrent(3)
1391 << ListChange::insert(3, 2)
1394 QTest::newRow("insert and remove all") << 0 << (QList<ListChange>()
1395 << ListChange::insert(0, 30)
1396 << ListChange::remove(0, 30)
1399 QTest::newRow("insert and remove current") << 30 << (QList<ListChange>()
1400 << ListChange::insert(1)
1401 << ListChange::setCurrent(1)
1402 << ListChange::remove(1)
1405 QTest::newRow("insert before 0, then remove cross section of new and old items") << 10 << (QList<ListChange>()
1406 << ListChange::insert(0, 10)
1407 << ListChange::remove(5, 10)
1410 QTest::newRow("insert multiple, then move new items to end") << 10 << (QList<ListChange>()
1411 << ListChange::insert(0, 3)
1412 << ListChange::move(0, 10, 3)
1415 QTest::newRow("insert multiple, then move new and some old items to end") << 10 << (QList<ListChange>()
1416 << ListChange::insert(0, 3)
1417 << ListChange::move(0, 8, 5)
1420 QTest::newRow("insert multiple at end, then move new and some old items to start") << 10 << (QList<ListChange>()
1421 << ListChange::setCurrent(9)
1422 << ListChange::insert(10, 3)
1423 << ListChange::move(8, 0, 5)
1427 QTest::newRow("move back and forth to same index") << 10 << (QList<ListChange>()
1428 << ListChange::setCurrent(1)
1429 << ListChange::move(1, 2, 2)
1430 << ListChange::move(2, 1, 2)
1433 QTest::newRow("move forwards then back") << 10 << (QList<ListChange>()
1434 << ListChange::setCurrent(2)
1435 << ListChange::move(1, 2, 3)
1436 << ListChange::move(3, 0, 5)
1439 QTest::newRow("move current, then remove it") << 10 << (QList<ListChange>()
1440 << ListChange::setCurrent(5)
1441 << ListChange::move(5, 0, 1)
1442 << ListChange::remove(0)
1445 QTest::newRow("move current, then insert before it") << 10 << (QList<ListChange>()
1446 << ListChange::setCurrent(5)
1447 << ListChange::move(5, 0, 1)
1448 << ListChange::insert(0)
1451 QTest::newRow("move multiple, then remove them") << 10 << (QList<ListChange>()
1452 << ListChange::setCurrent(1)
1453 << ListChange::move(5, 1, 3)
1454 << ListChange::remove(1, 3)
1457 QTest::newRow("move multiple, then insert before them") << 10 << (QList<ListChange>()
1458 << ListChange::setCurrent(5)
1459 << ListChange::move(5, 1, 3)
1460 << ListChange::insert(1, 5)
1463 QTest::newRow("move multiple, then insert after them") << 10 << (QList<ListChange>()
1464 << ListChange::setCurrent(3)
1465 << ListChange::move(0, 1, 2)
1466 << ListChange::insert(3, 5)
1470 QTest::newRow("clear current") << 0 << (QList<ListChange>()
1471 << ListChange::insert(0, 5)
1472 << ListChange::setCurrent(-1)
1473 << ListChange::remove(0, 5)
1474 << ListChange::insert(0, 5)
1478 void tst_QSGListView::swapWithFirstItem()
1480 QSGView *canvas = createView();
1484 for (int i = 0; i < 30; i++)
1485 model.addItem("Item" + QString::number(i), "");
1487 QDeclarativeContext *ctxt = canvas->rootContext();
1488 ctxt->setContextProperty("testModel", &model);
1490 TestObject *testObject = new TestObject;
1491 ctxt->setContextProperty("testObject", testObject);
1493 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1494 qApp->processEvents();
1496 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1497 QTRY_VERIFY(listview != 0);
1499 // ensure content position is stable
1500 listview->setContentY(0);
1501 model.moveItem(1, 0);
1502 QTRY_VERIFY(listview->contentY() == 0);
1508 void tst_QSGListView::enforceRange()
1510 QSGView *canvas = createView();
1513 for (int i = 0; i < 30; i++)
1514 model.addItem("Item" + QString::number(i), "");
1516 QDeclarativeContext *ctxt = canvas->rootContext();
1517 ctxt->setContextProperty("testModel", &model);
1519 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-enforcerange.qml")));
1520 qApp->processEvents();
1522 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1523 QTRY_VERIFY(listview != 0);
1525 QTRY_COMPARE(listview->preferredHighlightBegin(), 100.0);
1526 QTRY_COMPARE(listview->preferredHighlightEnd(), 100.0);
1527 QTRY_COMPARE(listview->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
1529 QSGItem *contentItem = listview->contentItem();
1530 QTRY_VERIFY(contentItem != 0);
1532 // view should be positioned at the top of the range.
1533 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
1535 QTRY_COMPARE(listview->contentY(), -100.0);
1537 QSGText *name = findItem<QSGText>(contentItem, "textName", 0);
1538 QTRY_VERIFY(name != 0);
1539 QTRY_COMPARE(name->text(), model.name(0));
1540 QSGText *number = findItem<QSGText>(contentItem, "textNumber", 0);
1541 QTRY_VERIFY(number != 0);
1542 QTRY_COMPARE(number->text(), model.number(0));
1544 // Check currentIndex is updated when contentItem moves
1545 listview->setContentY(20);
1547 QTRY_COMPARE(listview->currentIndex(), 6);
1551 for (int i = 0; i < 5; i++)
1552 model2.addItem("Item" + QString::number(i), "");
1554 ctxt->setContextProperty("testModel", &model2);
1555 QCOMPARE(listview->count(), 5);
1560 void tst_QSGListView::enforceRange_withoutHighlight()
1563 // If no highlight is set but StrictlyEnforceRange is used, the content should still move
1564 // to the correct position (i.e. to the next/previous item, not next/previous section)
1565 // when moving up/down via incrementCurrentIndex() and decrementCurrentIndex()
1567 QSGView *canvas = createView();
1572 model.addItem("Item 0", "a");
1573 model.addItem("Item 1", "b");
1574 model.addItem("Item 2", "b");
1575 model.addItem("Item 3", "c");
1577 QDeclarativeContext *ctxt = canvas->rootContext();
1578 ctxt->setContextProperty("testModel", &model);
1580 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-enforcerange-nohighlight.qml")));
1581 qApp->processEvents();
1583 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1584 QTRY_VERIFY(listview != 0);
1586 qreal expectedPos = -100.0;
1588 expectedPos += 10.0; // scroll past 1st section's delegate (10px height)
1589 QTRY_COMPARE(listview->contentY(), expectedPos);
1591 expectedPos += 20 + 10; // scroll past 1st section and section delegate of 2nd section
1592 QTest::keyClick(canvas, Qt::Key_Down);
1594 QTRY_COMPARE(listview->contentY(), expectedPos);
1596 expectedPos += 20; // scroll past 1st item of 2nd section
1597 QTest::keyClick(canvas, Qt::Key_Down);
1598 QTRY_COMPARE(listview->contentY(), expectedPos);
1600 expectedPos += 20 + 10; // scroll past 2nd item of 2nd section and section delegate of 3rd section
1601 QTest::keyClick(canvas, Qt::Key_Down);
1602 QTRY_COMPARE(listview->contentY(), expectedPos);
1607 void tst_QSGListView::spacing()
1609 QSGView *canvas = createView();
1613 for (int i = 0; i < 30; i++)
1614 model.addItem("Item" + QString::number(i), "");
1616 QDeclarativeContext *ctxt = canvas->rootContext();
1617 ctxt->setContextProperty("testModel", &model);
1619 TestObject *testObject = new TestObject;
1620 ctxt->setContextProperty("testObject", testObject);
1622 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1623 qApp->processEvents();
1625 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1626 QTRY_VERIFY(listview != 0);
1628 QSGItem *contentItem = listview->contentItem();
1629 QTRY_VERIFY(contentItem != 0);
1631 // Confirm items positioned correctly
1632 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1633 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1634 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1635 if (!item) qWarning() << "Item" << i << "not found";
1637 QTRY_VERIFY(item->y() == i*20);
1640 listview->setSpacing(10);
1641 QTRY_VERIFY(listview->spacing() == 10);
1643 // Confirm items positioned correctly
1644 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1645 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1646 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1647 if (!item) qWarning() << "Item" << i << "not found";
1649 QTRY_VERIFY(item->y() == i*30);
1652 listview->setSpacing(0);
1654 // Confirm items positioned correctly
1655 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1656 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1657 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1658 if (!item) qWarning() << "Item" << i << "not found";
1660 QTRY_COMPARE(item->y(), i*20.0);
1667 void tst_QSGListView::sections()
1669 QSGView *canvas = createView();
1673 for (int i = 0; i < 30; i++)
1674 model.addItem("Item" + QString::number(i), QString::number(i/5));
1676 QDeclarativeContext *ctxt = canvas->rootContext();
1677 ctxt->setContextProperty("testModel", &model);
1679 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections.qml")));
1680 qApp->processEvents();
1682 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1683 QTRY_VERIFY(listview != 0);
1685 QSGItem *contentItem = listview->contentItem();
1686 QTRY_VERIFY(contentItem != 0);
1688 // Confirm items positioned correctly
1689 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1690 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1691 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1693 QTRY_COMPARE(item->y(), qreal(i*20 + ((i+4)/5) * 20));
1694 QSGText *next = findItem<QSGText>(item, "nextSection");
1695 QCOMPARE(next->text().toInt(), (i+1)/5);
1698 QSignalSpy currentSectionChangedSpy(listview, SIGNAL(currentSectionChanged()));
1700 // Remove section boundary
1701 model.removeItem(5);
1702 QTRY_COMPARE(listview->count(), model.count());
1704 // New section header created
1705 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 5);
1707 QTRY_COMPARE(item->height(), 40.0);
1709 model.insertItem(3, "New Item", "0");
1710 QTRY_COMPARE(listview->count(), model.count());
1712 // Section header moved
1713 item = findItem<QSGItem>(contentItem, "wrapper", 5);
1715 QTRY_COMPARE(item->height(), 20.0);
1717 item = findItem<QSGItem>(contentItem, "wrapper", 6);
1719 QTRY_COMPARE(item->height(), 40.0);
1721 // insert item which will become a section header
1722 model.insertItem(6, "Replace header", "1");
1723 QTRY_COMPARE(listview->count(), model.count());
1725 item = findItem<QSGItem>(contentItem, "wrapper", 6);
1727 QTRY_COMPARE(item->height(), 40.0);
1729 item = findItem<QSGItem>(contentItem, "wrapper", 7);
1731 QTRY_COMPARE(item->height(), 20.0);
1733 QTRY_COMPARE(listview->currentSection(), QString("0"));
1735 listview->setContentY(140);
1736 QTRY_COMPARE(listview->currentSection(), QString("1"));
1738 QTRY_COMPARE(currentSectionChangedSpy.count(), 1);
1740 listview->setContentY(20);
1741 QTRY_COMPARE(listview->currentSection(), QString("0"));
1743 QTRY_COMPARE(currentSectionChangedSpy.count(), 2);
1745 item = findItem<QSGItem>(contentItem, "wrapper", 1);
1747 QTRY_COMPARE(item->height(), 20.0);
1749 // check that headers change when item changes
1750 listview->setContentY(0);
1751 model.modifyItem(0, "changed", "2");
1754 item = findItem<QSGItem>(contentItem, "wrapper", 1);
1756 QTRY_COMPARE(item->height(), 40.0);
1761 void tst_QSGListView::sectionsDelegate()
1763 QSGView *canvas = createView();
1767 for (int i = 0; i < 30; i++)
1768 model.addItem("Item" + QString::number(i), QString::number(i/5));
1770 QDeclarativeContext *ctxt = canvas->rootContext();
1771 ctxt->setContextProperty("testModel", &model);
1773 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections_delegate.qml")));
1774 qApp->processEvents();
1776 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1777 QTRY_VERIFY(listview != 0);
1779 QSGItem *contentItem = listview->contentItem();
1780 QTRY_VERIFY(contentItem != 0);
1782 // Confirm items positioned correctly
1783 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1784 for (int i = 0; i < model.count() && i < itemCount; ++i) {
1785 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1787 QTRY_COMPARE(item->y(), qreal(i*20 + ((i+5)/5) * 20));
1788 QSGText *next = findItem<QSGText>(item, "nextSection");
1789 QCOMPARE(next->text().toInt(), (i+1)/5);
1792 for (int i = 0; i < 3; ++i) {
1793 QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1795 QTRY_COMPARE(item->y(), qreal(i*20*6));
1798 model.modifyItem(0, "One", "aaa");
1799 model.modifyItem(1, "Two", "aaa");
1800 model.modifyItem(2, "Three", "aaa");
1801 model.modifyItem(3, "Four", "aaa");
1802 model.modifyItem(4, "Five", "aaa");
1805 for (int i = 0; i < 3; ++i) {
1806 QSGItem *item = findItem<QSGItem>(contentItem,
1807 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1809 QTRY_COMPARE(item->y(), qreal(i*20*6));
1812 // remove section boundary
1813 model.removeItem(5);
1814 QTRY_COMPARE(listview->count(), model.count());
1815 for (int i = 0; i < 3; ++i) {
1816 QSGItem *item = findItem<QSGItem>(contentItem,
1817 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1822 QList<QSGItem*> items = findItems<QSGItem>(contentItem, "sect_1");
1823 QCOMPARE(items.count(), 1);
1826 model.modifyItem(0, "One", "aaa");
1827 model.modifyItem(1, "One", "aaa");
1828 model.modifyItem(2, "One", "aaa");
1829 model.modifyItem(3, "Four", "aaa");
1830 model.modifyItem(4, "Four", "aaa");
1831 model.modifyItem(5, "Four", "aaa");
1832 model.modifyItem(6, "Five", "aaa");
1833 model.modifyItem(7, "Five", "aaa");
1834 model.modifyItem(8, "Five", "aaa");
1835 model.modifyItem(9, "Two", "aaa");
1836 model.modifyItem(10, "Two", "aaa");
1837 model.modifyItem(11, "Two", "aaa");
1838 QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_aaa").count(), 1);
1839 canvas->rootObject()->setProperty("sectionProperty", "name");
1840 // ensure view has settled.
1841 QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_Four").count(), 1);
1842 for (int i = 0; i < 4; ++i) {
1843 QSGItem *item = findItem<QSGItem>(contentItem,
1844 "sect_" + model.name(i*3));
1846 QTRY_COMPARE(item->y(), qreal(i*20*4));
1850 model.removeItems(10, 20);
1851 // ensure view has settled.
1852 QTRY_COMPARE(findItems<QSGItem>(contentItem, "wrapper").count(), 10);
1853 // Drag view up beyond bounds
1854 QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20,20));
1856 QMouseEvent mv(QEvent::MouseMove, QPoint(20,0), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1857 QApplication::sendEvent(canvas, &mv);
1860 QMouseEvent mv(QEvent::MouseMove, QPoint(20,-50), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1861 QApplication::sendEvent(canvas, &mv);
1864 QMouseEvent mv(QEvent::MouseMove, QPoint(20,-200), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1865 QApplication::sendEvent(canvas, &mv);
1867 QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,-200));
1868 // view should settle back at 0
1869 QTRY_COMPARE(listview->contentY(), 0.0);
1874 void tst_QSGListView::sectionsPositioning()
1876 QSGView *canvas = createView();
1880 for (int i = 0; i < 30; i++)
1881 model.addItem("Item" + QString::number(i), QString::number(i/5));
1883 QDeclarativeContext *ctxt = canvas->rootContext();
1884 ctxt->setContextProperty("testModel", &model);
1886 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections_delegate.qml")));
1887 qApp->processEvents();
1888 canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart | QSGViewSection::NextLabelAtEnd)));
1890 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1891 QTRY_VERIFY(listview != 0);
1893 QSGItem *contentItem = listview->contentItem();
1894 QTRY_VERIFY(contentItem != 0);
1896 for (int i = 0; i < 3; ++i) {
1897 QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1899 QTRY_COMPARE(item->y(), qreal(i*20*6));
1902 QSGItem *topItem = findVisibleChild(contentItem, "sect_0"); // section header
1904 QCOMPARE(topItem->y(), 0.);
1906 QSGItem *bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
1907 QVERIFY(bottomItem);
1908 QCOMPARE(bottomItem->y(), 300.);
1910 // move down a little and check that section header is at top
1911 listview->setContentY(10);
1912 QCOMPARE(topItem->y(), 0.);
1914 // push the top header up
1915 listview->setContentY(110);
1916 topItem = findVisibleChild(contentItem, "sect_0"); // section header
1918 QCOMPARE(topItem->y(), 100.);
1920 QSGItem *item = findVisibleChild(contentItem, "sect_1");
1922 QCOMPARE(item->y(), 120.);
1924 bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
1925 QVERIFY(bottomItem);
1926 QCOMPARE(bottomItem->y(), 410.);
1928 // Move past section 0
1929 listview->setContentY(120);
1930 topItem = findVisibleChild(contentItem, "sect_0"); // section header
1933 // Push section footer down
1934 listview->setContentY(70);
1935 bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
1936 QVERIFY(bottomItem);
1937 QCOMPARE(bottomItem->y(), 380.);
1939 // Change current section
1940 listview->setContentY(10);
1941 model.modifyItem(0, "One", "aaa");
1942 model.modifyItem(1, "Two", "aaa");
1943 model.modifyItem(2, "Three", "aaa");
1944 model.modifyItem(3, "Four", "aaa");
1945 model.modifyItem(4, "Five", "aaa");
1948 QTRY_COMPARE(listview->currentSection(), QString("aaa"));
1950 for (int i = 0; i < 3; ++i) {
1951 QSGItem *item = findItem<QSGItem>(contentItem,
1952 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1954 QTRY_COMPARE(item->y(), qreal(i*20*6));
1957 topItem = findVisibleChild(contentItem, "sect_aaa"); // section header
1959 QCOMPARE(topItem->y(), 10.);
1961 // remove section boundary
1962 listview->setContentY(120);
1963 model.removeItem(5);
1964 QTRY_COMPARE(listview->count(), model.count());
1965 for (int i = 0; i < 3; ++i) {
1966 QSGItem *item = findItem<QSGItem>(contentItem,
1967 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1969 QTRY_COMPARE(item->y(), qreal(i*20*6));
1972 QTRY_VERIFY(topItem = findVisibleChild(contentItem, "sect_aaa")); // section header
1973 QCOMPARE(topItem->y(), 120.);
1974 QVERIFY(topItem = findVisibleChild(contentItem, "sect_1"));
1975 QTRY_COMPARE(topItem->y(), 140.);
1977 // Change the next section
1978 listview->setContentY(0);
1979 bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
1980 QVERIFY(bottomItem);
1981 QTRY_COMPARE(bottomItem->y(), 320.);
1983 model.modifyItem(14, "New", "new");
1985 QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
1986 QTRY_COMPARE(bottomItem->y(), 320.);
1988 // Turn sticky footer off
1989 listview->setContentY(50);
1990 canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart)));
1991 item = findVisibleChild(contentItem, "sect_new"); // inline label restored
1992 QCOMPARE(item->y(), 360.);
1994 // Turn sticky header off
1995 listview->setContentY(50);
1996 canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels)));
1997 item = findVisibleChild(contentItem, "sect_aaa"); // inline label restored
1998 QCOMPARE(item->y(), 20.);
2003 void tst_QSGListView::currentIndex_delayedItemCreation()
2005 QFETCH(bool, setCurrentToZero);
2007 QSGView *canvas = createView();
2011 // test currentIndexChanged() is emitted even if currentIndex = 0 on start up
2012 // (since the currentItem will have changed and that shares the same index)
2013 canvas->rootContext()->setContextProperty("setCurrentToZero", setCurrentToZero);
2015 canvas->setSource(QUrl::fromLocalFile(TESTDATA("fillModelOnComponentCompleted.qml")));
2016 qApp->processEvents();
2018 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2019 QTRY_VERIFY(listview != 0);
2021 QSGItem *contentItem = listview->contentItem();
2022 QTRY_VERIFY(contentItem != 0);
2024 QSignalSpy spy(listview, SIGNAL(currentIndexChanged()));
2025 QCOMPARE(listview->currentIndex(), 0);
2026 QTRY_COMPARE(spy.count(), 1);
2031 void tst_QSGListView::currentIndex_delayedItemCreation_data()
2033 QTest::addColumn<bool>("setCurrentToZero");
2035 QTest::newRow("set to 0") << true;
2036 QTest::newRow("don't set to 0") << false;
2039 void tst_QSGListView::currentIndex()
2042 for (int i = 0; i < 30; i++)
2043 model.addItem("Item" + QString::number(i), QString::number(i));
2045 QSGView *canvas = new QSGView(0);
2046 canvas->setGeometry(0,0,240,320);
2048 QDeclarativeContext *ctxt = canvas->rootContext();
2049 ctxt->setContextProperty("testModel", &model);
2050 ctxt->setContextProperty("testWrap", QVariant(false));
2052 QString filename(TESTDATA("listview-initCurrent.qml"));
2053 canvas->setSource(QUrl::fromLocalFile(filename));
2055 qApp->processEvents();
2057 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2058 QTRY_VERIFY(listview != 0);
2060 QSGItem *contentItem = listview->contentItem();
2061 QTRY_VERIFY(contentItem != 0);
2063 // current item should be 20th item at startup
2064 // and current item should be in view
2065 QCOMPARE(listview->currentIndex(), 20);
2066 QCOMPARE(listview->contentY(), 100.0);
2067 QCOMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 20));
2068 QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
2071 listview->setCurrentIndex(0);
2072 QCOMPARE(listview->currentIndex(), 0);
2073 // confirm that the velocity is updated
2074 QTRY_VERIFY(listview->verticalVelocity() != 0.0);
2076 listview->incrementCurrentIndex();
2077 QCOMPARE(listview->currentIndex(), 1);
2078 listview->decrementCurrentIndex();
2079 QCOMPARE(listview->currentIndex(), 0);
2081 listview->decrementCurrentIndex();
2082 QCOMPARE(listview->currentIndex(), 0);
2085 ctxt->setContextProperty("testWrap", QVariant(true));
2086 QVERIFY(listview->isWrapEnabled());
2088 listview->decrementCurrentIndex();
2089 QCOMPARE(listview->currentIndex(), model.count()-1);
2091 QTRY_COMPARE(listview->contentY(), 280.0);
2093 listview->incrementCurrentIndex();
2094 QCOMPARE(listview->currentIndex(), 0);
2096 QTRY_COMPARE(listview->contentY(), 0.0);
2099 // footer should become visible if it is out of view, and then current index is set to count-1
2100 canvas->rootObject()->setProperty("showFooter", true);
2101 QTRY_VERIFY(listview->footerItem());
2102 listview->setCurrentIndex(model.count()-2);
2103 QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
2104 listview->setCurrentIndex(model.count()-1);
2105 QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
2106 canvas->rootObject()->setProperty("showFooter", false);
2108 // header should become visible if it is out of view, and then current index is set to 0
2109 canvas->rootObject()->setProperty("showHeader", true);
2110 QTRY_VERIFY(listview->headerItem());
2111 listview->setCurrentIndex(1);
2112 QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
2113 listview->setCurrentIndex(0);
2114 QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
2115 canvas->rootObject()->setProperty("showHeader", false);
2120 canvas->requestActivateWindow();
2121 QTest::qWaitForWindowShown(canvas);
2122 QTRY_VERIFY(qGuiApp->focusWindow() == canvas);
2124 listview->setCurrentIndex(0);
2126 QTest::keyClick(canvas, Qt::Key_Down);
2127 QCOMPARE(listview->currentIndex(), 1);
2129 QTest::keyClick(canvas, Qt::Key_Up);
2130 QCOMPARE(listview->currentIndex(), 0);
2132 // hold down Key_Down
2133 for (int i=0; i<model.count()-1; i++) {
2134 QTest::simulateEvent(canvas, true, Qt::Key_Down, Qt::NoModifier, "", true);
2135 QTRY_COMPARE(listview->currentIndex(), i+1);
2137 QTest::keyRelease(canvas, Qt::Key_Down);
2138 QTRY_COMPARE(listview->currentIndex(), model.count()-1);
2139 QTRY_COMPARE(listview->contentY(), 280.0);
2142 for (int i=model.count()-1; i > 0; i--) {
2143 QTest::simulateEvent(canvas, true, Qt::Key_Up, Qt::NoModifier, "", true);
2144 QTRY_COMPARE(listview->currentIndex(), i-1);
2146 QTest::keyRelease(canvas, Qt::Key_Up);
2147 QTRY_COMPARE(listview->currentIndex(), 0);
2148 QTRY_COMPARE(listview->contentY(), 0.0);
2151 // turn off auto highlight
2152 listview->setHighlightFollowsCurrentItem(false);
2153 QVERIFY(listview->highlightFollowsCurrentItem() == false);
2155 QVERIFY(listview->highlightItem());
2156 qreal hlPos = listview->highlightItem()->y();
2158 listview->setCurrentIndex(4);
2159 QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
2161 // insert item before currentIndex
2162 listview->setCurrentIndex(28);
2163 model.insertItem(0, "Foo", "1111");
2164 QTRY_COMPARE(canvas->rootObject()->property("current").toInt(), 29);
2166 // check removing highlight by setting currentIndex to -1;
2167 listview->setCurrentIndex(-1);
2169 QCOMPARE(listview->currentIndex(), -1);
2170 QVERIFY(!listview->highlightItem());
2171 QVERIFY(!listview->currentItem());
2176 void tst_QSGListView::noCurrentIndex()
2179 for (int i = 0; i < 30; i++)
2180 model.addItem("Item" + QString::number(i), QString::number(i));
2182 QSGView *canvas = new QSGView(0);
2183 canvas->setGeometry(0,0,240,320);
2185 QDeclarativeContext *ctxt = canvas->rootContext();
2186 ctxt->setContextProperty("testModel", &model);
2188 QString filename(TESTDATA("listview-noCurrent.qml"));
2189 canvas->setSource(QUrl::fromLocalFile(filename));
2191 qApp->processEvents();
2193 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2194 QTRY_VERIFY(listview != 0);
2196 QSGItem *contentItem = listview->contentItem();
2197 QTRY_VERIFY(contentItem != 0);
2199 // current index should be -1 at startup
2200 // and we should not have a currentItem or highlightItem
2201 QCOMPARE(listview->currentIndex(), -1);
2202 QCOMPARE(listview->contentY(), 0.0);
2203 QVERIFY(!listview->highlightItem());
2204 QVERIFY(!listview->currentItem());
2206 listview->setCurrentIndex(2);
2207 QCOMPARE(listview->currentIndex(), 2);
2208 QVERIFY(listview->highlightItem());
2209 QVERIFY(listview->currentItem());
2214 void tst_QSGListView::itemList()
2216 QSGView *canvas = createView();
2218 canvas->setSource(QUrl::fromLocalFile(TESTDATA("itemlist.qml")));
2219 qApp->processEvents();
2221 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
2222 QTRY_VERIFY(listview != 0);
2224 QSGItem *contentItem = listview->contentItem();
2225 QTRY_VERIFY(contentItem != 0);
2227 QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
2228 QTRY_VERIFY(model != 0);
2230 QTRY_VERIFY(model->count() == 3);
2231 QTRY_COMPARE(listview->currentIndex(), 0);
2233 QSGItem *item = findItem<QSGItem>(contentItem, "item1");
2235 QTRY_COMPARE(item->x(), 0.0);
2236 QCOMPARE(item->height(), listview->height());
2238 QSGText *text = findItem<QSGText>(contentItem, "text1");
2240 QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
2242 listview->setCurrentIndex(2);
2244 item = findItem<QSGItem>(contentItem, "item3");
2246 QTRY_COMPARE(item->x(), 480.0);
2248 text = findItem<QSGText>(contentItem, "text3");
2250 QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
2255 void tst_QSGListView::cacheBuffer()
2257 QSGView *canvas = createView();
2260 for (int i = 0; i < 30; i++)
2261 model.addItem("Item" + QString::number(i), "");
2263 QDeclarativeContext *ctxt = canvas->rootContext();
2264 ctxt->setContextProperty("testModel", &model);
2266 TestObject *testObject = new TestObject;
2267 ctxt->setContextProperty("testObject", testObject);
2269 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2270 qApp->processEvents();
2272 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2273 QTRY_VERIFY(listview != 0);
2275 QSGItem *contentItem = listview->contentItem();
2276 QTRY_VERIFY(contentItem != 0);
2277 QTRY_VERIFY(listview->delegate() != 0);
2278 QTRY_VERIFY(listview->model() != 0);
2279 QTRY_VERIFY(listview->highlight() != 0);
2281 // Confirm items positioned correctly
2282 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2283 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2284 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2285 if (!item) qWarning() << "Item" << i << "not found";
2287 QTRY_VERIFY(item->y() == i*20);
2290 testObject->setCacheBuffer(400);
2291 QTRY_VERIFY(listview->cacheBuffer() == 400);
2293 int newItemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2294 QTRY_VERIFY(newItemCount > itemCount);
2296 // Confirm items positioned correctly
2297 for (int i = 0; i < model.count() && i < newItemCount; ++i) {
2298 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2299 if (!item) qWarning() << "Item" << i << "not found";
2301 QTRY_VERIFY(item->y() == i*20);
2308 void tst_QSGListView::positionViewAtIndex()
2310 QSGView *canvas = createView();
2313 for (int i = 0; i < 40; i++)
2314 model.addItem("Item" + QString::number(i), "");
2316 QDeclarativeContext *ctxt = canvas->rootContext();
2317 ctxt->setContextProperty("testModel", &model);
2319 TestObject *testObject = new TestObject;
2320 ctxt->setContextProperty("testObject", testObject);
2322 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2323 qApp->processEvents();
2325 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2326 QTRY_VERIFY(listview != 0);
2328 QSGItem *contentItem = listview->contentItem();
2329 QTRY_VERIFY(contentItem != 0);
2331 // Confirm items positioned correctly
2332 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2333 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2334 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2335 if (!item) qWarning() << "Item" << i << "not found";
2337 QTRY_COMPARE(item->y(), i*20.);
2340 // Position on a currently visible item
2341 listview->positionViewAtIndex(3, QSGListView::Beginning);
2342 QTRY_COMPARE(listview->contentY(), 60.);
2344 // Confirm items positioned correctly
2345 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2346 for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) {
2347 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2348 if (!item) qWarning() << "Item" << i << "not found";
2350 QTRY_COMPARE(item->y(), i*20.);
2353 // Position on an item beyond the visible items
2354 listview->positionViewAtIndex(22, QSGListView::Beginning);
2355 QTRY_COMPARE(listview->contentY(), 440.);
2357 // Confirm items positioned correctly
2358 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2359 for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) {
2360 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2361 if (!item) qWarning() << "Item" << i << "not found";
2363 QTRY_COMPARE(item->y(), i*20.);
2366 // Position on an item that would leave empty space if positioned at the top
2367 listview->positionViewAtIndex(28, QSGListView::Beginning);
2368 QTRY_COMPARE(listview->contentY(), 480.);
2370 // Confirm items positioned correctly
2371 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2372 for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) {
2373 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2374 if (!item) qWarning() << "Item" << i << "not found";
2376 QTRY_COMPARE(item->y(), i*20.);
2379 // Position at the beginning again
2380 listview->positionViewAtIndex(0, QSGListView::Beginning);
2381 QTRY_COMPARE(listview->contentY(), 0.);
2383 // Confirm items positioned correctly
2384 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2385 for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
2386 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2387 if (!item) qWarning() << "Item" << i << "not found";
2389 QTRY_COMPARE(item->y(), i*20.);
2392 // Position at End using last index
2393 listview->positionViewAtIndex(model.count()-1, QSGListView::End);
2394 QTRY_COMPARE(listview->contentY(), 480.);
2396 // Confirm items positioned correctly
2397 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2398 for (int i = 24; i < model.count(); ++i) {
2399 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2400 if (!item) qWarning() << "Item" << i << "not found";
2402 QTRY_COMPARE(item->y(), i*20.);
2406 listview->positionViewAtIndex(20, QSGListView::End);
2407 QTRY_COMPARE(listview->contentY(), 100.);
2409 // Position in Center
2410 listview->positionViewAtIndex(15, QSGListView::Center);
2411 QTRY_COMPARE(listview->contentY(), 150.);
2413 // Ensure at least partially visible
2414 listview->positionViewAtIndex(15, QSGListView::Visible);
2415 QTRY_COMPARE(listview->contentY(), 150.);
2417 listview->setContentY(302);
2418 listview->positionViewAtIndex(15, QSGListView::Visible);
2419 QTRY_COMPARE(listview->contentY(), 302.);
2421 listview->setContentY(320);
2422 listview->positionViewAtIndex(15, QSGListView::Visible);
2423 QTRY_COMPARE(listview->contentY(), 300.);
2425 listview->setContentY(85);
2426 listview->positionViewAtIndex(20, QSGListView::Visible);
2427 QTRY_COMPARE(listview->contentY(), 85.);
2429 listview->setContentY(75);
2430 listview->positionViewAtIndex(20, QSGListView::Visible);
2431 QTRY_COMPARE(listview->contentY(), 100.);
2433 // Ensure completely visible
2434 listview->setContentY(120);
2435 listview->positionViewAtIndex(20, QSGListView::Contain);
2436 QTRY_COMPARE(listview->contentY(), 120.);
2438 listview->setContentY(302);
2439 listview->positionViewAtIndex(15, QSGListView::Contain);
2440 QTRY_COMPARE(listview->contentY(), 300.);
2442 listview->setContentY(85);
2443 listview->positionViewAtIndex(20, QSGListView::Contain);
2444 QTRY_COMPARE(listview->contentY(), 100.);
2446 // positionAtBeginnging
2447 listview->positionViewAtBeginning();
2448 QTRY_COMPARE(listview->contentY(), 0.);
2450 listview->setContentY(80);
2451 canvas->rootObject()->setProperty("showHeader", true);
2452 listview->positionViewAtBeginning();
2453 QTRY_COMPARE(listview->contentY(), -30.);
2456 listview->positionViewAtEnd();
2457 QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
2459 listview->setContentY(80);
2460 canvas->rootObject()->setProperty("showFooter", true);
2461 listview->positionViewAtEnd();
2462 QTRY_COMPARE(listview->contentY(), 510.);
2464 // set current item to outside visible view, position at beginning
2465 // and ensure highlight moves to current item
2466 listview->setCurrentIndex(1);
2467 listview->positionViewAtBeginning();
2468 QTRY_COMPARE(listview->contentY(), -30.);
2469 QVERIFY(listview->highlightItem());
2470 QCOMPARE(listview->highlightItem()->y(), 20.);
2476 void tst_QSGListView::resetModel()
2478 QSGView *canvas = createView();
2480 QStringList strings;
2481 strings << "one" << "two" << "three";
2482 QStringListModel model(strings);
2484 QDeclarativeContext *ctxt = canvas->rootContext();
2485 ctxt->setContextProperty("testModel", &model);
2487 canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
2488 qApp->processEvents();
2490 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2491 QTRY_VERIFY(listview != 0);
2493 QSGItem *contentItem = listview->contentItem();
2494 QTRY_VERIFY(contentItem != 0);
2496 QTRY_COMPARE(listview->count(), model.rowCount());
2498 for (int i = 0; i < model.rowCount(); ++i) {
2499 QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
2500 QTRY_VERIFY(display != 0);
2501 QTRY_COMPARE(display->text(), strings.at(i));
2505 strings << "four" << "five" << "six" << "seven";
2506 model.setStringList(strings);
2508 QTRY_COMPARE(listview->count(), model.rowCount());
2510 for (int i = 0; i < model.rowCount(); ++i) {
2511 QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
2512 QTRY_VERIFY(display != 0);
2513 QTRY_COMPARE(display->text(), strings.at(i));
2519 void tst_QSGListView::propertyChanges()
2521 QSGView *canvas = createView();
2522 QTRY_VERIFY(canvas);
2523 canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2525 QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2526 QTRY_VERIFY(listView);
2528 QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
2529 QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
2530 QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
2531 QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
2532 QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
2533 QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
2534 QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
2536 QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
2537 QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
2538 QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
2539 QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::ApplyRange);
2540 QTRY_COMPARE(listView->isWrapEnabled(), true);
2541 QTRY_COMPARE(listView->cacheBuffer(), 10);
2542 QTRY_COMPARE(listView->snapMode(), QSGListView::SnapToItem);
2544 listView->setHighlightFollowsCurrentItem(false);
2545 listView->setPreferredHighlightBegin(1.0);
2546 listView->setPreferredHighlightEnd(1.0);
2547 listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
2548 listView->setWrapEnabled(false);
2549 listView->setCacheBuffer(3);
2550 listView->setSnapMode(QSGListView::SnapOneItem);
2552 QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
2553 QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
2554 QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
2555 QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
2556 QTRY_COMPARE(listView->isWrapEnabled(), false);
2557 QTRY_COMPARE(listView->cacheBuffer(), 3);
2558 QTRY_COMPARE(listView->snapMode(), QSGListView::SnapOneItem);
2560 QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
2561 QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
2562 QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
2563 QTRY_COMPARE(highlightRangeModeSpy.count(),1);
2564 QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
2565 QTRY_COMPARE(cacheBufferSpy.count(),1);
2566 QTRY_COMPARE(snapModeSpy.count(),1);
2568 listView->setHighlightFollowsCurrentItem(false);
2569 listView->setPreferredHighlightBegin(1.0);
2570 listView->setPreferredHighlightEnd(1.0);
2571 listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
2572 listView->setWrapEnabled(false);
2573 listView->setCacheBuffer(3);
2574 listView->setSnapMode(QSGListView::SnapOneItem);
2576 QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
2577 QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
2578 QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
2579 QTRY_COMPARE(highlightRangeModeSpy.count(),1);
2580 QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
2581 QTRY_COMPARE(cacheBufferSpy.count(),1);
2582 QTRY_COMPARE(snapModeSpy.count(),1);
2587 void tst_QSGListView::componentChanges()
2589 QSGView *canvas = createView();
2590 QTRY_VERIFY(canvas);
2591 canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2593 QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2594 QTRY_VERIFY(listView);
2596 QDeclarativeComponent component(canvas->engine());
2597 component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
2599 QDeclarativeComponent delegateComponent(canvas->engine());
2600 delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
2602 QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
2603 QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
2604 QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
2605 QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
2607 listView->setHighlight(&component);
2608 listView->setHeader(&component);
2609 listView->setFooter(&component);
2610 listView->setDelegate(&delegateComponent);
2612 QTRY_COMPARE(listView->highlight(), &component);
2613 QTRY_COMPARE(listView->header(), &component);
2614 QTRY_COMPARE(listView->footer(), &component);
2615 QTRY_COMPARE(listView->delegate(), &delegateComponent);
2617 QTRY_COMPARE(highlightSpy.count(),1);
2618 QTRY_COMPARE(delegateSpy.count(),1);
2619 QTRY_COMPARE(headerSpy.count(),1);
2620 QTRY_COMPARE(footerSpy.count(),1);
2622 listView->setHighlight(&component);
2623 listView->setHeader(&component);
2624 listView->setFooter(&component);
2625 listView->setDelegate(&delegateComponent);
2627 QTRY_COMPARE(highlightSpy.count(),1);
2628 QTRY_COMPARE(delegateSpy.count(),1);
2629 QTRY_COMPARE(headerSpy.count(),1);
2630 QTRY_COMPARE(footerSpy.count(),1);
2635 void tst_QSGListView::modelChanges()
2637 QSGView *canvas = createView();
2638 QTRY_VERIFY(canvas);
2639 canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2641 QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2642 QTRY_VERIFY(listView);
2644 QDeclarativeListModel *alternateModel = canvas->rootObject()->findChild<QDeclarativeListModel*>("alternateModel");
2645 QTRY_VERIFY(alternateModel);
2646 QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
2647 QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
2649 listView->setModel(modelVariant);
2650 QTRY_COMPARE(listView->model(), modelVariant);
2651 QTRY_COMPARE(modelSpy.count(),1);
2653 listView->setModel(modelVariant);
2654 QTRY_COMPARE(modelSpy.count(),1);
2656 listView->setModel(QVariant());
2657 QTRY_COMPARE(modelSpy.count(),2);
2662 void tst_QSGListView::QTBUG_9791()
2664 QSGView *canvas = createView();
2666 canvas->setSource(QUrl::fromLocalFile(TESTDATA("strictlyenforcerange.qml")));
2667 qApp->processEvents();
2669 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2670 QTRY_VERIFY(listview != 0);
2672 QSGItem *contentItem = listview->contentItem();
2673 QTRY_VERIFY(contentItem != 0);
2674 QTRY_VERIFY(listview->delegate() != 0);
2675 QTRY_VERIFY(listview->model() != 0);
2677 QMetaObject::invokeMethod(listview, "fillModel");
2678 qApp->processEvents();
2680 // Confirm items positioned correctly
2681 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2682 QCOMPARE(itemCount, 3);
2684 for (int i = 0; i < itemCount; ++i) {
2685 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2686 if (!item) qWarning() << "Item" << i << "not found";
2688 QTRY_COMPARE(item->x(), i*300.0);
2691 // check that view is positioned correctly
2692 QTRY_COMPARE(listview->contentX(), 590.0);
2697 void tst_QSGListView::manualHighlight()
2699 QSGView *canvas = new QSGView(0);
2700 canvas->setGeometry(0,0,240,320);
2702 QString filename(TESTDATA("manual-highlight.qml"));
2703 canvas->setSource(QUrl::fromLocalFile(filename));
2705 qApp->processEvents();
2707 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2708 QTRY_VERIFY(listview != 0);
2710 QSGItem *contentItem = listview->contentItem();
2711 QTRY_VERIFY(contentItem != 0);
2713 QTRY_COMPARE(listview->currentIndex(), 0);
2714 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
2715 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2717 listview->setCurrentIndex(2);
2719 QTRY_COMPARE(listview->currentIndex(), 2);
2720 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2721 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2724 listview->positionViewAtIndex(3, QSGListView::Contain);
2726 QTRY_COMPARE(listview->currentIndex(), 2);
2727 QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2728 QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2733 void tst_QSGListView::QTBUG_11105()
2735 QSGView *canvas = createView();
2738 for (int i = 0; i < 30; i++)
2739 model.addItem("Item" + QString::number(i), "");
2741 QDeclarativeContext *ctxt = canvas->rootContext();
2742 ctxt->setContextProperty("testModel", &model);
2744 TestObject *testObject = new TestObject;
2745 ctxt->setContextProperty("testObject", testObject);
2747 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2748 qApp->processEvents();
2750 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2751 QTRY_VERIFY(listview != 0);
2753 QSGItem *contentItem = listview->contentItem();
2754 QTRY_VERIFY(contentItem != 0);
2756 // Confirm items positioned correctly
2757 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2758 for (int i = 0; i < model.count() && i < itemCount; ++i) {
2759 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2760 if (!item) qWarning() << "Item" << i << "not found";
2762 QTRY_VERIFY(item->y() == i*20);
2765 listview->positionViewAtIndex(20, QSGListView::Beginning);
2766 QCOMPARE(listview->contentY(), 280.);
2769 for (int i = 0; i < 5; i++)
2770 model2.addItem("Item" + QString::number(i), "");
2772 ctxt->setContextProperty("testModel", &model2);
2774 itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2775 QCOMPARE(itemCount, 5);
2781 void tst_QSGListView::header()
2783 QFETCH(QSGListView::Orientation, orientation);
2784 QFETCH(Qt::LayoutDirection, layoutDirection);
2785 QFETCH(QPointF, initialHeaderPos);
2786 QFETCH(QPointF, firstDelegatePos);
2787 QFETCH(QPointF, initialContentPos);
2788 QFETCH(QPointF, changedHeaderPos);
2789 QFETCH(QPointF, changedContentPos);
2790 QFETCH(QPointF, resizeContentPos);
2793 for (int i = 0; i < 30; i++)
2794 model.addItem("Item" + QString::number(i), "");
2796 QSGView *canvas = createView();
2797 canvas->rootContext()->setContextProperty("testModel", &model);
2798 canvas->rootContext()->setContextProperty("initialViewWidth", 240);
2799 canvas->rootContext()->setContextProperty("initialViewHeight", 320);
2800 canvas->setSource(QUrl::fromLocalFile(TESTDATA("header.qml")));
2802 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2803 QTRY_VERIFY(listview != 0);
2804 listview->setOrientation(orientation);
2805 listview->setLayoutDirection(layoutDirection);
2807 QSGItem *contentItem = listview->contentItem();
2808 QTRY_VERIFY(contentItem != 0);
2810 QSGText *header = findItem<QSGText>(contentItem, "header");
2813 QVERIFY(header == listview->headerItem());
2815 QCOMPARE(header->width(), 100.);
2816 QCOMPARE(header->height(), 30.);
2817 QCOMPARE(header->pos(), initialHeaderPos);
2818 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2820 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2822 QCOMPARE(item->pos(), firstDelegatePos);
2825 QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
2827 for (int i = 0; i < 30; i++)
2828 model.addItem("Item" + QString::number(i), "");
2830 QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
2831 QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
2833 QCOMPARE(headerItemSpy.count(), 1);
2835 header = findItem<QSGText>(contentItem, "header");
2837 header = findItem<QSGText>(contentItem, "header2");
2840 QVERIFY(header == listview->headerItem());
2842 QCOMPARE(header->pos(), changedHeaderPos);
2843 QCOMPARE(header->width(), 50.);
2844 QCOMPARE(header->height(), 20.);
2845 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2846 QCOMPARE(item->pos(), firstDelegatePos);
2851 // QTBUG-21207 header should become visible if view resizes from initial empty size
2853 canvas = createView();
2854 canvas->rootContext()->setContextProperty("testModel", &model);
2855 canvas->rootContext()->setContextProperty("initialViewWidth", 0.0);
2856 canvas->rootContext()->setContextProperty("initialViewHeight", 0.0);
2857 canvas->setSource(QUrl::fromLocalFile(TESTDATA("header.qml")));
2859 listview = findItem<QSGListView>(canvas->rootObject(), "list");
2860 QTRY_VERIFY(listview != 0);
2861 listview->setOrientation(orientation);
2862 listview->setLayoutDirection(layoutDirection);
2864 listview->setWidth(240);
2865 listview->setHeight(320);
2866 QTRY_COMPARE(listview->headerItem()->pos(), initialHeaderPos);
2867 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2873 void tst_QSGListView::header_data()
2875 QTest::addColumn<QSGListView::Orientation>("orientation");
2876 QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2877 QTest::addColumn<QPointF>("initialHeaderPos");
2878 QTest::addColumn<QPointF>("changedHeaderPos");
2879 QTest::addColumn<QPointF>("initialContentPos");
2880 QTest::addColumn<QPointF>("changedContentPos");
2881 QTest::addColumn<QPointF>("firstDelegatePos");
2882 QTest::addColumn<QPointF>("resizeContentPos");
2884 // header1 = 100 x 30
2885 // header2 = 50 x 20
2886 // delegates = 240 x 20
2889 // header above items, top left
2890 QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight
2898 // header above items, top right
2899 QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2907 // header to left of items
2908 QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2916 // header to right of items
2917 QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2920 << QPointF(-240 + 100, 0)
2921 << QPointF(-240 + 50, 0)
2923 << QPointF(-240 + 40, 0);
2926 void tst_QSGListView::header_delayItemCreation()
2928 QSGView *canvas = createView();
2932 canvas->rootContext()->setContextProperty("setCurrentToZero", false);
2933 canvas->setSource(QUrl::fromLocalFile(TESTDATA("fillModelOnComponentCompleted.qml")));
2934 qApp->processEvents();
2936 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2937 QTRY_VERIFY(listview != 0);
2939 QSGItem *contentItem = listview->contentItem();
2940 QTRY_VERIFY(contentItem != 0);
2942 QSGText *header = findItem<QSGText>(contentItem, "header");
2944 QCOMPARE(header->y(), -header->height());
2946 QCOMPARE(listview->contentY(), -header->height());
2949 QTRY_COMPARE(header->y(), -header->height());
2954 void tst_QSGListView::footer()
2956 QFETCH(QSGListView::Orientation, orientation);
2957 QFETCH(Qt::LayoutDirection, layoutDirection);
2958 QFETCH(QPointF, initialFooterPos);
2959 QFETCH(QPointF, firstDelegatePos);
2960 QFETCH(QPointF, initialContentPos);
2961 QFETCH(QPointF, changedFooterPos);
2962 QFETCH(QPointF, changedContentPos);
2963 QFETCH(QPointF, resizeContentPos);
2965 QSGView *canvas = createView();
2968 for (int i = 0; i < 3; i++)
2969 model.addItem("Item" + QString::number(i), "");
2971 QDeclarativeContext *ctxt = canvas->rootContext();
2972 ctxt->setContextProperty("testModel", &model);
2974 canvas->setSource(QUrl::fromLocalFile(TESTDATA("footer.qml")));
2976 qApp->processEvents();
2978 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2979 QTRY_VERIFY(listview != 0);
2980 listview->setOrientation(orientation);
2981 listview->setLayoutDirection(layoutDirection);
2983 QSGItem *contentItem = listview->contentItem();
2984 QTRY_VERIFY(contentItem != 0);
2986 QSGText *footer = findItem<QSGText>(contentItem, "footer");
2989 QVERIFY(footer == listview->footerItem());
2991 QCOMPARE(footer->pos(), initialFooterPos);
2992 QCOMPARE(footer->width(), 100.);
2993 QCOMPARE(footer->height(), 30.);
2994 QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2996 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2998 QCOMPARE(item->pos(), firstDelegatePos);
3001 model.removeItem(1);
3003 if (orientation == QSGListView::Vertical) {
3004 QTRY_COMPARE(footer->y(), initialFooterPos.y() - 20); // delegate height = 20
3006 QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
3007 initialFooterPos.x() - 40 : initialFooterPos.x() + 40); // delegate width = 40
3013 QPointF posWhenNoItems(0, 0);
3014 if (orientation == QSGListView::Horizontal && layoutDirection == Qt::RightToLeft)
3015 posWhenNoItems.setX(-100);
3016 QTRY_COMPARE(footer->pos(), posWhenNoItems);
3018 // if header is present, it's at a negative pos, so the footer should not move
3019 canvas->rootObject()->setProperty("showHeader", true);
3020 QTRY_COMPARE(footer->pos(), posWhenNoItems);
3021 canvas->rootObject()->setProperty("showHeader", false);
3024 for (int i = 0; i < 30; i++)
3025 model.addItem("Item" + QString::number(i), "");
3027 QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
3028 QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
3030 QCOMPARE(footerItemSpy.count(), 1);
3032 footer = findItem<QSGText>(contentItem, "footer");
3034 footer = findItem<QSGText>(contentItem, "footer2");
3037 QVERIFY(footer == listview->footerItem());
3039 QCOMPARE(footer->pos(), changedFooterPos);
3040 QCOMPARE(footer->width(), 50.);
3041 QCOMPARE(footer->height(), 20.);
3042 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3044 item = findItem<QSGItem>(contentItem, "wrapper", 0);
3046 QCOMPARE(item->pos(), firstDelegatePos);
3048 listview->positionViewAtEnd();
3049 footer->setHeight(10);
3050 footer->setWidth(40);
3051 QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3056 void tst_QSGListView::footer_data()
3058 QTest::addColumn<QSGListView::Orientation>("orientation");
3059 QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3060 QTest::addColumn<QPointF>("initialFooterPos");
3061 QTest::addColumn<QPointF>("changedFooterPos");
3062 QTest::addColumn<QPointF>("initialContentPos");
3063 QTest::addColumn<QPointF>("changedContentPos");
3064 QTest::addColumn<QPointF>("firstDelegatePos");
3065 QTest::addColumn<QPointF>("resizeContentPos");
3067 // footer1 = 100 x 30
3068 // footer2 = 50 x 20
3069 // delegates = 40 x 20
3071 // view height = 320
3073 // footer below items, bottom left
3074 QTest::newRow("vertical, layout left to right") << QSGListView::Vertical << Qt::LeftToRight
3075 << QPointF(0, 3 * 20)
3076 << QPointF(0, 30 * 20) // added 30 items
3080 << QPointF(0, 30 * 20 - 320 + 10);
3082 // footer below items, bottom right
3083 QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
3084 << QPointF(0, 3 * 20)
3085 << QPointF(0, 30 * 20)
3089 << QPointF(0, 30 * 20 - 320 + 10);
3091 // footer to right of items
3092 QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
3093 << QPointF(40 * 3, 0)
3094 << QPointF(40 * 30, 0)
3098 << QPointF(40 * 30 - 240 + 40, 0);
3100 // footer to left of items
3101 QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
3102 << QPointF(-(40 * 3) - 100, 0)
3103 << QPointF(-(40 * 30) - 50, 0) // 50 = new footer width
3107 << QPointF(-(40 * 30) - 40, 0);
3110 class LVAccessor : public QSGListView
3113 qreal minY() const { return minYExtent(); }
3114 qreal maxY() const { return maxYExtent(); }
3115 qreal minX() const { return minXExtent(); }
3116 qreal maxX() const { return maxXExtent(); }
3119 void tst_QSGListView::headerFooter()
3123 QSGView *canvas = createView();
3126 QDeclarativeContext *ctxt = canvas->rootContext();
3127 ctxt->setContextProperty("testModel", &model);
3129 canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3130 qApp->processEvents();
3132 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3133 QTRY_VERIFY(listview != 0);
3135 QSGItem *contentItem = listview->contentItem();
3136 QTRY_VERIFY(contentItem != 0);
3138 QSGItem *header = findItem<QSGItem>(contentItem, "header");
3140 QCOMPARE(header->y(), -header->height());
3142 QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3144 QCOMPARE(footer->y(), 0.);
3146 QCOMPARE(static_cast<LVAccessor*>(listview)->minY(), header->height());
3147 QCOMPARE(static_cast<LVAccessor*>(listview)->maxY(), header->height());
3153 QSGView *canvas = createView();
3156 QDeclarativeContext *ctxt = canvas->rootContext();
3157 ctxt->setContextProperty("testModel", &model);
3159 canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3160 canvas->rootObject()->setProperty("horizontal", true);
3161 qApp->processEvents();
3163 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3164 QTRY_VERIFY(listview != 0);
3166 QSGItem *contentItem = listview->contentItem();
3167 QTRY_VERIFY(contentItem != 0);
3169 QSGItem *header = findItem<QSGItem>(contentItem, "header");
3171 QCOMPARE(header->x(), -header->width());
3173 QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3175 QCOMPARE(footer->x(), 0.);
3177 QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), header->width());
3178 QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), header->width());
3184 QSGView *canvas = createView();
3187 QDeclarativeContext *ctxt = canvas->rootContext();
3188 ctxt->setContextProperty("testModel", &model);
3190 canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3191 canvas->rootObject()->setProperty("horizontal", true);
3192 canvas->rootObject()->setProperty("rtl", true);
3193 qApp->processEvents();
3195 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3196 QTRY_VERIFY(listview != 0);
3198 QSGItem *contentItem = listview->contentItem();
3199 QTRY_VERIFY(contentItem != 0);
3201 QSGItem *header = findItem<QSGItem>(contentItem, "header");
3203 QCOMPARE(header->x(), 0.);
3205 QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3207 QCOMPARE(footer->x(), -footer->width());
3209 QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), 240. - header->width());
3210 QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), 240. - header->width());
3216 void tst_QSGListView::resizeView()
3218 QSGView *canvas = createView();
3221 for (int i = 0; i < 40; i++)
3222 model.addItem("Item" + QString::number(i), "");
3224 QDeclarativeContext *ctxt = canvas->rootContext();
3225 ctxt->setContextProperty("testModel", &model);
3227 TestObject *testObject = new TestObject;
3228 ctxt->setContextProperty("testObject", testObject);
3230 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3231 qApp->processEvents();
3233 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3234 QTRY_VERIFY(listview != 0);
3236 QSGItem *contentItem = listview->contentItem();
3237 QTRY_VERIFY(contentItem != 0);
3239 // Confirm items positioned correctly
3240 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
3241 for (int i = 0; i < model.count() && i < itemCount; ++i) {
3242 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3243 if (!item) qWarning() << "Item" << i << "not found";
3245 QTRY_COMPARE(item->y(), i*20.);
3248 QVariant heightRatio;
3249 QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3250 QCOMPARE(heightRatio.toReal(), 0.4);
3252 listview->setHeight(200);
3254 QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3255 QCOMPARE(heightRatio.toReal(), 0.25);
3261 void tst_QSGListView::resizeViewAndRepaint()
3263 QSGView *canvas = createView();
3267 for (int i = 0; i < 40; i++)
3268 model.addItem("Item" + QString::number(i), "");
3270 QDeclarativeContext *ctxt = canvas->rootContext();
3271 ctxt->setContextProperty("testModel", &model);
3272 ctxt->setContextProperty("initialHeight", 100);
3274 canvas->setSource(QUrl::fromLocalFile(TESTDATA("resizeview.qml")));
3275 qApp->processEvents();
3277 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3278 QTRY_VERIFY(listview != 0);
3279 QSGItem *contentItem = listview->contentItem();
3280 QTRY_VERIFY(contentItem != 0);
3282 // item at index 10 should not be currently visible
3283 QVERIFY(!findItem<QSGItem>(contentItem, "wrapper", 10));
3285 listview->setHeight(320);
3286 QTRY_VERIFY(findItem<QSGItem>(contentItem, "wrapper", 10));
3288 listview->setHeight(100);
3289 QTRY_VERIFY(!findItem<QSGItem>(contentItem, "wrapper", 10));
3294 void tst_QSGListView::sizeLessThan1()
3296 QSGView *canvas = createView();
3299 for (int i = 0; i < 30; i++)
3300 model.addItem("Item" + QString::number(i), "");
3302 QDeclarativeContext *ctxt = canvas->rootContext();
3303 ctxt->setContextProperty("testModel", &model);
3305 TestObject *testObject = new TestObject;
3306 ctxt->setContextProperty("testObject", testObject);
3308 canvas->setSource(QUrl::fromLocalFile(TESTDATA("sizelessthan1.qml")));
3309 qApp->processEvents();
3311 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3312 QTRY_VERIFY(listview != 0);
3314 QSGItem *contentItem = listview->contentItem();
3315 QTRY_VERIFY(contentItem != 0);
3317 // Confirm items positioned correctly
3318 int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
3319 for (int i = 0; i < model.count() && i < itemCount; ++i) {
3320 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3321 if (!item) qWarning() << "Item" << i << "not found";
3323 QTRY_COMPARE(item->y(), i*0.5);
3330 void tst_QSGListView::QTBUG_14821()
3332 QSGView *canvas = createView();
3334 canvas->setSource(QUrl::fromLocalFile(TESTDATA("qtbug14821.qml")));
3335 qApp->processEvents();
3337 QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3338 QVERIFY(listview != 0);
3340 QSGItem *contentItem = listview->contentItem();
3341 QVERIFY(contentItem != 0);
3343 listview->decrementCurrentIndex();
3344 QCOMPARE(listview->currentIndex(), 99);
3346 listview->incrementCurrentIndex();
3347 QCOMPARE(listview->currentIndex(), 0);
3352 void tst_QSGListView::resizeDelegate()
3354 QSGView *canvas = createView();
3357 QStringList strings;
3358 for (int i = 0; i < 30; ++i)
3359 strings << QString::number(i);
3360 QStringListModel model(strings);
3362 QDeclarativeContext *ctxt = canvas->rootContext();
3363 ctxt->setContextProperty("testModel", &model);
3365 canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
3366 qApp->processEvents();
3368 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3369 QVERIFY(listview != 0);
3371 QSGItem *contentItem = listview->contentItem();
3372 QVERIFY(contentItem != 0);
3374 QCOMPARE(listview->count(), model.rowCount());
3376 listview->setCurrentIndex(25);
3377 listview->setContentY(0);
3380 for (int i = 0; i < 16; ++i) {
3381 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3383 QCOMPARE(item->y(), i*20.0);
3386 QCOMPARE(listview->currentItem()->y(), 500.0);
3387 QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
3389 canvas->rootObject()->setProperty("delegateHeight", 30);
3392 for (int i = 0; i < 11; ++i) {
3393 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3395 QTRY_COMPARE(item->y(), i*30.0);
3398 QTRY_COMPARE(listview->currentItem()->y(), 750.0);
3399 QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
3401 listview->setCurrentIndex(1);
3402 listview->positionViewAtIndex(25, QSGListView::Beginning);
3403 listview->positionViewAtIndex(5, QSGListView::Beginning);
3405 for (int i = 5; i < 16; ++i) {
3406 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3408 QCOMPARE(item->y(), i*30.0);
3411 QTRY_COMPARE(listview->currentItem()->y(), 30.0);
3412 QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
3414 canvas->rootObject()->setProperty("delegateHeight", 20);
3417 for (int i = 5; i < 11; ++i) {
3418 QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3420 QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
3423 QTRY_COMPARE(listview->currentItem()->y(), 70.0);
3424 QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
3429 void tst_QSGListView::resizeFirstDelegate()
3431 // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
3432 // and other delegates have height > 0
3434 QSGView *canvas = createView();
3437 // bug only occurs when all items in the model are visible
3439 for (int i = 0; i < 10; i++)
3440 model.addItem("Item" + QString::number(i), "");
3442 QDeclarativeContext *ctxt = canvas->rootContext();
3443 ctxt->setContextProperty("testModel", &model);
3445 TestObject *testObject = new TestObject;
3446 ctxt->setContextProperty("testObject", testObject);
3448 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3449 qApp->processEvents();
3451 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3452 QVERIFY(listview != 0);
3454 QSGItem *contentItem = listview->contentItem();
3455 QVERIFY(contentItem != 0);
3458 for (int i = 0; i < model.count(); ++i) {
3459 item = findItem<QSGItem>(contentItem, "wrapper", i);
3461 QCOMPARE(item->y(), i*20.0);
3464 item = findItem<QSGItem>(contentItem, "wrapper", 0);
3467 // check the content y has not jumped up and down
3468 QCOMPARE(listview->contentY(), 0.0);
3469 QSignalSpy spy(listview, SIGNAL(contentYChanged()));
3471 QCOMPARE(spy.count(), 0);
3473 for (int i = 1; i < model.count(); ++i) {
3474 item = findItem<QSGItem>(contentItem, "wrapper", i);
3476 QTRY_COMPARE(item->y(), (i-1)*20.0);
3483 void tst_QSGListView::QTBUG_16037()
3485 QSGView *canvas = createView();
3488 canvas->setSource(QUrl::fromLocalFile(TESTDATA("qtbug16037.qml")));
3489 qApp->processEvents();
3491 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "listview");
3492 QTRY_VERIFY(listview != 0);
3494 QVERIFY(listview->contentHeight() <= 0.0);
3496 QMetaObject::invokeMethod(canvas->rootObject(), "setModel");
3498 QTRY_COMPARE(listview->contentHeight(), 80.0);
3503 void tst_QSGListView::indexAt()
3505 QSGView *canvas = createView();
3508 for (int i = 0; i < 30; i++)
3509 model.addItem("Item" + QString::number(i), "");
3511 QDeclarativeContext *ctxt = canvas->rootContext();
3512 ctxt->setContextProperty("testModel", &model);
3514 TestObject *testObject = new TestObject;
3515 ctxt->setContextProperty("testObject", testObject);
3517 canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3518 qApp->processEvents();
3520 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3521 QTRY_VERIFY(listview != 0);
3523 QSGItem *contentItem = listview->contentItem();
3524 QTRY_VERIFY(contentItem != 0);
3526 QCOMPARE(listview->indexAt(0,0), 0);
3527 QCOMPARE(listview->indexAt(0,19), 0);
3528 QCOMPARE(listview->indexAt(239,19), 0);
3529 QCOMPARE(listview->indexAt(0,20), 1);
3530 QCOMPARE(listview->indexAt(240,20), -1);
3536 void tst_QSGListView::incrementalModel()
3538 QSGView *canvas = createView();
3540 IncrementalModel model;
3541 QDeclarativeContext *ctxt = canvas->rootContext();
3542 ctxt->setContextProperty("testModel", &model);
3544 canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
3545 qApp->processEvents();
3547 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3548 QTRY_VERIFY(listview != 0);
3550 QSGItem *contentItem = listview->contentItem();
3551 QTRY_VERIFY(contentItem != 0);
3553 QTRY_COMPARE(listview->count(), 20);
3555 listview->positionViewAtIndex(10, QSGListView::Beginning);
3557 QTRY_COMPARE(listview->count(), 25);
3562 void tst_QSGListView::onAdd()
3564 QFETCH(int, initialItemCount);
3565 QFETCH(int, itemsToAdd);
3567 const int delegateHeight = 10;
3570 // these initial items should not trigger ListView.onAdd
3571 for (int i=0; i<initialItemCount; i++)
3572 model.addItem("dummy value", "dummy value");
3574 QSGView *canvas = createView();
3575 canvas->setGeometry(0,0,200, delegateHeight * (initialItemCount + itemsToAdd));
3576 QDeclarativeContext *ctxt = canvas->rootContext();
3577 ctxt->setContextProperty("testModel", &model);
3578 ctxt->setContextProperty("delegateHeight", delegateHeight);
3579 canvas->setSource(QUrl::fromLocalFile(TESTDATA("attachedSignals.qml")));
3581 QObject *object = canvas->rootObject();
3582 object->setProperty("width", canvas->width());
3583 object->setProperty("height", canvas->height());
3584 qApp->processEvents();
3586 QList<QPair<QString, QString> > items;
3587 for (int i=0; i<itemsToAdd; i++)
3588 items << qMakePair(QString("value %1").arg(i), QString::number(i));
3589 model.addItems(items);
3590 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
3592 QVariantList result = object->property("addedDelegates").toList();
3593 QCOMPARE(result.count(), items.count());
3594 for (int i=0; i<items.count(); i++)
3595 QCOMPARE(result[i].toString(), items[i].first);
3600 void tst_QSGListView::onAdd_data()
3602 QTest::addColumn<int>("initialItemCount");
3603 QTest::addColumn<int>("itemsToAdd");
3605 QTest::newRow("0, add 1") << 0 << 1;
3606 QTest::newRow("0, add 2") << 0 << 2;
3607 QTest::newRow("0, add 10") << 0 << 10;
3609 QTest::newRow("1, add 1") << 1 << 1;
3610 QTest::newRow("1, add 2") << 1 << 2;
3611 QTest::newRow("1, add 10") << 1 << 10;
3613 QTest::newRow("5, add 1") << 5 << 1;
3614 QTest::newRow("5, add 2") << 5 << 2;
3615 QTest::newRow("5, add 10") << 5 << 10;
3618 void tst_QSGListView::onRemove()
3620 QFETCH(int, initialItemCount);
3621 QFETCH(int, indexToRemove);
3622 QFETCH(int, removeCount);
3624 const int delegateHeight = 10;
3626 for (int i=0; i<initialItemCount; i++)
3627 model.addItem(QString("value %1").arg(i), "dummy value");
3629 QSGView *canvas = createView();
3630 QDeclarativeContext *ctxt = canvas->rootContext();
3631 ctxt->setContextProperty("testModel", &model);
3632 ctxt->setContextProperty("delegateHeight", delegateHeight);
3633 canvas->setSource(QUrl::fromLocalFile(TESTDATA("attachedSignals.qml")));
3634 QObject *object = canvas->rootObject();
3636 model.removeItems(indexToRemove, removeCount);
3637 QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
3639 QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
3644 void tst_QSGListView::onRemove_data()
3646 QTest::addColumn<int>("initialItemCount");
3647 QTest::addColumn<int>("indexToRemove");
3648 QTest::addColumn<int>("removeCount");
3650 QTest::newRow("remove first") << 1 << 0 << 1;
3651 QTest::newRow("two items, remove first") << 2 << 0 << 1;
3652 QTest::newRow("two items, remove last") << 2 << 1 << 1;
3653 QTest::newRow("two items, remove all") << 2 << 0 << 2;
3655 QTest::newRow("four items, remove first") << 4 << 0 << 1;
3656 QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
3657 QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
3658 QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
3659 QTest::newRow("four items, remove last") << 4 << 3 << 1;
3660 QTest::newRow("four items, remove all") << 4 << 0 << 4;
3662 QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
3663 QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
3664 QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
3667 void tst_QSGListView::rightToLeft()
3669 QSGView *canvas = createView();
3670 canvas->setGeometry(0,0,640,320);
3671 canvas->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3672 qApp->processEvents();
3674 QVERIFY(canvas->rootObject() != 0);
3675 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
3676 QTRY_VERIFY(listview != 0);
3678 QSGItem *contentItem = listview->contentItem();
3679 QTRY_VERIFY(contentItem != 0);
3681 QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
3682 QTRY_VERIFY(model != 0);
3684 QTRY_VERIFY(model->count() == 3);
3685 QTRY_COMPARE(listview->currentIndex(), 0);
3687 // initial position at first item, right edge aligned
3688 QCOMPARE(listview->contentX(), -640.);
3690 QSGItem *item = findItem<QSGItem>(contentItem, "item1");
3692 QTRY_COMPARE(item->x(), -100.0);
3693 QCOMPARE(item->height(), listview->height());
3695 QSGText *text = findItem<QSGText>(contentItem, "text1");
3697 QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
3699 listview->setCurrentIndex(2);
3701 item = findItem<QSGItem>(contentItem, "item3");
3703 QTRY_COMPARE(item->x(), -540.0);
3705 text = findItem<QSGText>(contentItem, "text3");
3707 QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
3709 QCOMPARE(listview->contentX(), -640.);
3711 // Ensure resizing maintains position relative to right edge
3712 qobject_cast<QSGItem*>(canvas->rootObject())->setWidth(600);
3713 QTRY_COMPARE(listview->contentX(), -600.);
3718 void tst_QSGListView::test_mirroring()
3720 QSGView *canvasA = createView();
3721 canvasA->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3722 QSGListView *listviewA = findItem<QSGListView>(canvasA->rootObject(), "view");
3723 QTRY_VERIFY(listviewA != 0);
3725 QSGView *canvasB = createView();
3726 canvasB->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3727 QSGListView *listviewB = findItem<QSGListView>(canvasB->rootObject(), "view");
3728 QTRY_VERIFY(listviewA != 0);
3729 qApp->processEvents();
3731 QList<QString> objectNames;
3732 objectNames << "item1" << "item2"; // << "item3"
3734 listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3735 listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3736 QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
3739 foreach (const QString objectName, objectNames)
3740 QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3742 listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3743 listviewB->setProperty("layoutDirection", Qt::LeftToRight);
3746 foreach (const QString objectName, objectNames)
3747 QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3749 QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
3750 QSGItemPrivate::get(listviewB)->setLayoutMirror(true);
3751 QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
3753 // LTR != LTR+mirror
3754 foreach (const QString objectName, objectNames)
3755 QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3757 listviewA->setProperty("layoutDirection", Qt::RightToLeft);
3759 // RTL == LTR+mirror
3760 foreach (const QString objectName, objectNames)
3761 QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3763 listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3765 // RTL != RTL+mirror
3766 foreach (const QString objectName, objectNames)
3767 QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3769 listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3771 // LTR == RTL+mirror
3772 foreach (const QString objectName, objectNames)
3773 QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3779 void tst_QSGListView::margins()
3781 QSGView *canvas = createView();
3784 for (int i = 0; i < 50; i++)
3785 model.addItem("Item" + QString::number(i), "");
3787 QDeclarativeContext *ctxt = canvas->rootContext();
3788 ctxt->setContextProperty("testModel", &model);
3790 canvas->setSource(QUrl::fromLocalFile(TESTDATA("margins.qml")));
3792 qApp->processEvents();
3794 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3795 QTRY_VERIFY(listview != 0);
3797 QSGItem *contentItem = listview->contentItem();
3798 QTRY_VERIFY(contentItem != 0);
3800 QCOMPARE(listview->contentY(), -30.);
3801 QCOMPARE(listview->yOrigin(), 0.);
3804 listview->positionViewAtEnd();
3805 qreal pos = listview->contentY();
3806 listview->setContentY(pos + 80);
3807 listview->returnToBounds();
3808 QTRY_COMPARE(listview->contentY(), pos + 50);
3810 // remove item before visible and check that top margin is maintained
3811 // and yOrigin is updated
3812 listview->setContentY(100);
3813 model.removeItem(1);
3815 listview->setContentY(-50);
3816 listview->returnToBounds();
3817 QCOMPARE(listview->yOrigin(), 20.);
3818 QTRY_COMPARE(listview->contentY(), -10.);
3820 // reduce top margin
3821 listview->setTopMargin(20);
3822 QCOMPARE(listview->yOrigin(), 20.);
3823 QTRY_COMPARE(listview->contentY(), 0.);
3826 listview->positionViewAtEnd();
3827 pos = listview->contentY();
3828 listview->setContentY(pos + 80);
3829 listview->returnToBounds();
3830 QTRY_COMPARE(listview->contentY(), pos + 50);
3832 // reduce bottom margin
3833 pos = listview->contentY();
3834 listview->setBottomMargin(40);
3835 QCOMPARE(listview->yOrigin(), 20.);
3836 QTRY_COMPARE(listview->contentY(), pos-10);
3841 void tst_QSGListView::snapToItem_data()
3843 QTest::addColumn<QSGListView::Orientation>("orientation");
3844 QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3845 QTest::addColumn<int>("highlightRangeMode");
3846 QTest::addColumn<QPoint>("flickStart");
3847 QTest::addColumn<QPoint>("flickEnd");
3848 QTest::addColumn<qreal>("snapAlignment");
3849 QTest::addColumn<qreal>("endExtent");
3850 QTest::addColumn<qreal>("startExtent");
3852 QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight << int(QSGItemView::NoHighlightRange)
3853 << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 1200.0 << 0.0;
3855 QTest::newRow("horizontal, left to right") << QSGListView::Horizontal << Qt::LeftToRight << int(QSGItemView::NoHighlightRange)
3856 << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 1200.0 << 0.0;
3858 QTest::newRow("horizontal, right to left") << QSGListView::Horizontal << Qt::RightToLeft << int(QSGItemView::NoHighlightRange)
3859 << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -1200.0 - 240.0 << -240.0;
3861 QTest::newRow("vertical, left to right, enforce range") << QSGListView::Vertical << Qt::LeftToRight << int(QSGItemView::StrictlyEnforceRange)
3862 << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 1340.0 << -20.0;
3864 QTest::newRow("horizontal, left to right, enforce range") << QSGListView::Horizontal << Qt::LeftToRight << int(QSGItemView::StrictlyEnforceRange)
3865 << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 1340.0 << -20.0;
3867 QTest::newRow("horizontal, right to left, enforce range") << QSGListView::Horizontal << Qt::RightToLeft << int(QSGItemView::StrictlyEnforceRange)
3868 << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -1200.0 - 240.0 - 140.0 << -220.0;
3871 void tst_QSGListView::snapToItem()
3873 QFETCH(QSGListView::Orientation, orientation);
3874 QFETCH(Qt::LayoutDirection, layoutDirection);
3875 QFETCH(int, highlightRangeMode);
3876 QFETCH(QPoint, flickStart);
3877 QFETCH(QPoint, flickEnd);
3878 QFETCH(qreal, snapAlignment);
3879 QFETCH(qreal, endExtent);
3880 QFETCH(qreal, startExtent);
3882 QSGView *canvas = createView();
3884 canvas->setSource(QUrl::fromLocalFile(TESTDATA("snapToItem.qml")));
3886 qApp->processEvents();
3888 QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3889 QTRY_VERIFY(listview != 0);
3891 listview->setOrientation(orientation);
3892 listview->setLayoutDirection(layoutDirection);
3893 listview->setHighlightRangeMode(QSGItemView::HighlightRangeMode(highlightRangeMode));
3895 QSGItem *contentItem = listview->contentItem();
3896 QTRY_VERIFY(contentItem != 0);
3898 // confirm that a flick hits an item boundary
3899 flick(canvas, flickStart, flickEnd, 180);
3900 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3901 if (orientation == QSGListView::Vertical)
3902 QCOMPARE(qreal(fmod(listview->contentY(),80.0)), snapAlignment);
3904 QCOMPARE(qreal(fmod(listview->contentX(),80.0)), snapAlignment);
3908 flick(canvas, flickStart, flickEnd, 180);
3909 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3910 } while (orientation == QSGListView::Vertical
3911 ? !listview->isAtYEnd()
3912 : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
3914 if (orientation == QSGListView::Vertical)
3915 QCOMPARE(listview->contentY(), endExtent);
3917 QCOMPARE(listview->contentX(), endExtent);
3921 flick(canvas, flickEnd, flickStart, 180);
3922 QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3923 } while (orientation == QSGListView::Vertical
3924 ? !listview->isAtYBeginning()
3925 : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
3927 if (orientation == QSGListView::Vertical)
3928 QCOMPARE(listview->contentY(), startExtent);
3930 QCOMPARE(listview->contentX(), startExtent);
3935 void tst_QSGListView::qListModelInterface_items()
3940 void tst_QSGListView::qAbstractItemModel_items()
3942 items<TestModel2>();
3945 void tst_QSGListView::qListModelInterface_changed()
3947 changed<TestModel>();
3950 void tst_QSGListView::qAbstractItemModel_changed()
3952 changed<TestModel2>();
3955 void tst_QSGListView::qListModelInterface_inserted()
3957 inserted<TestModel>();
3960 void tst_QSGListView::qListModelInterface_inserted_more()
3962 inserted_more<TestModel>();
3965 void tst_QSGListView::qListModelInterface_inserted_more_data()
3967 inserted_more_data();
3970 void tst_QSGListView::qAbstractItemModel_inserted()
3972 inserted<TestModel2>();
3975 void tst_QSGListView::qAbstractItemModel_inserted_more()
3977 inserted_more<TestModel2>();
3980 void tst_QSGListView::qAbstractItemModel_inserted_more_data()
3982 inserted_more_data();
3985 void tst_QSGListView::qListModelInterface_removed()
3987 removed<TestModel>(false);
3988 removed<TestModel>(true);
3991 void tst_QSGListView::qAbstractItemModel_removed()
3993 removed<TestModel2>(false);
3994 removed<TestModel2>(true);
3997 void tst_QSGListView::qListModelInterface_moved()
4002 void tst_QSGListView::qListModelInterface_moved_data()
4007 void tst_QSGListView::qAbstractItemModel_moved()
4009 moved<TestModel2>();
4012 void tst_QSGListView::qAbstractItemModel_moved_data()
4017 void tst_QSGListView::qListModelInterface_clear()
4022 void tst_QSGListView::qAbstractItemModel_clear()
4024 clear<TestModel2>();
4027 void tst_QSGListView::creationContext()
4030 canvas.setGeometry(0,0,240,320);
4031 canvas.setSource(QUrl::fromLocalFile(TESTDATA("creationContext.qml")));
4032 qApp->processEvents();
4034 QSGItem *rootItem = qobject_cast<QSGItem *>(canvas.rootObject());
4036 QVERIFY(rootItem->property("count").toInt() > 0);
4039 QVERIFY(item = rootItem->findChild<QSGItem *>("listItem"));
4040 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4041 QVERIFY(item = rootItem->findChild<QSGItem *>("header"));
4042 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4043 QVERIFY(item = rootItem->findChild<QSGItem *>("footer"));
4044 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4045 QVERIFY(item = rootItem->findChild<QSGItem *>("section"));
4046 QCOMPARE(item->property("text").toString(), QString("Hello!"));
4049 QSGView *tst_QSGListView::createView()
4051 QSGView *canvas = new QSGView(0);
4052 canvas->setGeometry(0,0,240,320);
4057 void tst_QSGListView::flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration)
4059 const int pointCount = 5;
4060 QPoint diff = to - from;
4062 // send press, five equally spaced moves, and release.
4063 QTest::mousePress(canvas, Qt::LeftButton, 0, from);
4065 for (int i = 0; i < pointCount; ++i) {
4066 QMouseEvent mv(QEvent::MouseMove, from + (i+1)*diff/pointCount, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
4067 QApplication::sendEvent(canvas, &mv);
4068 QTest::qWait(duration/pointCount);
4069 QCoreApplication::processEvents();
4072 QTest::mouseRelease(canvas, Qt::LeftButton, 0, to);
4076 QSGItem *tst_QSGListView::findVisibleChild(QSGItem *parent, const QString &objectName)
4079 QList<QSGItem*> items = parent->findChildren<QSGItem*>(objectName);
4080 for (int i = 0; i < items.count(); ++i) {
4081 if (items.at(i)->isVisible()) {
4089 Find an item with the specified objectName. If index is supplied then the
4090 item must also evaluate the {index} expression equal to index
4092 template<typename T>
4093 T *tst_QSGListView::findItem(QSGItem *parent, const QString &objectName, int index)
4095 const QMetaObject &mo = T::staticMetaObject;
4096 //qDebug() << parent->childItems().count() << "children";
4097 for (int i = 0; i < parent->childItems().count(); ++i) {
4098 QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4101 //qDebug() << "try" << item;
4102 if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName)) {
4104 QDeclarativeExpression e(qmlContext(item), item, "index");
4105 if (e.evaluate().toInt() == index)
4106 return static_cast<T*>(item);
4108 return static_cast<T*>(item);
4111 item = findItem<T>(item, objectName, index);
4113 return static_cast<T*>(item);
4119 template<typename T>
4120 QList<T*> tst_QSGListView::findItems(QSGItem *parent, const QString &objectName)
4123 const QMetaObject &mo = T::staticMetaObject;
4124 //qDebug() << parent->childItems().count() << "children";
4125 for (int i = 0; i < parent->childItems().count(); ++i) {
4126 QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4127 if (!item || !item->isVisible())
4129 //qDebug() << "try" << item;
4130 if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName))
4131 items.append(static_cast<T*>(item));
4132 items += findItems<T>(item, objectName);
4138 void tst_QSGListView::dumpTree(QSGItem *parent, int depth)
4140 static QString padding(" ");
4141 for (int i = 0; i < parent->childItems().count(); ++i) {
4142 QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4145 qDebug() << padding.left(depth*2) << item;
4146 dumpTree(item, depth+1);
4150 QTEST_MAIN(tst_QSGListView)
4152 #include "tst_qsglistview.moc"