Unit test for QTBUG-21742
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qsglistview / tst_qsglistview.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
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.
17 **
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.
21 **
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.
29 **
30 ** Other Usage
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.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
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 "incrementalmodel.h"
57 #include <math.h>
58
59 Q_DECLARE_METATYPE(Qt::LayoutDirection)
60 Q_DECLARE_METATYPE(QSGListView::Orientation)
61
62 class tst_QSGListView : public QObject
63 {
64     Q_OBJECT
65 public:
66     tst_QSGListView();
67
68 private slots:
69     void initTestCase();
70     void cleanupTestCase();
71     // Test both QListModelInterface and QAbstractItemModel model types
72     void qListModelInterface_items();
73     void qAbstractItemModel_items();
74
75     void qListModelInterface_changed();
76     void qAbstractItemModel_changed();
77
78     void qListModelInterface_inserted();
79     void qListModelInterface_inserted_more();
80     void qListModelInterface_inserted_more_data();
81     void qAbstractItemModel_inserted();
82     void qAbstractItemModel_inserted_more();
83     void qAbstractItemModel_inserted_more_data();
84
85     void qListModelInterface_removed();
86     void qAbstractItemModel_removed();
87
88     void qListModelInterface_moved();
89     void qListModelInterface_moved_data();
90     void qAbstractItemModel_moved();
91     void qAbstractItemModel_moved_data();
92
93     void multipleChanges();
94     void multipleChanges_data();
95
96     void qListModelInterface_clear();
97     void qAbstractItemModel_clear();
98
99     void swapWithFirstItem();
100     void itemList();
101     void currentIndex_delayedItemCreation();
102     void currentIndex_delayedItemCreation_data();
103     void currentIndex();
104     void noCurrentIndex();
105     void enforceRange();
106     void enforceRange_withoutHighlight();
107     void spacing();
108     void sections();
109     void sectionsPositioning();
110     void sectionsDelegate();
111     void cacheBuffer();
112     void positionViewAtIndex();
113     void resetModel();
114     void propertyChanges();
115     void componentChanges();
116     void modelChanges();
117     void manualHighlight();
118     void header();
119     void header_data();
120     void header_delayItemCreation();
121     void footer();
122     void footer_data();
123     void headerFooter();
124     void resizeView();
125     void resizeViewAndRepaint();
126     void sizeLessThan1();
127     void QTBUG_14821();
128     void resizeDelegate();
129     void resizeFirstDelegate();
130     void QTBUG_16037();
131     void indexAt();
132     void incrementalModel();
133     void onAdd();
134     void onAdd_data();
135     void onRemove();
136     void onRemove_data();
137     void rightToLeft();
138     void test_mirroring();
139     void margins();
140     void creationContext();
141     void snapToItem_data();
142     void snapToItem();
143
144     void QTBUG_9791();
145     void QTBUG_11105();
146     void QTBUG_21742();
147
148 private:
149     template <class T> void items();
150     template <class T> void changed();
151     template <class T> void inserted();
152     template <class T> void inserted_more();
153     template <class T> void removed(bool animated);
154     template <class T> void moved();
155     template <class T> void clear();
156     QSGView *createView();
157     void flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration);
158     QSGItem *findVisibleChild(QSGItem *parent, const QString &objectName);
159     template<typename T>
160     T *findItem(QSGItem *parent, const QString &id, int index=-1);
161     template<typename T>
162     QList<T*> findItems(QSGItem *parent, const QString &objectName);
163     void dumpTree(QSGItem *parent, int depth = 0);
164
165     void inserted_more_data();
166     void moved_data();
167 };
168
169 void tst_QSGListView::initTestCase()
170 {
171 }
172
173 void tst_QSGListView::cleanupTestCase()
174 {
175
176 }
177 class TestObject : public QObject
178 {
179     Q_OBJECT
180
181     Q_PROPERTY(bool error READ error WRITE setError NOTIFY changedError)
182     Q_PROPERTY(bool animate READ animate NOTIFY changedAnim)
183     Q_PROPERTY(bool invalidHighlight READ invalidHighlight NOTIFY changedHl)
184     Q_PROPERTY(int cacheBuffer READ cacheBuffer NOTIFY changedCacheBuffer)
185
186 public:
187     TestObject(QObject *parent = 0)
188         : QObject(parent), mError(true), mAnimate(false), mInvalidHighlight(false)
189         , mCacheBuffer(0) {}
190
191     bool error() const { return mError; }
192     void setError(bool err) { mError = err; emit changedError(); }
193
194     bool animate() const { return mAnimate; }
195     void setAnimate(bool anim) { mAnimate = anim; emit changedAnim(); }
196
197     bool invalidHighlight() const { return mInvalidHighlight; }
198     void setInvalidHighlight(bool invalid) { mInvalidHighlight = invalid; emit changedHl(); }
199
200     int cacheBuffer() const { return mCacheBuffer; }
201     void setCacheBuffer(int buffer) { mCacheBuffer = buffer; emit changedCacheBuffer(); }
202
203 signals:
204     void changedError();
205     void changedAnim();
206     void changedHl();
207     void changedCacheBuffer();
208
209 public:
210     bool mError;
211     bool mAnimate;
212     bool mInvalidHighlight;
213     int mCacheBuffer;
214 };
215
216 template<typename T>
217 void tst_qsglistview_move(int from, int to, int n, T *items)
218 {
219     if (from > to) {
220         // Only move forwards - flip if backwards moving
221         int tfrom = from;
222         int tto = to;
223         from = tto;
224         to = tto+n;
225         n = tfrom-tto;
226     }
227     if (n == 1) {
228         items->move(from, to);
229     } else {
230         T replaced;
231         int i=0;
232         typename T::ConstIterator it=items->begin(); it += from+n;
233         for (; i<to-from; ++i,++it)
234             replaced.append(*it);
235         i=0;
236         it=items->begin(); it += from;
237         for (; i<n; ++i,++it)
238             replaced.append(*it);
239         typename T::ConstIterator f=replaced.begin();
240         typename T::Iterator t=items->begin(); t += from;
241         for (; f != replaced.end(); ++f, ++t)
242             *t = *f;
243     }
244 }
245
246 class TestModel : public QListModelInterface
247 {
248     Q_OBJECT
249 public:
250     TestModel(QObject *parent = 0) : QListModelInterface(parent) {}
251     ~TestModel() {}
252
253     enum Roles { Name, Number };
254
255     QString name(int index) const { return list.at(index).first; }
256     QString number(int index) const { return list.at(index).second; }
257
258     int count() const { return list.count(); }
259
260     QList<int> roles() const { return QList<int>() << Name << Number; }
261     QString toString(int role) const {
262         switch (role) {
263         case Name:
264             return "name";
265         case Number:
266             return "number";
267         default:
268             return "";
269         }
270     }
271
272     QVariant data(int index, int role) const
273     {
274         if (role==0)
275             return list.at(index).first;
276         if (role==1)
277             return list.at(index).second;
278         return QVariant();
279     }
280     QHash<int, QVariant> data(int index, const QList<int> &roles) const {
281         QHash<int,QVariant> returnHash;
282
283         for (int i = 0; i < roles.size(); ++i) {
284             int role = roles.at(i);
285             QVariant info;
286             switch (role) {
287             case Name:
288                 info = list.at(index).first;
289                 break;
290             case Number:
291                 info = list.at(index).second;
292                 break;
293             default:
294                 break;
295             }
296             returnHash.insert(role, info);
297         }
298         return returnHash;
299     }
300
301     void addItem(const QString &name, const QString &number) {
302         list.append(QPair<QString,QString>(name, number));
303         emit itemsInserted(list.count()-1, 1);
304     }
305
306     void insertItem(int index, const QString &name, const QString &number) {
307         list.insert(index, QPair<QString,QString>(name, number));
308         emit itemsInserted(index, 1);
309     }
310
311     void insertItems(int index, const QList<QPair<QString, QString> > &items) {
312         for (int i=0; i<items.count(); i++)
313             list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
314         emit itemsInserted(index, items.count());
315     }
316
317     void removeItem(int index) {
318         list.removeAt(index);
319         emit itemsRemoved(index, 1);
320     }
321
322     void removeItems(int index, int count) {
323         int c = count;
324         while (c--)
325             list.removeAt(index);
326         emit itemsRemoved(index, count);
327     }
328
329     void moveItem(int from, int to) {
330         list.move(from, to);
331         emit itemsMoved(from, to, 1);
332     }
333
334     void moveItems(int from, int to, int count) {
335         tst_qsglistview_move(from, to, count, &list);
336         emit itemsMoved(from, to, count);
337     }
338
339     void modifyItem(int index, const QString &name, const QString &number) {
340         list[index] = QPair<QString,QString>(name, number);
341         emit itemsChanged(index, 1, roles());
342     }
343
344     void clear() {
345         int count = list.count();
346         list.clear();
347         emit itemsRemoved(0, count);
348     }
349
350 private:
351     QList<QPair<QString,QString> > list;
352 };
353
354
355 class TestModel2 : public QAbstractListModel
356 {
357 public:
358     enum Roles { Name = Qt::UserRole+1, Number = Qt::UserRole+2 };
359
360     TestModel2(QObject *parent=0) : QAbstractListModel(parent) {
361         QHash<int, QByteArray> roles;
362         roles[Name] = "name";
363         roles[Number] = "number";
364         setRoleNames(roles);
365     }
366
367     int rowCount(const QModelIndex &parent=QModelIndex()) const { Q_UNUSED(parent); return list.count(); }
368     QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const {
369         QVariant rv;
370         if (role == Name)
371             rv = list.at(index.row()).first;
372         else if (role == Number)
373             rv = list.at(index.row()).second;
374
375         return rv;
376     }
377
378     int count() const { return rowCount(); }
379     QString name(int index) const { return list.at(index).first; }
380     QString number(int index) const { return list.at(index).second; }
381
382     void addItem(const QString &name, const QString &number) {
383         emit beginInsertRows(QModelIndex(), list.count(), list.count());
384         list.append(QPair<QString,QString>(name, number));
385         emit endInsertRows();
386     }
387
388     void addItems(const QList<QPair<QString, QString> > &items) {
389         emit beginInsertRows(QModelIndex(), list.count(), list.count()+items.count()-1);
390         for (int i=0; i<items.count(); i++)
391             list.append(QPair<QString,QString>(items[i].first, items[i].second));
392         emit endInsertRows();
393     }
394
395     void insertItem(int index, const QString &name, const QString &number) {
396         emit beginInsertRows(QModelIndex(), index, index);
397         list.insert(index, QPair<QString,QString>(name, number));
398         emit endInsertRows();
399     }
400
401     void insertItems(int index, const QList<QPair<QString, QString> > &items) {
402         emit beginInsertRows(QModelIndex(), index, index+items.count()-1);
403         for (int i=0; i<items.count(); i++)
404             list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
405         emit endInsertRows();
406     }
407
408     void removeItem(int index) {
409         emit beginRemoveRows(QModelIndex(), index, index);
410         list.removeAt(index);
411         emit endRemoveRows();
412     }
413
414     void removeItems(int index, int count) {
415         emit beginRemoveRows(QModelIndex(), index, index+count-1);
416         while (count--)
417             list.removeAt(index);
418         emit endRemoveRows();
419     }
420
421     void moveItem(int from, int to) {
422         emit beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
423         list.move(from, to);
424         emit endMoveRows();
425     }
426
427     void moveItems(int from, int to, int count) {
428         emit beginMoveRows(QModelIndex(), from, from+count-1, QModelIndex(), to > from ? to+count : to);
429         tst_qsglistview_move(from, to, count, &list);
430         emit endMoveRows();
431     }
432
433     void modifyItem(int idx, const QString &name, const QString &number) {
434         list[idx] = QPair<QString,QString>(name, number);
435         emit dataChanged(index(idx,0), index(idx,0));
436     }
437
438     void clear() {
439         int count = list.count();
440         emit beginRemoveRows(QModelIndex(), 0, count-1);
441         list.clear();
442         emit endRemoveRows();
443     }
444
445 private:
446     QList<QPair<QString,QString> > list;
447 };
448
449 tst_QSGListView::tst_QSGListView()
450 {
451 }
452
453 template <class T>
454 void tst_QSGListView::items()
455 {
456     QSGView *canvas = createView();
457
458     T model;
459     model.addItem("Fred", "12345");
460     model.addItem("John", "2345");
461     model.addItem("Bob", "54321");
462
463     QDeclarativeContext *ctxt = canvas->rootContext();
464     ctxt->setContextProperty("testModel", &model);
465
466     TestObject *testObject = new TestObject;
467     ctxt->setContextProperty("testObject", testObject);
468
469     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
470     qApp->processEvents();
471
472     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
473     QTRY_VERIFY(listview != 0);
474
475     QSGItem *contentItem = listview->contentItem();
476     QTRY_VERIFY(contentItem != 0);
477
478     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
479     QTRY_VERIFY(testObject->error() == false);
480
481     QTRY_VERIFY(listview->highlightItem() != 0);
482     QTRY_COMPARE(listview->count(), model.count());
483     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
484     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
485
486     // current item should be first item
487     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
488
489     for (int i = 0; i < model.count(); ++i) {
490         QSGText *name = findItem<QSGText>(contentItem, "textName", i);
491         QTRY_VERIFY(name != 0);
492         QTRY_COMPARE(name->text(), model.name(i));
493         QSGText *number = findItem<QSGText>(contentItem, "textNumber", i);
494         QTRY_VERIFY(number != 0);
495         QTRY_COMPARE(number->text(), model.number(i));
496     }
497
498     // switch to other delegate
499     testObject->setAnimate(true);
500     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
501     QTRY_VERIFY(testObject->error() == false);
502     QTRY_VERIFY(listview->currentItem());
503
504     // set invalid highlight
505     testObject->setInvalidHighlight(true);
506     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
507     QTRY_VERIFY(testObject->error() == false);
508     QTRY_VERIFY(listview->currentItem());
509     QTRY_VERIFY(listview->highlightItem() == 0);
510
511     // back to normal highlight
512     testObject->setInvalidHighlight(false);
513     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
514     QTRY_VERIFY(testObject->error() == false);
515     QTRY_VERIFY(listview->currentItem());
516     QTRY_VERIFY(listview->highlightItem() != 0);
517
518     // set an empty model and confirm that items are destroyed
519     T model2;
520     ctxt->setContextProperty("testModel", &model2);
521
522     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
523     QTRY_VERIFY(itemCount == 0);
524
525     QTRY_COMPARE(listview->highlightResizeSpeed(), 1000.0);
526     QTRY_COMPARE(listview->highlightMoveSpeed(), 1000.0);
527
528     delete canvas;
529     delete testObject;
530 }
531
532
533 template <class T>
534 void tst_QSGListView::changed()
535 {
536     QSGView *canvas = createView();
537
538     T model;
539     model.addItem("Fred", "12345");
540     model.addItem("John", "2345");
541     model.addItem("Bob", "54321");
542
543     QDeclarativeContext *ctxt = canvas->rootContext();
544     ctxt->setContextProperty("testModel", &model);
545
546     TestObject *testObject = new TestObject;
547     ctxt->setContextProperty("testObject", testObject);
548
549     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
550     qApp->processEvents();
551
552     QSGFlickable *listview = findItem<QSGFlickable>(canvas->rootObject(), "list");
553     QTRY_VERIFY(listview != 0);
554
555     QSGItem *contentItem = listview->contentItem();
556     QTRY_VERIFY(contentItem != 0);
557
558     model.modifyItem(1, "Will", "9876");
559     QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
560     QTRY_VERIFY(name != 0);
561     QTRY_COMPARE(name->text(), model.name(1));
562     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
563     QTRY_VERIFY(number != 0);
564     QTRY_COMPARE(number->text(), model.number(1));
565
566     delete canvas;
567     delete testObject;
568 }
569
570 template <class T>
571 void tst_QSGListView::inserted()
572 {
573     QSGView *canvas = createView();
574     canvas->show();
575
576     T model;
577     model.addItem("Fred", "12345");
578     model.addItem("John", "2345");
579     model.addItem("Bob", "54321");
580
581     QDeclarativeContext *ctxt = canvas->rootContext();
582     ctxt->setContextProperty("testModel", &model);
583
584     TestObject *testObject = new TestObject;
585     ctxt->setContextProperty("testObject", testObject);
586
587     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
588     qApp->processEvents();
589
590     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
591     QTRY_VERIFY(listview != 0);
592
593     QSGItem *contentItem = listview->contentItem();
594     QTRY_VERIFY(contentItem != 0);
595
596     model.insertItem(1, "Will", "9876");
597
598     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
599     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
600
601     QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
602     QTRY_VERIFY(name != 0);
603     QTRY_COMPARE(name->text(), model.name(1));
604     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
605     QTRY_VERIFY(number != 0);
606     QTRY_COMPARE(number->text(), model.number(1));
607
608     // Confirm items positioned correctly
609     for (int i = 0; i < model.count(); ++i) {
610         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
611         QTRY_COMPARE(item->y(), i*20.0);
612     }
613
614     model.insertItem(0, "Foo", "1111"); // zero index, and current item
615
616     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
617     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
618
619     name = findItem<QSGText>(contentItem, "textName", 0);
620     QTRY_VERIFY(name != 0);
621     QTRY_COMPARE(name->text(), model.name(0));
622     number = findItem<QSGText>(contentItem, "textNumber", 0);
623     QTRY_VERIFY(number != 0);
624     QTRY_COMPARE(number->text(), model.number(0));
625
626     QTRY_COMPARE(listview->currentIndex(), 1);
627
628     // Confirm items positioned correctly
629     for (int i = 0; i < model.count(); ++i) {
630         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
631         QTRY_COMPARE(item->y(), i*20.0);
632     }
633
634     for (int i = model.count(); i < 30; ++i)
635         model.insertItem(i, "Hello", QString::number(i));
636
637     listview->setContentY(80);
638
639     // Insert item outside visible area
640     model.insertItem(1, "Hello", "1324");
641
642     QTRY_VERIFY(listview->contentY() == 80);
643
644     // Confirm items positioned correctly
645     for (int i = 5; i < 5+15; ++i) {
646         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
647         if (!item) qWarning() << "Item" << i << "not found";
648         QTRY_VERIFY(item);
649         QTRY_COMPARE(item->y(), i*20.0 - 20.0);
650     }
651
652 //    QTRY_COMPARE(listview->contentItemHeight(), model.count() * 20.0);
653
654     // QTBUG-19675
655     model.clear();
656     model.insertItem(0, "Hello", "1234");
657     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
658
659     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
660     QVERIFY(item);
661     QCOMPARE(item->y(), 0.);
662     QVERIFY(listview->contentY() == 0);
663
664     delete canvas;
665     delete testObject;
666 }
667
668 template <class T>
669 void tst_QSGListView::inserted_more()
670 {
671     QFETCH(qreal, contentY);
672     QFETCH(int, insertIndex);
673     QFETCH(int, insertCount);
674     QFETCH(qreal, itemsOffsetAfterMove);
675
676     QSGText *name;
677     QSGText *number;
678     QSGView *canvas = createView();
679     canvas->show();
680
681     T model;
682     for (int i = 0; i < 30; i++)
683         model.addItem("Item" + QString::number(i), "");
684
685     QDeclarativeContext *ctxt = canvas->rootContext();
686     ctxt->setContextProperty("testModel", &model);
687
688     TestObject *testObject = new TestObject;
689     ctxt->setContextProperty("testObject", testObject);
690
691     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
692     qApp->processEvents();
693
694     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
695     QTRY_VERIFY(listview != 0);
696     QSGItem *contentItem = listview->contentItem();
697     QTRY_VERIFY(contentItem != 0);
698
699     listview->setContentY(contentY);
700
701     QList<QPair<QString, QString> > newData;
702     for (int i=0; i<insertCount; i++)
703         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
704     model.insertItems(insertIndex, newData);
705     QTRY_COMPARE(listview->property("count").toInt(), model.count());
706
707     // check visibleItems.first() is in correct position
708     QSGItem *item0 = findItem<QSGItem>(contentItem, "wrapper", 0);
709     QVERIFY(item0);
710     QCOMPARE(item0->y(), itemsOffsetAfterMove);
711
712     QList<QSGItem*> items = findItems<QSGItem>(contentItem, "wrapper");
713     int firstVisibleIndex = -1;
714     for (int i=0; i<items.count(); i++) {
715         if (items[i]->y() >= contentY) {
716             QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
717             firstVisibleIndex = e.evaluate().toInt();
718             break;
719         }
720     }
721     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
722
723     // Confirm items positioned correctly and indexes correct
724     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
725     for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
726         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
727         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
728         QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
729         name = findItem<QSGText>(contentItem, "textName", i);
730         QVERIFY(name != 0);
731         QTRY_COMPARE(name->text(), model.name(i));
732         number = findItem<QSGText>(contentItem, "textNumber", i);
733         QVERIFY(number != 0);
734         QTRY_COMPARE(number->text(), model.number(i));
735     }
736
737     delete canvas;
738     delete testObject;
739 }
740
741 void tst_QSGListView::inserted_more_data()
742 {
743     QTest::addColumn<qreal>("contentY");
744     QTest::addColumn<int>("insertIndex");
745     QTest::addColumn<int>("insertCount");
746     QTest::addColumn<qreal>("itemsOffsetAfterMove");
747
748     QTest::newRow("add 1, before visible items")
749             << 80.0     // show 4-19
750             << 3 << 1
751             << -20.0;   // insert above first visible i.e. 0 is at -20, first visible should not move
752
753     QTest::newRow("add multiple, before visible")
754             << 80.0     // show 4-19
755             << 3 << 3
756             << -20.0 * 3;   // again first visible should not move
757
758     QTest::newRow("add 1, at start of visible, content at start")
759             << 0.0
760             << 0 << 1
761             << 0.0;
762
763     QTest::newRow("add multiple, start of visible, content at start")
764             << 0.0
765             << 0 << 3
766             << 0.0;
767
768     QTest::newRow("add 1, at start of visible, content not at start")
769             << 80.0     // show 4-19
770             << 4 << 1
771             << 0.0;
772
773     QTest::newRow("add multiple, at start of visible, content not at start")
774             << 80.0     // show 4-19
775             << 4 << 3
776             << 0.0;
777
778
779     QTest::newRow("add 1, at end of visible, content at start")
780             << 0.0
781             << 15 << 1
782             << 0.0;
783
784     QTest::newRow("add 1, at end of visible, content at start")
785             << 0.0
786             << 15 << 3
787             << 0.0;
788
789     QTest::newRow("add 1, at end of visible, content not at start")
790             << 80.0     // show 4-19
791             << 19 << 1
792             << 0.0;
793
794     QTest::newRow("add multiple, at end of visible, content not at start")
795             << 80.0     // show 4-19
796             << 19 << 3
797             << 0.0;
798
799
800     QTest::newRow("add 1, after visible, content at start")
801             << 0.0
802             << 16 << 1
803             << 0.0;
804
805     QTest::newRow("add 1, after visible, content at start")
806             << 0.0
807             << 16 << 3
808             << 0.0;
809
810     QTest::newRow("add 1, after visible, content not at start")
811             << 80.0     // show 4-19
812             << 20 << 1
813             << 0.0;
814
815     QTest::newRow("add multiple, after visible, content not at start")
816             << 80.0     // show 4-19
817             << 20 << 3
818             << 0.0;
819 }
820
821 template <class T>
822 void tst_QSGListView::removed(bool animated)
823 {
824     QSGView *canvas = createView();
825
826     T model;
827     for (int i = 0; i < 50; i++)
828         model.addItem("Item" + QString::number(i), "");
829
830     QDeclarativeContext *ctxt = canvas->rootContext();
831     ctxt->setContextProperty("testModel", &model);
832
833     TestObject *testObject = new TestObject;
834     ctxt->setContextProperty("testObject", testObject);
835
836     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
837     canvas->show();
838     qApp->processEvents();
839
840     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
841     QTRY_VERIFY(listview != 0);
842
843     QSGItem *contentItem = listview->contentItem();
844     QTRY_VERIFY(contentItem != 0);
845
846     model.removeItem(1);
847     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
848
849     QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
850     QTRY_VERIFY(name != 0);
851     QTRY_COMPARE(name->text(), model.name(1));
852     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
853     QTRY_VERIFY(number != 0);
854     QTRY_COMPARE(number->text(), model.number(1));
855
856     // Confirm items positioned correctly
857     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
858     for (int i = 0; i < model.count() && i < itemCount; ++i) {
859         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
860         if (!item) qWarning() << "Item" << i << "not found";
861         QTRY_VERIFY(item);
862         QTRY_VERIFY(item->y() == i*20);
863     }
864
865     // Remove first item (which is the current item);
866     model.removeItem(0);  // post: top item starts at 20
867     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
868
869     name = findItem<QSGText>(contentItem, "textName", 0);
870     QTRY_VERIFY(name != 0);
871     QTRY_COMPARE(name->text(), model.name(0));
872     number = findItem<QSGText>(contentItem, "textNumber", 0);
873     QTRY_VERIFY(number != 0);
874     QTRY_COMPARE(number->text(), model.number(0));
875
876     // Confirm items positioned correctly
877     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
878     for (int i = 0; i < model.count() && i < itemCount; ++i) {
879         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
880         if (!item) qWarning() << "Item" << i << "not found";
881         QTRY_VERIFY(item);
882         QTRY_COMPARE(item->y(),i*20.0 + 20.0);
883     }
884
885     // Remove items not visible
886     model.removeItem(18);
887     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
888
889     // Confirm items positioned correctly
890     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
891     for (int i = 0; i < model.count() && i < itemCount; ++i) {
892         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
893         if (!item) qWarning() << "Item" << i << "not found";
894         QTRY_VERIFY(item);
895         QTRY_COMPARE(item->y(),i*20.0+20.0);
896     }
897
898     // Remove items before visible
899     listview->setContentY(80);
900     listview->setCurrentIndex(10);
901
902     model.removeItem(1); // post: top item will be at 40
903     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
904
905     // Confirm items positioned correctly
906     for (int i = 2; i < 18; ++i) {
907         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
908         if (!item) qWarning() << "Item" << i << "not found";
909         QTRY_VERIFY(item);
910         QTRY_COMPARE(item->y(),40+i*20.0);
911     }
912
913     // Remove current index
914     QTRY_VERIFY(listview->currentIndex() == 9);
915     QSGItem *oldCurrent = listview->currentItem();
916     model.removeItem(9);
917
918     QTRY_COMPARE(listview->currentIndex(), 9);
919     QTRY_VERIFY(listview->currentItem() != oldCurrent);
920
921     listview->setContentY(40); // That's the top now
922     // let transitions settle.
923     QTest::qWait(300);
924
925     // Confirm items positioned correctly
926     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
927     for (int i = 0; i < model.count() && i < itemCount; ++i) {
928         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
929         if (!item) qWarning() << "Item" << i << "not found";
930         QTRY_VERIFY(item);
931         QTRY_COMPARE(item->y(),40+i*20.0);
932     }
933
934     // remove current item beyond visible items.
935     listview->setCurrentIndex(20);
936     listview->setContentY(40);
937     model.removeItem(20);
938
939     QTRY_COMPARE(listview->currentIndex(), 20);
940     QTRY_VERIFY(listview->currentItem() != 0);
941
942     // remove item before current, but visible
943     listview->setCurrentIndex(8);
944     oldCurrent = listview->currentItem();
945     model.removeItem(6);
946
947     QTRY_COMPARE(listview->currentIndex(), 7);
948     QTRY_VERIFY(listview->currentItem() == oldCurrent);
949
950     listview->setContentY(80);
951     QTest::qWait(300);
952
953     // remove all visible items
954     model.removeItems(1, 18);
955     QTRY_COMPARE(listview->count() , model.count());
956
957     // Confirm items positioned correctly
958     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
959     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
960         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i+2);
961         if (!item) qWarning() << "Item" << i+2 << "not found";
962         QTRY_VERIFY(item);
963         QTRY_COMPARE(item->y(),80+i*20.0);
964     }
965
966     model.removeItems(1, 17);
967     QTRY_COMPARE(listview->count() , model.count());
968
969     model.removeItems(2, 1);
970     QTRY_COMPARE(listview->count() , model.count());
971
972     model.addItem("New", "1");
973     QTRY_COMPARE(listview->count() , model.count());
974
975     QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", model.count()-1));
976     QCOMPARE(name->text(), QString("New"));
977
978     // Add some more items so that we don't run out
979     model.clear();
980     for (int i = 0; i < 50; i++)
981         model.addItem("Item" + QString::number(i), "");
982
983     // QTBUG-QTBUG-20575
984     listview->setCurrentIndex(0);
985     listview->setContentY(30);
986     model.removeItem(0);
987     QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", 0));
988
989     // QTBUG-19198 move to end and remove all visible items one at a time.
990     listview->positionViewAtEnd();
991     for (int i = 0; i < 18; ++i)
992         model.removeItems(model.count() - 1, 1);
993     QTRY_VERIFY(findItems<QSGItem>(contentItem, "wrapper").count() > 16);
994
995     delete canvas;
996     delete testObject;
997 }
998
999 template <class T>
1000 void tst_QSGListView::clear()
1001 {
1002     QSGView *canvas = createView();
1003
1004     T model;
1005     for (int i = 0; i < 30; i++)
1006         model.addItem("Item" + QString::number(i), "");
1007
1008     QDeclarativeContext *ctxt = canvas->rootContext();
1009     ctxt->setContextProperty("testModel", &model);
1010
1011     TestObject *testObject = new TestObject;
1012     ctxt->setContextProperty("testObject", testObject);
1013
1014     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1015     qApp->processEvents();
1016
1017     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1018     QTRY_VERIFY(listview != 0);
1019
1020     QSGItem *contentItem = listview->contentItem();
1021     QTRY_VERIFY(contentItem != 0);
1022
1023     model.clear();
1024
1025     QTRY_VERIFY(listview->count() == 0);
1026     QTRY_VERIFY(listview->currentItem() == 0);
1027     QTRY_VERIFY(listview->contentY() == 0);
1028     QVERIFY(listview->currentIndex() == -1);
1029
1030     // confirm sanity when adding an item to cleared list
1031     model.addItem("New", "1");
1032     QTRY_VERIFY(listview->count() == 1);
1033     QVERIFY(listview->currentItem() != 0);
1034     QVERIFY(listview->currentIndex() == 0);
1035
1036     delete canvas;
1037     delete testObject;
1038 }
1039
1040 template <class T>
1041 void tst_QSGListView::moved()
1042 {
1043     QFETCH(qreal, contentY);
1044     QFETCH(int, from);
1045     QFETCH(int, to);
1046     QFETCH(int, count);
1047     QFETCH(qreal, itemsOffsetAfterMove);
1048
1049     QSGText *name;
1050     QSGText *number;
1051     QSGView *canvas = createView();
1052     canvas->show();
1053
1054     T model;
1055     for (int i = 0; i < 30; i++)
1056         model.addItem("Item" + QString::number(i), "");
1057
1058     QDeclarativeContext *ctxt = canvas->rootContext();
1059     ctxt->setContextProperty("testModel", &model);
1060
1061     TestObject *testObject = new TestObject;
1062     ctxt->setContextProperty("testObject", testObject);
1063
1064     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1065     qApp->processEvents();
1066
1067     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1068     QTRY_VERIFY(listview != 0);
1069
1070     QSGItem *contentItem = listview->contentItem();
1071     QTRY_VERIFY(contentItem != 0);
1072
1073     QSGItem *currentItem = listview->currentItem();
1074     QTRY_VERIFY(currentItem != 0);
1075
1076     listview->setContentY(contentY);
1077     model.moveItems(from, to, count);
1078
1079     // wait for items to move
1080     QTest::qWait(100);
1081
1082     QList<QSGItem*> items = findItems<QSGItem>(contentItem, "wrapper");
1083     int firstVisibleIndex = -1;
1084     for (int i=0; i<items.count(); i++) {
1085         if (items[i]->y() >= contentY) {
1086             QDeclarativeExpression e(qmlContext(items[i]), items[i], "index");
1087             firstVisibleIndex = e.evaluate().toInt();
1088             break;
1089         }
1090     }
1091     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
1092
1093     // Confirm items positioned correctly and indexes correct
1094     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1095     for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
1096         if (i >= firstVisibleIndex + 16)    // index has moved out of view
1097             continue;
1098         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1099         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1100         QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
1101         name = findItem<QSGText>(contentItem, "textName", i);
1102         QVERIFY(name != 0);
1103         QTRY_COMPARE(name->text(), model.name(i));
1104         number = findItem<QSGText>(contentItem, "textNumber", i);
1105         QVERIFY(number != 0);
1106         QTRY_COMPARE(number->text(), model.number(i));
1107
1108         // current index should have been updated
1109         if (item == currentItem)
1110             QTRY_COMPARE(listview->currentIndex(), i);
1111     }
1112
1113     delete canvas;
1114     delete testObject;
1115 }
1116
1117 void tst_QSGListView::moved_data()
1118 {
1119     QTest::addColumn<qreal>("contentY");
1120     QTest::addColumn<int>("from");
1121     QTest::addColumn<int>("to");
1122     QTest::addColumn<int>("count");
1123     QTest::addColumn<qreal>("itemsOffsetAfterMove");
1124
1125     // model starts with 30 items, each 20px high, in area 320px high
1126     // 16 items should be visible at a time
1127     // itemsOffsetAfterMove should be > 0 whenever items above the visible pos have moved
1128
1129     QTest::newRow("move 1 forwards, within visible items")
1130             << 0.0
1131             << 1 << 4 << 1
1132             << 0.0;
1133
1134     QTest::newRow("move 1 forwards, from non-visible -> visible")
1135             << 80.0     // show 4-19
1136             << 1 << 18 << 1
1137             << 20.0;    // removed 1 item above the first visible, so item 0 should drop down by 1 to minimize movement
1138
1139     QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)")
1140             << 80.0     // show 4-19
1141             << 0 << 4 << 1
1142             << 20.0;    // first item has moved to below item4, everything drops down by size of 1 item
1143
1144     QTest::newRow("move 1 forwards, from visible -> non-visible")
1145             << 0.0
1146             << 1 << 16 << 1
1147             << 0.0;
1148
1149     QTest::newRow("move 1 forwards, from visible -> non-visible (move first item)")
1150             << 0.0
1151             << 0 << 16 << 1
1152             << 0.0;
1153
1154
1155     QTest::newRow("move 1 backwards, within visible items")
1156             << 0.0
1157             << 4 << 1 << 1
1158             << 0.0;
1159
1160     QTest::newRow("move 1 backwards, within visible items (to first index)")
1161             << 0.0
1162             << 4 << 0 << 1
1163             << 0.0;
1164
1165     QTest::newRow("move 1 backwards, from non-visible -> visible")
1166             << 0.0
1167             << 20 << 4 << 1
1168             << 0.0;
1169
1170     QTest::newRow("move 1 backwards, from non-visible -> visible (move last item)")
1171             << 0.0
1172             << 29 << 15 << 1
1173             << 0.0;
1174
1175     QTest::newRow("move 1 backwards, from visible -> non-visible")
1176             << 80.0     // show 4-19
1177             << 16 << 1 << 1
1178             << -20.0;   // to minimize movement, item 0 moves to -20, and other items do not move
1179
1180     QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)")
1181             << 80.0     // show 4-19
1182             << 16 << 0 << 1
1183             << -20.0;   // to minimize movement, item 16 (now at 0) moves to -20, and other items do not move
1184
1185
1186     QTest::newRow("move multiple forwards, within visible items")
1187             << 0.0
1188             << 0 << 5 << 3
1189             << 0.0;
1190
1191     QTest::newRow("move multiple forwards, before visible items")
1192             << 140.0     // show 7-22
1193             << 4 << 5 << 3      // 4,5,6 move to below 7
1194             << 20.0 * 3;      // 4,5,6 moved down
1195
1196     QTest::newRow("move multiple forwards, from non-visible -> visible")
1197             << 80.0     // show 4-19
1198             << 1 << 5 << 3
1199             << 20.0 * 3;    // moving 3 from above the content y should adjust y positions accordingly
1200
1201     QTest::newRow("move multiple forwards, from non-visible -> visible (move first item)")
1202             << 80.0     // show 4-19
1203             << 0 << 5 << 3
1204             << 20.0 * 3;        // moving 3 from above the content y should adjust y positions accordingly
1205
1206     QTest::newRow("move multiple forwards, from visible -> non-visible")
1207             << 0.0
1208             << 1 << 16 << 3
1209             << 0.0;
1210
1211     QTest::newRow("move multiple forwards, from visible -> non-visible (move first item)")
1212             << 0.0
1213             << 0 << 16 << 3
1214             << 0.0;
1215
1216
1217     QTest::newRow("move multiple backwards, within visible items")
1218             << 0.0
1219             << 4 << 1 << 3
1220             << 0.0;
1221
1222     QTest::newRow("move multiple backwards, from non-visible -> visible")
1223             << 0.0
1224             << 20 << 4 << 3
1225             << 0.0;
1226
1227     QTest::newRow("move multiple backwards, from non-visible -> visible (move last item)")
1228             << 0.0
1229             << 27 << 10 << 3
1230             << 0.0;
1231
1232     QTest::newRow("move multiple backwards, from visible -> non-visible")
1233             << 80.0     // show 4-19
1234             << 16 << 1 << 3
1235             << -20.0 * 3;   // to minimize movement, 0 moves by -60, and other items do not move
1236
1237     QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)")
1238             << 80.0     // show 4-19
1239             << 16 << 0 << 3
1240             << -20.0 * 3;   // to minimize movement, 16,17,18 move to above item 0, and other items do not move
1241 }
1242
1243
1244 struct ListChange {
1245     enum { Inserted, Removed, Moved, SetCurrent } type;
1246     int index;
1247     int count;
1248     int to;     // Move
1249
1250     static ListChange insert(int index, int count = 1) { ListChange c = { Inserted, index, count, -1 }; return c; }
1251     static ListChange remove(int index, int count = 1) { ListChange c = { Removed, index, count, -1 }; return c; }
1252     static ListChange move(int index, int to, int count) { ListChange c = { Moved, index, count, to }; return c; }
1253     static ListChange setCurrent(int index) { ListChange c = { SetCurrent, index, -1, -1 }; return c; }
1254 };
1255 Q_DECLARE_METATYPE(QList<ListChange>)
1256
1257 void tst_QSGListView::multipleChanges()
1258 {
1259     QFETCH(int, startCount);
1260     QFETCH(QList<ListChange>, changes);
1261     QFETCH(int, newCount);
1262     QFETCH(int, newCurrentIndex);
1263
1264     QSGView *canvas = createView();
1265     canvas->show();
1266
1267     TestModel model;
1268     for (int i = 0; i < startCount; i++)
1269         model.addItem("Item" + QString::number(i), "");
1270
1271     QDeclarativeContext *ctxt = canvas->rootContext();
1272     ctxt->setContextProperty("testModel", &model);
1273
1274     TestObject *testObject = new TestObject;
1275     ctxt->setContextProperty("testObject", testObject);
1276
1277     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1278     qApp->processEvents();
1279
1280     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1281     QTRY_VERIFY(listview != 0);
1282
1283     for (int i=0; i<changes.count(); i++) {
1284         switch (changes[i].type) {
1285             case ListChange::Inserted:
1286             {
1287                 QList<QPair<QString, QString> > items;
1288                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
1289                     items << qMakePair(QString("new item " + j), QString::number(j));
1290                 model.insertItems(changes[i].index, items);
1291                 break;
1292             }
1293             case ListChange::Removed:
1294                 model.removeItems(changes[i].index, changes[i].count);
1295                 break;
1296             case ListChange::Moved:
1297                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
1298                 break;
1299             case ListChange::SetCurrent:
1300                 listview->setCurrentIndex(changes[i].index);
1301                 break;
1302         }
1303     }
1304
1305     QTRY_COMPARE(listview->count(), newCount);
1306     QCOMPARE(listview->count(), model.count());
1307     QTRY_COMPARE(listview->currentIndex(), newCurrentIndex);
1308
1309     QSGText *name;
1310     QSGText *number;
1311     QSGItem *contentItem = listview->contentItem();
1312     QTRY_VERIFY(contentItem != 0);
1313     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1314     for (int i=0; i < model.count() && i < itemCount; ++i) {
1315         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1316         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1317         name = findItem<QSGText>(contentItem, "textName", i);
1318         QVERIFY(name != 0);
1319         QTRY_COMPARE(name->text(), model.name(i));
1320         number = findItem<QSGText>(contentItem, "textNumber", i);
1321         QVERIFY(number != 0);
1322         QTRY_COMPARE(number->text(), model.number(i));
1323     }
1324
1325     delete testObject;
1326     delete canvas;
1327 }
1328
1329 void tst_QSGListView::multipleChanges_data()
1330 {
1331     QTest::addColumn<int>("startCount");
1332     QTest::addColumn<QList<ListChange> >("changes");
1333     QTest::addColumn<int>("newCount");
1334     QTest::addColumn<int>("newCurrentIndex");
1335
1336     QList<ListChange> changes;
1337
1338     for (int i=1; i<30; i++)
1339         changes << ListChange::remove(0);
1340     QTest::newRow("remove all but 1, first->last") << 30 << changes << 1 << 0;
1341
1342     changes << ListChange::remove(0);
1343     QTest::newRow("remove all") << 30 << changes << 0 << -1;
1344
1345     changes.clear();
1346     changes << ListChange::setCurrent(29);
1347     for (int i=29; i>0; i--)
1348         changes << ListChange::remove(i);
1349     QTest::newRow("remove last (current) -> first") << 30 << changes << 1 << 0;
1350
1351     QTest::newRow("remove then insert at 0") << 10 << (QList<ListChange>()
1352             << ListChange::remove(0, 1)
1353             << ListChange::insert(0, 1)
1354             ) << 10 << 1;
1355
1356     QTest::newRow("remove then insert at non-zero index") << 10 << (QList<ListChange>()
1357             << ListChange::setCurrent(2)
1358             << ListChange::remove(2, 1)
1359             << ListChange::insert(2, 1)
1360             ) << 10 << 3;
1361
1362     QTest::newRow("remove current then insert below it") << 10 << (QList<ListChange>()
1363             << ListChange::setCurrent(1)
1364             << ListChange::remove(1, 3)
1365             << ListChange::insert(2, 2)
1366             ) << 9 << 1;
1367
1368     QTest::newRow("remove current index then move it down") << 10 << (QList<ListChange>()
1369             << ListChange::setCurrent(2)
1370             << ListChange::remove(1, 3)
1371             << ListChange::move(1, 5, 1)
1372             ) << 7 << 5;
1373
1374     QTest::newRow("remove current index then move it up") << 10 << (QList<ListChange>()
1375             << ListChange::setCurrent(5)
1376             << ListChange::remove(4, 3)
1377             << ListChange::move(4, 1, 1)
1378             ) << 7 << 1;
1379
1380
1381     QTest::newRow("insert multiple times") << 0 << (QList<ListChange>()
1382             << ListChange::insert(0, 2)
1383             << ListChange::insert(0, 4)
1384             << ListChange::insert(0, 6)
1385             ) << 12 << 10;
1386
1387     QTest::newRow("insert multiple times with current index changes") << 0 << (QList<ListChange>()
1388             << ListChange::insert(0, 2)
1389             << ListChange::insert(0, 4)
1390             << ListChange::insert(0, 6)
1391             << ListChange::setCurrent(3)
1392             << ListChange::insert(3, 2)
1393             ) << 14 << 5;
1394
1395     QTest::newRow("insert and remove all") << 0 << (QList<ListChange>()
1396             << ListChange::insert(0, 30)
1397             << ListChange::remove(0, 30)
1398             ) << 0 << -1;
1399
1400     QTest::newRow("insert and remove current") << 30 << (QList<ListChange>()
1401             << ListChange::insert(1)
1402             << ListChange::setCurrent(1)
1403             << ListChange::remove(1)
1404             ) << 30 << 1;
1405
1406     QTest::newRow("insert before 0, then remove cross section of new and old items") << 10 << (QList<ListChange>()
1407             << ListChange::insert(0, 10)
1408             << ListChange::remove(5, 10)
1409             ) << 10 << 5;
1410
1411     QTest::newRow("insert multiple, then move new items to end") << 10 << (QList<ListChange>()
1412             << ListChange::insert(0, 3)
1413             << ListChange::move(0, 10, 3)
1414             ) << 13 << 0;
1415
1416     QTest::newRow("insert multiple, then move new and some old items to end") << 10 << (QList<ListChange>()
1417             << ListChange::insert(0, 3)
1418             << ListChange::move(0, 8, 5)
1419             ) << 13 << 11;
1420
1421     QTest::newRow("insert multiple at end, then move new and some old items to start") << 10 << (QList<ListChange>()
1422             << ListChange::setCurrent(9)
1423             << ListChange::insert(10, 3)
1424             << ListChange::move(8, 0, 5)
1425             ) << 13 << 1;
1426
1427
1428     QTest::newRow("move back and forth to same index") << 10 << (QList<ListChange>()
1429             << ListChange::setCurrent(1)
1430             << ListChange::move(1, 2, 2)
1431             << ListChange::move(2, 1, 2)
1432             ) << 10 << 1;
1433
1434     QTest::newRow("move forwards then back") << 10 << (QList<ListChange>()
1435             << ListChange::setCurrent(2)
1436             << ListChange::move(1, 2, 3)
1437             << ListChange::move(3, 0, 5)
1438             ) << 10 << 0;
1439
1440     QTest::newRow("move current, then remove it") << 10 << (QList<ListChange>()
1441             << ListChange::setCurrent(5)
1442             << ListChange::move(5, 0, 1)
1443             << ListChange::remove(0)
1444             ) << 9 << 0;
1445
1446     QTest::newRow("move current, then insert before it") << 10 << (QList<ListChange>()
1447             << ListChange::setCurrent(5)
1448             << ListChange::move(5, 0, 1)
1449             << ListChange::insert(0)
1450             ) << 11 << 1;
1451
1452     QTest::newRow("move multiple, then remove them") << 10 << (QList<ListChange>()
1453             << ListChange::setCurrent(1)
1454             << ListChange::move(5, 1, 3)
1455             << ListChange::remove(1, 3)
1456             ) << 7 << 1;
1457
1458     QTest::newRow("move multiple, then insert before them") << 10 << (QList<ListChange>()
1459             << ListChange::setCurrent(5)
1460             << ListChange::move(5, 1, 3)
1461             << ListChange::insert(1, 5)
1462             ) << 15 << 6;
1463
1464     QTest::newRow("move multiple, then insert after them") << 10 << (QList<ListChange>()
1465             << ListChange::setCurrent(3)
1466             << ListChange::move(0, 1, 2)
1467             << ListChange::insert(3, 5)
1468             ) << 15 << 8;
1469
1470
1471     QTest::newRow("clear current") << 0 << (QList<ListChange>()
1472             << ListChange::insert(0, 5)
1473             << ListChange::setCurrent(-1)
1474             << ListChange::remove(0, 5)
1475             << ListChange::insert(0, 5)
1476             ) << 5 << -1;
1477 }
1478
1479 void tst_QSGListView::swapWithFirstItem()
1480 {
1481     QSGView *canvas = createView();
1482     canvas->show();
1483
1484     TestModel model;
1485     for (int i = 0; i < 30; i++)
1486         model.addItem("Item" + QString::number(i), "");
1487
1488     QDeclarativeContext *ctxt = canvas->rootContext();
1489     ctxt->setContextProperty("testModel", &model);
1490
1491     TestObject *testObject = new TestObject;
1492     ctxt->setContextProperty("testObject", testObject);
1493
1494     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1495     qApp->processEvents();
1496
1497     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1498     QTRY_VERIFY(listview != 0);
1499
1500     // ensure content position is stable
1501     listview->setContentY(0);
1502     model.moveItem(1, 0);
1503     QTRY_VERIFY(listview->contentY() == 0);
1504
1505     delete testObject;
1506     delete canvas;
1507 }
1508
1509 void tst_QSGListView::enforceRange()
1510 {
1511     QSGView *canvas = createView();
1512
1513     TestModel model;
1514     for (int i = 0; i < 30; i++)
1515         model.addItem("Item" + QString::number(i), "");
1516
1517     QDeclarativeContext *ctxt = canvas->rootContext();
1518     ctxt->setContextProperty("testModel", &model);
1519
1520     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-enforcerange.qml")));
1521     qApp->processEvents();
1522
1523     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1524     QTRY_VERIFY(listview != 0);
1525
1526     QTRY_COMPARE(listview->preferredHighlightBegin(), 100.0);
1527     QTRY_COMPARE(listview->preferredHighlightEnd(), 100.0);
1528     QTRY_COMPARE(listview->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
1529
1530     QSGItem *contentItem = listview->contentItem();
1531     QTRY_VERIFY(contentItem != 0);
1532
1533     // view should be positioned at the top of the range.
1534     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
1535     QTRY_VERIFY(item);
1536     QTRY_COMPARE(listview->contentY(), -100.0);
1537
1538     QSGText *name = findItem<QSGText>(contentItem, "textName", 0);
1539     QTRY_VERIFY(name != 0);
1540     QTRY_COMPARE(name->text(), model.name(0));
1541     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 0);
1542     QTRY_VERIFY(number != 0);
1543     QTRY_COMPARE(number->text(), model.number(0));
1544
1545     // Check currentIndex is updated when contentItem moves
1546     listview->setContentY(20);
1547
1548     QTRY_COMPARE(listview->currentIndex(), 6);
1549
1550     // change model
1551     TestModel model2;
1552     for (int i = 0; i < 5; i++)
1553         model2.addItem("Item" + QString::number(i), "");
1554
1555     ctxt->setContextProperty("testModel", &model2);
1556     QCOMPARE(listview->count(), 5);
1557
1558     delete canvas;
1559 }
1560
1561 void tst_QSGListView::enforceRange_withoutHighlight()
1562 {
1563     // QTBUG-20287
1564     // If no highlight is set but StrictlyEnforceRange is used, the content should still move
1565     // to the correct position (i.e. to the next/previous item, not next/previous section)
1566     // when moving up/down via incrementCurrentIndex() and decrementCurrentIndex()
1567
1568     QSGView *canvas = createView();
1569     canvas->show();
1570     QTest::qWait(200);
1571
1572     TestModel model;
1573     model.addItem("Item 0", "a");
1574     model.addItem("Item 1", "b");
1575     model.addItem("Item 2", "b");
1576     model.addItem("Item 3", "c");
1577
1578     QDeclarativeContext *ctxt = canvas->rootContext();
1579     ctxt->setContextProperty("testModel", &model);
1580
1581     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-enforcerange-nohighlight.qml")));
1582     qApp->processEvents();
1583
1584     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1585     QTRY_VERIFY(listview != 0);
1586
1587     qreal expectedPos = -100.0;
1588
1589     expectedPos += 10.0;    // scroll past 1st section's delegate (10px height)
1590     QTRY_COMPARE(listview->contentY(), expectedPos);
1591
1592     expectedPos += 20 + 10;     // scroll past 1st section and section delegate of 2nd section
1593     QTest::keyClick(canvas, Qt::Key_Down);
1594
1595     QTRY_COMPARE(listview->contentY(), expectedPos);
1596
1597     expectedPos += 20;     // scroll past 1st item of 2nd section
1598     QTest::keyClick(canvas, Qt::Key_Down);
1599     QTRY_COMPARE(listview->contentY(), expectedPos);
1600
1601     expectedPos += 20 + 10;     // scroll past 2nd item of 2nd section and section delegate of 3rd section
1602     QTest::keyClick(canvas, Qt::Key_Down);
1603     QTRY_COMPARE(listview->contentY(), expectedPos);
1604
1605     delete canvas;
1606 }
1607
1608 void tst_QSGListView::spacing()
1609 {
1610     QSGView *canvas = createView();
1611     canvas->show();
1612
1613     TestModel model;
1614     for (int i = 0; i < 30; i++)
1615         model.addItem("Item" + QString::number(i), "");
1616
1617     QDeclarativeContext *ctxt = canvas->rootContext();
1618     ctxt->setContextProperty("testModel", &model);
1619
1620     TestObject *testObject = new TestObject;
1621     ctxt->setContextProperty("testObject", testObject);
1622
1623     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
1624     qApp->processEvents();
1625
1626     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1627     QTRY_VERIFY(listview != 0);
1628
1629     QSGItem *contentItem = listview->contentItem();
1630     QTRY_VERIFY(contentItem != 0);
1631
1632     // Confirm items positioned correctly
1633     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1634     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1635         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1636         if (!item) qWarning() << "Item" << i << "not found";
1637         QTRY_VERIFY(item);
1638         QTRY_VERIFY(item->y() == i*20);
1639     }
1640
1641     listview->setSpacing(10);
1642     QTRY_VERIFY(listview->spacing() == 10);
1643
1644     // Confirm items positioned correctly
1645     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1646     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1647         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1648         if (!item) qWarning() << "Item" << i << "not found";
1649         QTRY_VERIFY(item);
1650         QTRY_VERIFY(item->y() == i*30);
1651     }
1652
1653     listview->setSpacing(0);
1654
1655     // Confirm items positioned correctly
1656     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1657     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1658         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1659         if (!item) qWarning() << "Item" << i << "not found";
1660         QTRY_VERIFY(item);
1661         QTRY_COMPARE(item->y(), i*20.0);
1662     }
1663
1664     delete canvas;
1665     delete testObject;
1666 }
1667
1668 void tst_QSGListView::sections()
1669 {
1670     QSGView *canvas = createView();
1671     canvas->show();
1672
1673     TestModel model;
1674     for (int i = 0; i < 30; i++)
1675         model.addItem("Item" + QString::number(i), QString::number(i/5));
1676
1677     QDeclarativeContext *ctxt = canvas->rootContext();
1678     ctxt->setContextProperty("testModel", &model);
1679
1680     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections.qml")));
1681     qApp->processEvents();
1682
1683     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1684     QTRY_VERIFY(listview != 0);
1685
1686     QSGItem *contentItem = listview->contentItem();
1687     QTRY_VERIFY(contentItem != 0);
1688
1689     // Confirm items positioned correctly
1690     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1691     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1692         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1693         QTRY_VERIFY(item);
1694         QTRY_COMPARE(item->y(), qreal(i*20 + ((i+4)/5) * 20));
1695         QSGText *next = findItem<QSGText>(item, "nextSection");
1696         QCOMPARE(next->text().toInt(), (i+1)/5);
1697     }
1698
1699     QSignalSpy currentSectionChangedSpy(listview, SIGNAL(currentSectionChanged()));
1700
1701     // Remove section boundary
1702     model.removeItem(5);
1703     QTRY_COMPARE(listview->count(), model.count());
1704
1705     // New section header created
1706     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 5);
1707     QTRY_VERIFY(item);
1708     QTRY_COMPARE(item->height(), 40.0);
1709
1710     model.insertItem(3, "New Item", "0");
1711     QTRY_COMPARE(listview->count(), model.count());
1712
1713     // Section header moved
1714     item = findItem<QSGItem>(contentItem, "wrapper", 5);
1715     QTRY_VERIFY(item);
1716     QTRY_COMPARE(item->height(), 20.0);
1717
1718     item = findItem<QSGItem>(contentItem, "wrapper", 6);
1719     QTRY_VERIFY(item);
1720     QTRY_COMPARE(item->height(), 40.0);
1721
1722     // insert item which will become a section header
1723     model.insertItem(6, "Replace header", "1");
1724     QTRY_COMPARE(listview->count(), model.count());
1725
1726     item = findItem<QSGItem>(contentItem, "wrapper", 6);
1727     QTRY_VERIFY(item);
1728     QTRY_COMPARE(item->height(), 40.0);
1729
1730     item = findItem<QSGItem>(contentItem, "wrapper", 7);
1731     QTRY_VERIFY(item);
1732     QTRY_COMPARE(item->height(), 20.0);
1733
1734     QTRY_COMPARE(listview->currentSection(), QString("0"));
1735
1736     listview->setContentY(140);
1737     QTRY_COMPARE(listview->currentSection(), QString("1"));
1738
1739     QTRY_COMPARE(currentSectionChangedSpy.count(), 1);
1740
1741     listview->setContentY(20);
1742     QTRY_COMPARE(listview->currentSection(), QString("0"));
1743
1744     QTRY_COMPARE(currentSectionChangedSpy.count(), 2);
1745
1746     item = findItem<QSGItem>(contentItem, "wrapper", 1);
1747     QTRY_VERIFY(item);
1748     QTRY_COMPARE(item->height(), 20.0);
1749
1750     // check that headers change when item changes
1751     listview->setContentY(0);
1752     model.modifyItem(0, "changed", "2");
1753     QTest::qWait(300);
1754
1755     item = findItem<QSGItem>(contentItem, "wrapper", 1);
1756     QTRY_VERIFY(item);
1757     QTRY_COMPARE(item->height(), 40.0);
1758
1759     delete canvas;
1760 }
1761
1762 void tst_QSGListView::sectionsDelegate()
1763 {
1764     QSGView *canvas = createView();
1765     canvas->show();
1766
1767     TestModel model;
1768     for (int i = 0; i < 30; i++)
1769         model.addItem("Item" + QString::number(i), QString::number(i/5));
1770
1771     QDeclarativeContext *ctxt = canvas->rootContext();
1772     ctxt->setContextProperty("testModel", &model);
1773
1774     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections_delegate.qml")));
1775     qApp->processEvents();
1776
1777     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1778     QTRY_VERIFY(listview != 0);
1779
1780     QSGItem *contentItem = listview->contentItem();
1781     QTRY_VERIFY(contentItem != 0);
1782
1783     // Confirm items positioned correctly
1784     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1785     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1786         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1787         QTRY_VERIFY(item);
1788         QTRY_COMPARE(item->y(), qreal(i*20 + ((i+5)/5) * 20));
1789         QSGText *next = findItem<QSGText>(item, "nextSection");
1790         QCOMPARE(next->text().toInt(), (i+1)/5);
1791     }
1792
1793     for (int i = 0; i < 3; ++i) {
1794         QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1795         QVERIFY(item);
1796         QTRY_COMPARE(item->y(), qreal(i*20*6));
1797     }
1798
1799     model.modifyItem(0, "One", "aaa");
1800     model.modifyItem(1, "Two", "aaa");
1801     model.modifyItem(2, "Three", "aaa");
1802     model.modifyItem(3, "Four", "aaa");
1803     model.modifyItem(4, "Five", "aaa");
1804     QTest::qWait(300);
1805
1806     for (int i = 0; i < 3; ++i) {
1807         QSGItem *item = findItem<QSGItem>(contentItem,
1808                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1809         QVERIFY(item);
1810         QTRY_COMPARE(item->y(), qreal(i*20*6));
1811     }
1812
1813     // remove section boundary
1814     model.removeItem(5);
1815     QTRY_COMPARE(listview->count(), model.count());
1816     for (int i = 0; i < 3; ++i) {
1817         QSGItem *item = findItem<QSGItem>(contentItem,
1818                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1819         QVERIFY(item);
1820     }
1821
1822     // QTBUG-17606
1823     QList<QSGItem*> items = findItems<QSGItem>(contentItem, "sect_1");
1824     QCOMPARE(items.count(), 1);
1825
1826     // QTBUG-17759
1827     model.modifyItem(0, "One", "aaa");
1828     model.modifyItem(1, "One", "aaa");
1829     model.modifyItem(2, "One", "aaa");
1830     model.modifyItem(3, "Four", "aaa");
1831     model.modifyItem(4, "Four", "aaa");
1832     model.modifyItem(5, "Four", "aaa");
1833     model.modifyItem(6, "Five", "aaa");
1834     model.modifyItem(7, "Five", "aaa");
1835     model.modifyItem(8, "Five", "aaa");
1836     model.modifyItem(9, "Two", "aaa");
1837     model.modifyItem(10, "Two", "aaa");
1838     model.modifyItem(11, "Two", "aaa");
1839     QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_aaa").count(), 1);
1840     canvas->rootObject()->setProperty("sectionProperty", "name");
1841     // ensure view has settled.
1842     QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_Four").count(), 1);
1843     for (int i = 0; i < 4; ++i) {
1844         QSGItem *item = findItem<QSGItem>(contentItem,
1845                 "sect_" + model.name(i*3));
1846         QVERIFY(item);
1847         QTRY_COMPARE(item->y(), qreal(i*20*4));
1848     }
1849
1850     // QTBUG-17769
1851     model.removeItems(10, 20);
1852     // ensure view has settled.
1853     QTRY_COMPARE(findItems<QSGItem>(contentItem, "wrapper").count(), 10);
1854     // Drag view up beyond bounds
1855     QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20,20));
1856     {
1857         QMouseEvent mv(QEvent::MouseMove, QPoint(20,0), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1858         QApplication::sendEvent(canvas, &mv);
1859     }
1860     {
1861         QMouseEvent mv(QEvent::MouseMove, QPoint(20,-50), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1862         QApplication::sendEvent(canvas, &mv);
1863     }
1864     {
1865         QMouseEvent mv(QEvent::MouseMove, QPoint(20,-200), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1866         QApplication::sendEvent(canvas, &mv);
1867     }
1868     QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,-200));
1869     // view should settle back at 0
1870     QTRY_COMPARE(listview->contentY(), 0.0);
1871
1872     delete canvas;
1873 }
1874
1875 void tst_QSGListView::sectionsPositioning()
1876 {
1877     QSGView *canvas = createView();
1878     canvas->show();
1879
1880     TestModel model;
1881     for (int i = 0; i < 30; i++)
1882         model.addItem("Item" + QString::number(i), QString::number(i/5));
1883
1884     QDeclarativeContext *ctxt = canvas->rootContext();
1885     ctxt->setContextProperty("testModel", &model);
1886
1887     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listview-sections_delegate.qml")));
1888     qApp->processEvents();
1889     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart | QSGViewSection::NextLabelAtEnd)));
1890
1891     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1892     QTRY_VERIFY(listview != 0);
1893
1894     QSGItem *contentItem = listview->contentItem();
1895     QTRY_VERIFY(contentItem != 0);
1896
1897     for (int i = 0; i < 3; ++i) {
1898         QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1899         QVERIFY(item);
1900         QTRY_COMPARE(item->y(), qreal(i*20*6));
1901     }
1902
1903     QSGItem *topItem = findVisibleChild(contentItem, "sect_0"); // section header
1904     QVERIFY(topItem);
1905     QCOMPARE(topItem->y(), 0.);
1906
1907     QSGItem *bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
1908     QVERIFY(bottomItem);
1909     QCOMPARE(bottomItem->y(), 300.);
1910
1911     // move down a little and check that section header is at top
1912     listview->setContentY(10);
1913     QCOMPARE(topItem->y(), 0.);
1914
1915     // push the top header up
1916     listview->setContentY(110);
1917     topItem = findVisibleChild(contentItem, "sect_0"); // section header
1918     QVERIFY(topItem);
1919     QCOMPARE(topItem->y(), 100.);
1920
1921     QSGItem *item = findVisibleChild(contentItem, "sect_1");
1922     QVERIFY(item);
1923     QCOMPARE(item->y(), 120.);
1924
1925     bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
1926     QVERIFY(bottomItem);
1927     QCOMPARE(bottomItem->y(), 410.);
1928
1929     // Move past section 0
1930     listview->setContentY(120);
1931     topItem = findVisibleChild(contentItem, "sect_0"); // section header
1932     QVERIFY(!topItem);
1933
1934     // Push section footer down
1935     listview->setContentY(70);
1936     bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
1937     QVERIFY(bottomItem);
1938     QCOMPARE(bottomItem->y(), 380.);
1939
1940     // Change current section
1941     listview->setContentY(10);
1942     model.modifyItem(0, "One", "aaa");
1943     model.modifyItem(1, "Two", "aaa");
1944     model.modifyItem(2, "Three", "aaa");
1945     model.modifyItem(3, "Four", "aaa");
1946     model.modifyItem(4, "Five", "aaa");
1947     QTest::qWait(300);
1948
1949     QTRY_COMPARE(listview->currentSection(), QString("aaa"));
1950
1951     for (int i = 0; i < 3; ++i) {
1952         QSGItem *item = findItem<QSGItem>(contentItem,
1953                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1954         QVERIFY(item);
1955         QTRY_COMPARE(item->y(), qreal(i*20*6));
1956     }
1957
1958     topItem = findVisibleChild(contentItem, "sect_aaa"); // section header
1959     QVERIFY(topItem);
1960     QCOMPARE(topItem->y(), 10.);
1961
1962     // remove section boundary
1963     listview->setContentY(120);
1964     model.removeItem(5);
1965     QTRY_COMPARE(listview->count(), model.count());
1966     for (int i = 0; i < 3; ++i) {
1967         QSGItem *item = findItem<QSGItem>(contentItem,
1968                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1969         QVERIFY(item);
1970         QTRY_COMPARE(item->y(), qreal(i*20*6));
1971     }
1972
1973     QTRY_VERIFY(topItem = findVisibleChild(contentItem, "sect_aaa")); // section header
1974     QCOMPARE(topItem->y(), 120.);
1975     QVERIFY(topItem = findVisibleChild(contentItem, "sect_1"));
1976     QTRY_COMPARE(topItem->y(), 140.);
1977
1978     // Change the next section
1979     listview->setContentY(0);
1980     bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
1981     QVERIFY(bottomItem);
1982     QTRY_COMPARE(bottomItem->y(), 320.);
1983
1984     model.modifyItem(14, "New", "new");
1985
1986     QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
1987     QTRY_COMPARE(bottomItem->y(), 320.);
1988
1989     // Turn sticky footer off
1990     listview->setContentY(50);
1991     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels | QSGViewSection::CurrentLabelAtStart)));
1992     item = findVisibleChild(contentItem, "sect_new"); // inline label restored
1993     QCOMPARE(item->y(), 360.);
1994
1995     // Turn sticky header off
1996     listview->setContentY(50);
1997     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QSGViewSection::InlineLabels)));
1998     item = findVisibleChild(contentItem, "sect_aaa"); // inline label restored
1999     QCOMPARE(item->y(), 20.);
2000
2001     delete canvas;
2002 }
2003
2004 void tst_QSGListView::currentIndex_delayedItemCreation()
2005 {
2006     QFETCH(bool, setCurrentToZero);
2007
2008     QSGView *canvas = createView();
2009
2010     TestModel model;
2011
2012     // test currentIndexChanged() is emitted even if currentIndex = 0 on start up
2013     // (since the currentItem will have changed and that shares the same index)
2014     canvas->rootContext()->setContextProperty("setCurrentToZero", setCurrentToZero);
2015
2016     canvas->setSource(QUrl::fromLocalFile(TESTDATA("fillModelOnComponentCompleted.qml")));
2017     qApp->processEvents();
2018
2019     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2020     QTRY_VERIFY(listview != 0);
2021
2022     QSGItem *contentItem = listview->contentItem();
2023     QTRY_VERIFY(contentItem != 0);
2024
2025     QSignalSpy spy(listview, SIGNAL(currentIndexChanged()));
2026     QCOMPARE(listview->currentIndex(), 0);
2027     QTRY_COMPARE(spy.count(), 1);
2028
2029     delete canvas;
2030 }
2031
2032 void tst_QSGListView::currentIndex_delayedItemCreation_data()
2033 {
2034     QTest::addColumn<bool>("setCurrentToZero");
2035
2036     QTest::newRow("set to 0") << true;
2037     QTest::newRow("don't set to 0") << false;
2038 }
2039
2040 void tst_QSGListView::currentIndex()
2041 {
2042     TestModel model;
2043     for (int i = 0; i < 30; i++)
2044         model.addItem("Item" + QString::number(i), QString::number(i));
2045
2046     QSGView *canvas = new QSGView(0);
2047     canvas->setGeometry(0,0,240,320);
2048
2049     QDeclarativeContext *ctxt = canvas->rootContext();
2050     ctxt->setContextProperty("testModel", &model);
2051     ctxt->setContextProperty("testWrap", QVariant(false));
2052
2053     QString filename(TESTDATA("listview-initCurrent.qml"));
2054     canvas->setSource(QUrl::fromLocalFile(filename));
2055
2056     qApp->processEvents();
2057
2058     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2059     QTRY_VERIFY(listview != 0);
2060
2061     QSGItem *contentItem = listview->contentItem();
2062     QTRY_VERIFY(contentItem != 0);
2063
2064     // current item should be 20th item at startup
2065     // and current item should be in view
2066     QCOMPARE(listview->currentIndex(), 20);
2067     QCOMPARE(listview->contentY(), 100.0);
2068     QCOMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 20));
2069     QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
2070
2071     // no wrap
2072     listview->setCurrentIndex(0);
2073     QCOMPARE(listview->currentIndex(), 0);
2074     // confirm that the velocity is updated
2075     QTRY_VERIFY(listview->verticalVelocity() != 0.0);
2076
2077     listview->incrementCurrentIndex();
2078     QCOMPARE(listview->currentIndex(), 1);
2079     listview->decrementCurrentIndex();
2080     QCOMPARE(listview->currentIndex(), 0);
2081
2082     listview->decrementCurrentIndex();
2083     QCOMPARE(listview->currentIndex(), 0);
2084
2085     // with wrap
2086     ctxt->setContextProperty("testWrap", QVariant(true));
2087     QVERIFY(listview->isWrapEnabled());
2088
2089     listview->decrementCurrentIndex();
2090     QCOMPARE(listview->currentIndex(), model.count()-1);
2091
2092     QTRY_COMPARE(listview->contentY(), 280.0);
2093
2094     listview->incrementCurrentIndex();
2095     QCOMPARE(listview->currentIndex(), 0);
2096
2097     QTRY_COMPARE(listview->contentY(), 0.0);
2098
2099
2100     // footer should become visible if it is out of view, and then current index is set to count-1
2101     canvas->rootObject()->setProperty("showFooter", true);
2102     QTRY_VERIFY(listview->footerItem());
2103     listview->setCurrentIndex(model.count()-2);
2104     QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
2105     listview->setCurrentIndex(model.count()-1);
2106     QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
2107     canvas->rootObject()->setProperty("showFooter", false);
2108
2109     // header should become visible if it is out of view, and then current index is set to 0
2110     canvas->rootObject()->setProperty("showHeader", true);
2111     QTRY_VERIFY(listview->headerItem());
2112     listview->setCurrentIndex(1);
2113     QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
2114     listview->setCurrentIndex(0);
2115     QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
2116     canvas->rootObject()->setProperty("showHeader", false);
2117
2118
2119     // Test keys
2120     canvas->show();
2121     canvas->requestActivateWindow();
2122     QTest::qWaitForWindowShown(canvas);
2123     QTRY_VERIFY(qGuiApp->focusWindow() == canvas);
2124
2125     listview->setCurrentIndex(0);
2126
2127     QTest::keyClick(canvas, Qt::Key_Down);
2128     QCOMPARE(listview->currentIndex(), 1);
2129
2130     QTest::keyClick(canvas, Qt::Key_Up);
2131     QCOMPARE(listview->currentIndex(), 0);
2132
2133     // hold down Key_Down
2134     for (int i=0; i<model.count()-1; i++) {
2135         QTest::simulateEvent(canvas, true, Qt::Key_Down, Qt::NoModifier, "", true);
2136         QTRY_COMPARE(listview->currentIndex(), i+1);
2137     }
2138     QTest::keyRelease(canvas, Qt::Key_Down);
2139     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
2140     QTRY_COMPARE(listview->contentY(), 280.0);
2141
2142     // hold down Key_Up
2143     for (int i=model.count()-1; i > 0; i--) {
2144         QTest::simulateEvent(canvas, true, Qt::Key_Up, Qt::NoModifier, "", true);
2145         QTRY_COMPARE(listview->currentIndex(), i-1);
2146     }
2147     QTest::keyRelease(canvas, Qt::Key_Up);
2148     QTRY_COMPARE(listview->currentIndex(), 0);
2149     QTRY_COMPARE(listview->contentY(), 0.0);
2150
2151
2152     // turn off auto highlight
2153     listview->setHighlightFollowsCurrentItem(false);
2154     QVERIFY(listview->highlightFollowsCurrentItem() == false);
2155
2156     QVERIFY(listview->highlightItem());
2157     qreal hlPos = listview->highlightItem()->y();
2158
2159     listview->setCurrentIndex(4);
2160     QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
2161
2162     // insert item before currentIndex
2163     listview->setCurrentIndex(28);
2164     model.insertItem(0, "Foo", "1111");
2165     QTRY_COMPARE(canvas->rootObject()->property("current").toInt(), 29);
2166
2167     // check removing highlight by setting currentIndex to -1;
2168     listview->setCurrentIndex(-1);
2169
2170     QCOMPARE(listview->currentIndex(), -1);
2171     QVERIFY(!listview->highlightItem());
2172     QVERIFY(!listview->currentItem());
2173
2174     delete canvas;
2175 }
2176
2177 void tst_QSGListView::noCurrentIndex()
2178 {
2179     TestModel model;
2180     for (int i = 0; i < 30; i++)
2181         model.addItem("Item" + QString::number(i), QString::number(i));
2182
2183     QSGView *canvas = new QSGView(0);
2184     canvas->setGeometry(0,0,240,320);
2185
2186     QDeclarativeContext *ctxt = canvas->rootContext();
2187     ctxt->setContextProperty("testModel", &model);
2188
2189     QString filename(TESTDATA("listview-noCurrent.qml"));
2190     canvas->setSource(QUrl::fromLocalFile(filename));
2191
2192     qApp->processEvents();
2193
2194     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2195     QTRY_VERIFY(listview != 0);
2196
2197     QSGItem *contentItem = listview->contentItem();
2198     QTRY_VERIFY(contentItem != 0);
2199
2200     // current index should be -1 at startup
2201     // and we should not have a currentItem or highlightItem
2202     QCOMPARE(listview->currentIndex(), -1);
2203     QCOMPARE(listview->contentY(), 0.0);
2204     QVERIFY(!listview->highlightItem());
2205     QVERIFY(!listview->currentItem());
2206
2207     listview->setCurrentIndex(2);
2208     QCOMPARE(listview->currentIndex(), 2);
2209     QVERIFY(listview->highlightItem());
2210     QVERIFY(listview->currentItem());
2211
2212     delete canvas;
2213 }
2214
2215 void tst_QSGListView::itemList()
2216 {
2217     QSGView *canvas = createView();
2218
2219     canvas->setSource(QUrl::fromLocalFile(TESTDATA("itemlist.qml")));
2220     qApp->processEvents();
2221
2222     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
2223     QTRY_VERIFY(listview != 0);
2224
2225     QSGItem *contentItem = listview->contentItem();
2226     QTRY_VERIFY(contentItem != 0);
2227
2228     QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
2229     QTRY_VERIFY(model != 0);
2230
2231     QTRY_VERIFY(model->count() == 3);
2232     QTRY_COMPARE(listview->currentIndex(), 0);
2233
2234     QSGItem *item = findItem<QSGItem>(contentItem, "item1");
2235     QTRY_VERIFY(item);
2236     QTRY_COMPARE(item->x(), 0.0);
2237     QCOMPARE(item->height(), listview->height());
2238
2239     QSGText *text = findItem<QSGText>(contentItem, "text1");
2240     QTRY_VERIFY(text);
2241     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
2242
2243     listview->setCurrentIndex(2);
2244
2245     item = findItem<QSGItem>(contentItem, "item3");
2246     QTRY_VERIFY(item);
2247     QTRY_COMPARE(item->x(), 480.0);
2248
2249     text = findItem<QSGText>(contentItem, "text3");
2250     QTRY_VERIFY(text);
2251     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
2252
2253     delete canvas;
2254 }
2255
2256 void tst_QSGListView::cacheBuffer()
2257 {
2258     QSGView *canvas = createView();
2259
2260     TestModel model;
2261     for (int i = 0; i < 30; i++)
2262         model.addItem("Item" + QString::number(i), "");
2263
2264     QDeclarativeContext *ctxt = canvas->rootContext();
2265     ctxt->setContextProperty("testModel", &model);
2266
2267     TestObject *testObject = new TestObject;
2268     ctxt->setContextProperty("testObject", testObject);
2269
2270     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2271     qApp->processEvents();
2272
2273     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2274     QTRY_VERIFY(listview != 0);
2275
2276     QSGItem *contentItem = listview->contentItem();
2277     QTRY_VERIFY(contentItem != 0);
2278     QTRY_VERIFY(listview->delegate() != 0);
2279     QTRY_VERIFY(listview->model() != 0);
2280     QTRY_VERIFY(listview->highlight() != 0);
2281
2282     // Confirm items positioned correctly
2283     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2284     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2285         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2286         if (!item) qWarning() << "Item" << i << "not found";
2287         QTRY_VERIFY(item);
2288         QTRY_VERIFY(item->y() == i*20);
2289     }
2290
2291     testObject->setCacheBuffer(400);
2292     QTRY_VERIFY(listview->cacheBuffer() == 400);
2293
2294     int newItemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2295     QTRY_VERIFY(newItemCount > itemCount);
2296
2297     // Confirm items positioned correctly
2298     for (int i = 0; i < model.count() && i < newItemCount; ++i) {
2299         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2300         if (!item) qWarning() << "Item" << i << "not found";
2301         QTRY_VERIFY(item);
2302         QTRY_VERIFY(item->y() == i*20);
2303     }
2304
2305     delete canvas;
2306     delete testObject;
2307 }
2308
2309 void tst_QSGListView::positionViewAtIndex()
2310 {
2311     QSGView *canvas = createView();
2312
2313     TestModel model;
2314     for (int i = 0; i < 40; i++)
2315         model.addItem("Item" + QString::number(i), "");
2316
2317     QDeclarativeContext *ctxt = canvas->rootContext();
2318     ctxt->setContextProperty("testModel", &model);
2319
2320     TestObject *testObject = new TestObject;
2321     ctxt->setContextProperty("testObject", testObject);
2322
2323     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2324     qApp->processEvents();
2325
2326     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2327     QTRY_VERIFY(listview != 0);
2328
2329     QSGItem *contentItem = listview->contentItem();
2330     QTRY_VERIFY(contentItem != 0);
2331
2332     // Confirm items positioned correctly
2333     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2334     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2335         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2336         if (!item) qWarning() << "Item" << i << "not found";
2337         QTRY_VERIFY(item);
2338         QTRY_COMPARE(item->y(), i*20.);
2339     }
2340
2341     // Position on a currently visible item
2342     listview->positionViewAtIndex(3, QSGListView::Beginning);
2343     QTRY_COMPARE(listview->contentY(), 60.);
2344
2345     // Confirm items positioned correctly
2346     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2347     for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) {
2348         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2349         if (!item) qWarning() << "Item" << i << "not found";
2350         QTRY_VERIFY(item);
2351         QTRY_COMPARE(item->y(), i*20.);
2352     }
2353
2354     // Position on an item beyond the visible items
2355     listview->positionViewAtIndex(22, QSGListView::Beginning);
2356     QTRY_COMPARE(listview->contentY(), 440.);
2357
2358     // Confirm items positioned correctly
2359     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2360     for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) {
2361         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2362         if (!item) qWarning() << "Item" << i << "not found";
2363         QTRY_VERIFY(item);
2364         QTRY_COMPARE(item->y(), i*20.);
2365     }
2366
2367     // Position on an item that would leave empty space if positioned at the top
2368     listview->positionViewAtIndex(28, QSGListView::Beginning);
2369     QTRY_COMPARE(listview->contentY(), 480.);
2370
2371     // Confirm items positioned correctly
2372     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2373     for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) {
2374         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2375         if (!item) qWarning() << "Item" << i << "not found";
2376         QTRY_VERIFY(item);
2377         QTRY_COMPARE(item->y(), i*20.);
2378     }
2379
2380     // Position at the beginning again
2381     listview->positionViewAtIndex(0, QSGListView::Beginning);
2382     QTRY_COMPARE(listview->contentY(), 0.);
2383
2384     // Confirm items positioned correctly
2385     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2386     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
2387         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2388         if (!item) qWarning() << "Item" << i << "not found";
2389         QTRY_VERIFY(item);
2390         QTRY_COMPARE(item->y(), i*20.);
2391     }
2392
2393     // Position at End using last index
2394     listview->positionViewAtIndex(model.count()-1, QSGListView::End);
2395     QTRY_COMPARE(listview->contentY(), 480.);
2396
2397     // Confirm items positioned correctly
2398     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2399     for (int i = 24; i < model.count(); ++i) {
2400         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2401         if (!item) qWarning() << "Item" << i << "not found";
2402         QTRY_VERIFY(item);
2403         QTRY_COMPARE(item->y(), i*20.);
2404     }
2405
2406     // Position at End
2407     listview->positionViewAtIndex(20, QSGListView::End);
2408     QTRY_COMPARE(listview->contentY(), 100.);
2409
2410     // Position in Center
2411     listview->positionViewAtIndex(15, QSGListView::Center);
2412     QTRY_COMPARE(listview->contentY(), 150.);
2413
2414     // Ensure at least partially visible
2415     listview->positionViewAtIndex(15, QSGListView::Visible);
2416     QTRY_COMPARE(listview->contentY(), 150.);
2417
2418     listview->setContentY(302);
2419     listview->positionViewAtIndex(15, QSGListView::Visible);
2420     QTRY_COMPARE(listview->contentY(), 302.);
2421
2422     listview->setContentY(320);
2423     listview->positionViewAtIndex(15, QSGListView::Visible);
2424     QTRY_COMPARE(listview->contentY(), 300.);
2425
2426     listview->setContentY(85);
2427     listview->positionViewAtIndex(20, QSGListView::Visible);
2428     QTRY_COMPARE(listview->contentY(), 85.);
2429
2430     listview->setContentY(75);
2431     listview->positionViewAtIndex(20, QSGListView::Visible);
2432     QTRY_COMPARE(listview->contentY(), 100.);
2433
2434     // Ensure completely visible
2435     listview->setContentY(120);
2436     listview->positionViewAtIndex(20, QSGListView::Contain);
2437     QTRY_COMPARE(listview->contentY(), 120.);
2438
2439     listview->setContentY(302);
2440     listview->positionViewAtIndex(15, QSGListView::Contain);
2441     QTRY_COMPARE(listview->contentY(), 300.);
2442
2443     listview->setContentY(85);
2444     listview->positionViewAtIndex(20, QSGListView::Contain);
2445     QTRY_COMPARE(listview->contentY(), 100.);
2446
2447     // positionAtBeginnging
2448     listview->positionViewAtBeginning();
2449     QTRY_COMPARE(listview->contentY(), 0.);
2450
2451     listview->setContentY(80);
2452     canvas->rootObject()->setProperty("showHeader", true);
2453     listview->positionViewAtBeginning();
2454     QTRY_COMPARE(listview->contentY(), -30.);
2455
2456     // positionAtEnd
2457     listview->positionViewAtEnd();
2458     QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
2459
2460     listview->setContentY(80);
2461     canvas->rootObject()->setProperty("showFooter", true);
2462     listview->positionViewAtEnd();
2463     QTRY_COMPARE(listview->contentY(), 510.);
2464
2465     // set current item to outside visible view, position at beginning
2466     // and ensure highlight moves to current item
2467     listview->setCurrentIndex(1);
2468     listview->positionViewAtBeginning();
2469     QTRY_COMPARE(listview->contentY(), -30.);
2470     QVERIFY(listview->highlightItem());
2471     QCOMPARE(listview->highlightItem()->y(), 20.);
2472
2473     delete canvas;
2474     delete testObject;
2475 }
2476
2477 void tst_QSGListView::resetModel()
2478 {
2479     QSGView *canvas = createView();
2480
2481     QStringList strings;
2482     strings << "one" << "two" << "three";
2483     QStringListModel model(strings);
2484
2485     QDeclarativeContext *ctxt = canvas->rootContext();
2486     ctxt->setContextProperty("testModel", &model);
2487
2488     canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
2489     qApp->processEvents();
2490
2491     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2492     QTRY_VERIFY(listview != 0);
2493
2494     QSGItem *contentItem = listview->contentItem();
2495     QTRY_VERIFY(contentItem != 0);
2496
2497     QTRY_COMPARE(listview->count(), model.rowCount());
2498
2499     for (int i = 0; i < model.rowCount(); ++i) {
2500         QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
2501         QTRY_VERIFY(display != 0);
2502         QTRY_COMPARE(display->text(), strings.at(i));
2503     }
2504
2505     strings.clear();
2506     strings << "four" << "five" << "six" << "seven";
2507     model.setStringList(strings);
2508
2509     QTRY_COMPARE(listview->count(), model.rowCount());
2510
2511     for (int i = 0; i < model.rowCount(); ++i) {
2512         QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
2513         QTRY_VERIFY(display != 0);
2514         QTRY_COMPARE(display->text(), strings.at(i));
2515     }
2516
2517     delete canvas;
2518 }
2519
2520 void tst_QSGListView::propertyChanges()
2521 {
2522     QSGView *canvas = createView();
2523     QTRY_VERIFY(canvas);
2524     canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2525
2526     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2527     QTRY_VERIFY(listView);
2528
2529     QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
2530     QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
2531     QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
2532     QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
2533     QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
2534     QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
2535     QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
2536
2537     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
2538     QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
2539     QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
2540     QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::ApplyRange);
2541     QTRY_COMPARE(listView->isWrapEnabled(), true);
2542     QTRY_COMPARE(listView->cacheBuffer(), 10);
2543     QTRY_COMPARE(listView->snapMode(), QSGListView::SnapToItem);
2544
2545     listView->setHighlightFollowsCurrentItem(false);
2546     listView->setPreferredHighlightBegin(1.0);
2547     listView->setPreferredHighlightEnd(1.0);
2548     listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
2549     listView->setWrapEnabled(false);
2550     listView->setCacheBuffer(3);
2551     listView->setSnapMode(QSGListView::SnapOneItem);
2552
2553     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
2554     QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
2555     QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
2556     QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
2557     QTRY_COMPARE(listView->isWrapEnabled(), false);
2558     QTRY_COMPARE(listView->cacheBuffer(), 3);
2559     QTRY_COMPARE(listView->snapMode(), QSGListView::SnapOneItem);
2560
2561     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
2562     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
2563     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
2564     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
2565     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
2566     QTRY_COMPARE(cacheBufferSpy.count(),1);
2567     QTRY_COMPARE(snapModeSpy.count(),1);
2568
2569     listView->setHighlightFollowsCurrentItem(false);
2570     listView->setPreferredHighlightBegin(1.0);
2571     listView->setPreferredHighlightEnd(1.0);
2572     listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
2573     listView->setWrapEnabled(false);
2574     listView->setCacheBuffer(3);
2575     listView->setSnapMode(QSGListView::SnapOneItem);
2576
2577     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
2578     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
2579     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
2580     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
2581     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
2582     QTRY_COMPARE(cacheBufferSpy.count(),1);
2583     QTRY_COMPARE(snapModeSpy.count(),1);
2584
2585     delete canvas;
2586 }
2587
2588 void tst_QSGListView::componentChanges()
2589 {
2590     QSGView *canvas = createView();
2591     QTRY_VERIFY(canvas);
2592     canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2593
2594     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2595     QTRY_VERIFY(listView);
2596
2597     QDeclarativeComponent component(canvas->engine());
2598     component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
2599
2600     QDeclarativeComponent delegateComponent(canvas->engine());
2601     delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
2602
2603     QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
2604     QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
2605     QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
2606     QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
2607
2608     listView->setHighlight(&component);
2609     listView->setHeader(&component);
2610     listView->setFooter(&component);
2611     listView->setDelegate(&delegateComponent);
2612
2613     QTRY_COMPARE(listView->highlight(), &component);
2614     QTRY_COMPARE(listView->header(), &component);
2615     QTRY_COMPARE(listView->footer(), &component);
2616     QTRY_COMPARE(listView->delegate(), &delegateComponent);
2617
2618     QTRY_COMPARE(highlightSpy.count(),1);
2619     QTRY_COMPARE(delegateSpy.count(),1);
2620     QTRY_COMPARE(headerSpy.count(),1);
2621     QTRY_COMPARE(footerSpy.count(),1);
2622
2623     listView->setHighlight(&component);
2624     listView->setHeader(&component);
2625     listView->setFooter(&component);
2626     listView->setDelegate(&delegateComponent);
2627
2628     QTRY_COMPARE(highlightSpy.count(),1);
2629     QTRY_COMPARE(delegateSpy.count(),1);
2630     QTRY_COMPARE(headerSpy.count(),1);
2631     QTRY_COMPARE(footerSpy.count(),1);
2632
2633     delete canvas;
2634 }
2635
2636 void tst_QSGListView::modelChanges()
2637 {
2638     QSGView *canvas = createView();
2639     QTRY_VERIFY(canvas);
2640     canvas->setSource(QUrl::fromLocalFile(TESTDATA("propertychangestest.qml")));
2641
2642     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2643     QTRY_VERIFY(listView);
2644
2645     QDeclarativeListModel *alternateModel = canvas->rootObject()->findChild<QDeclarativeListModel*>("alternateModel");
2646     QTRY_VERIFY(alternateModel);
2647     QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
2648     QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
2649
2650     listView->setModel(modelVariant);
2651     QTRY_COMPARE(listView->model(), modelVariant);
2652     QTRY_COMPARE(modelSpy.count(),1);
2653
2654     listView->setModel(modelVariant);
2655     QTRY_COMPARE(modelSpy.count(),1);
2656
2657     listView->setModel(QVariant());
2658     QTRY_COMPARE(modelSpy.count(),2);
2659
2660     delete canvas;
2661 }
2662
2663 void tst_QSGListView::QTBUG_9791()
2664 {
2665     QSGView *canvas = createView();
2666
2667     canvas->setSource(QUrl::fromLocalFile(TESTDATA("strictlyenforcerange.qml")));
2668     qApp->processEvents();
2669
2670     QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2671     QTRY_VERIFY(listview != 0);
2672
2673     QSGItem *contentItem = listview->contentItem();
2674     QTRY_VERIFY(contentItem != 0);
2675     QTRY_VERIFY(listview->delegate() != 0);
2676     QTRY_VERIFY(listview->model() != 0);
2677
2678     QMetaObject::invokeMethod(listview, "fillModel");
2679     qApp->processEvents();
2680
2681     // Confirm items positioned correctly
2682     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2683     QCOMPARE(itemCount, 3);
2684
2685     for (int i = 0; i < itemCount; ++i) {
2686         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2687         if (!item) qWarning() << "Item" << i << "not found";
2688         QTRY_VERIFY(item);
2689         QTRY_COMPARE(item->x(), i*300.0);
2690     }
2691
2692     // check that view is positioned correctly
2693     QTRY_COMPARE(listview->contentX(), 590.0);
2694
2695     delete canvas;
2696 }
2697
2698 void tst_QSGListView::manualHighlight()
2699 {
2700     QSGView *canvas = new QSGView(0);
2701     canvas->setGeometry(0,0,240,320);
2702
2703     QString filename(TESTDATA("manual-highlight.qml"));
2704     canvas->setSource(QUrl::fromLocalFile(filename));
2705
2706     qApp->processEvents();
2707
2708     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2709     QTRY_VERIFY(listview != 0);
2710
2711     QSGItem *contentItem = listview->contentItem();
2712     QTRY_VERIFY(contentItem != 0);
2713
2714     QTRY_COMPARE(listview->currentIndex(), 0);
2715     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
2716     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2717
2718     listview->setCurrentIndex(2);
2719
2720     QTRY_COMPARE(listview->currentIndex(), 2);
2721     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2722     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2723
2724     // QTBUG-15972
2725     listview->positionViewAtIndex(3, QSGListView::Contain);
2726
2727     QTRY_COMPARE(listview->currentIndex(), 2);
2728     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2729     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2730
2731     delete canvas;
2732 }
2733
2734 void tst_QSGListView::QTBUG_11105()
2735 {
2736     QSGView *canvas = createView();
2737
2738     TestModel model;
2739     for (int i = 0; i < 30; i++)
2740         model.addItem("Item" + QString::number(i), "");
2741
2742     QDeclarativeContext *ctxt = canvas->rootContext();
2743     ctxt->setContextProperty("testModel", &model);
2744
2745     TestObject *testObject = new TestObject;
2746     ctxt->setContextProperty("testObject", testObject);
2747
2748     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
2749     qApp->processEvents();
2750
2751     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2752     QTRY_VERIFY(listview != 0);
2753
2754     QSGItem *contentItem = listview->contentItem();
2755     QTRY_VERIFY(contentItem != 0);
2756
2757     // Confirm items positioned correctly
2758     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2759     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2760         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2761         if (!item) qWarning() << "Item" << i << "not found";
2762         QTRY_VERIFY(item);
2763         QTRY_VERIFY(item->y() == i*20);
2764     }
2765
2766     listview->positionViewAtIndex(20, QSGListView::Beginning);
2767     QCOMPARE(listview->contentY(), 280.);
2768
2769     TestModel model2;
2770     for (int i = 0; i < 5; i++)
2771         model2.addItem("Item" + QString::number(i), "");
2772
2773     ctxt->setContextProperty("testModel", &model2);
2774
2775     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2776     QCOMPARE(itemCount, 5);
2777
2778     delete canvas;
2779     delete testObject;
2780 }
2781
2782 void tst_QSGListView::header()
2783 {
2784     QFETCH(QSGListView::Orientation, orientation);
2785     QFETCH(Qt::LayoutDirection, layoutDirection);
2786     QFETCH(QPointF, initialHeaderPos);
2787     QFETCH(QPointF, firstDelegatePos);
2788     QFETCH(QPointF, initialContentPos);
2789     QFETCH(QPointF, changedHeaderPos);
2790     QFETCH(QPointF, changedContentPos);
2791     QFETCH(QPointF, resizeContentPos);
2792
2793     TestModel model;
2794     for (int i = 0; i < 30; i++)
2795         model.addItem("Item" + QString::number(i), "");
2796
2797     QSGView *canvas = createView();
2798     canvas->rootContext()->setContextProperty("testModel", &model);
2799     canvas->rootContext()->setContextProperty("initialViewWidth", 240);
2800     canvas->rootContext()->setContextProperty("initialViewHeight", 320);
2801     canvas->setSource(QUrl::fromLocalFile(TESTDATA("header.qml")));
2802
2803     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2804     QTRY_VERIFY(listview != 0);
2805     listview->setOrientation(orientation);
2806     listview->setLayoutDirection(layoutDirection);
2807
2808     QSGItem *contentItem = listview->contentItem();
2809     QTRY_VERIFY(contentItem != 0);
2810
2811     QSGText *header = findItem<QSGText>(contentItem, "header");
2812     QVERIFY(header);
2813
2814     QVERIFY(header == listview->headerItem());
2815
2816     QCOMPARE(header->width(), 100.);
2817     QCOMPARE(header->height(), 30.);
2818     QCOMPARE(header->pos(), initialHeaderPos);
2819     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2820
2821     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2822     QVERIFY(item);
2823     QCOMPARE(item->pos(), firstDelegatePos);
2824
2825     model.clear();
2826     QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
2827
2828     for (int i = 0; i < 30; i++)
2829         model.addItem("Item" + QString::number(i), "");
2830
2831     QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
2832     QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
2833
2834     QCOMPARE(headerItemSpy.count(), 1);
2835
2836     header = findItem<QSGText>(contentItem, "header");
2837     QVERIFY(!header);
2838     header = findItem<QSGText>(contentItem, "header2");
2839     QVERIFY(header);
2840
2841     QVERIFY(header == listview->headerItem());
2842
2843     QCOMPARE(header->pos(), changedHeaderPos);
2844     QCOMPARE(header->width(), 50.);
2845     QCOMPARE(header->height(), 20.);
2846     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2847     QCOMPARE(item->pos(), firstDelegatePos);
2848
2849     delete canvas;
2850
2851
2852     // QTBUG-21207 header should become visible if view resizes from initial empty size
2853
2854     canvas = createView();
2855     canvas->rootContext()->setContextProperty("testModel", &model);
2856     canvas->rootContext()->setContextProperty("initialViewWidth", 0.0);
2857     canvas->rootContext()->setContextProperty("initialViewHeight", 0.0);
2858     canvas->setSource(QUrl::fromLocalFile(TESTDATA("header.qml")));
2859
2860     listview = findItem<QSGListView>(canvas->rootObject(), "list");
2861     QTRY_VERIFY(listview != 0);
2862     listview->setOrientation(orientation);
2863     listview->setLayoutDirection(layoutDirection);
2864
2865     listview->setWidth(240);
2866     listview->setHeight(320);
2867     QTRY_COMPARE(listview->headerItem()->pos(), initialHeaderPos);
2868     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2869
2870
2871     delete canvas;
2872 }
2873
2874 void tst_QSGListView::header_data()
2875 {
2876     QTest::addColumn<QSGListView::Orientation>("orientation");
2877     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2878     QTest::addColumn<QPointF>("initialHeaderPos");
2879     QTest::addColumn<QPointF>("changedHeaderPos");
2880     QTest::addColumn<QPointF>("initialContentPos");
2881     QTest::addColumn<QPointF>("changedContentPos");
2882     QTest::addColumn<QPointF>("firstDelegatePos");
2883     QTest::addColumn<QPointF>("resizeContentPos");
2884
2885     // header1 = 100 x 30
2886     // header2 = 50 x 20
2887     // delegates = 240 x 20
2888     // view width = 240
2889
2890     // header above items, top left
2891     QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight
2892         << QPointF(0, -30)
2893         << QPointF(0, -20)
2894         << QPointF(0, -30)
2895         << QPointF(0, -20)
2896         << QPointF(0, 0)
2897         << QPointF(0, -10);
2898
2899     // header above items, top right
2900     QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2901         << QPointF(0, -30)
2902         << QPointF(0, -20)
2903         << QPointF(0, -30)
2904         << QPointF(0, -20)
2905         << QPointF(0, 0)
2906         << QPointF(0, -10);
2907
2908     // header to left of items
2909     QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2910         << QPointF(-100, 0)
2911         << QPointF(-50, 0)
2912         << QPointF(-100, 0)
2913         << QPointF(-50, 0)
2914         << QPointF(0, 0)
2915         << QPointF(-40, 0);
2916
2917     // header to right of items
2918     QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2919         << QPointF(0, 0)
2920         << QPointF(0, 0)
2921         << QPointF(-240 + 100, 0)
2922         << QPointF(-240 + 50, 0)
2923         << QPointF(-240, 0)
2924         << QPointF(-240 + 40, 0);
2925 }
2926
2927 void tst_QSGListView::header_delayItemCreation()
2928 {
2929     QSGView *canvas = createView();
2930
2931     TestModel model;
2932
2933     canvas->rootContext()->setContextProperty("setCurrentToZero", false);
2934     canvas->setSource(QUrl::fromLocalFile(TESTDATA("fillModelOnComponentCompleted.qml")));
2935     qApp->processEvents();
2936
2937     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2938     QTRY_VERIFY(listview != 0);
2939
2940     QSGItem *contentItem = listview->contentItem();
2941     QTRY_VERIFY(contentItem != 0);
2942
2943     QSGText *header = findItem<QSGText>(contentItem, "header");
2944     QVERIFY(header);
2945     QCOMPARE(header->y(), -header->height());
2946
2947     QCOMPARE(listview->contentY(), -header->height());
2948
2949     model.clear();
2950     QTRY_COMPARE(header->y(), -header->height());
2951
2952     delete canvas;
2953 }
2954
2955 void tst_QSGListView::footer()
2956 {
2957     QFETCH(QSGListView::Orientation, orientation);
2958     QFETCH(Qt::LayoutDirection, layoutDirection);
2959     QFETCH(QPointF, initialFooterPos);
2960     QFETCH(QPointF, firstDelegatePos);
2961     QFETCH(QPointF, initialContentPos);
2962     QFETCH(QPointF, changedFooterPos);
2963     QFETCH(QPointF, changedContentPos);
2964     QFETCH(QPointF, resizeContentPos);
2965
2966     QSGView *canvas = createView();
2967
2968     TestModel model;
2969     for (int i = 0; i < 3; i++)
2970         model.addItem("Item" + QString::number(i), "");
2971
2972     QDeclarativeContext *ctxt = canvas->rootContext();
2973     ctxt->setContextProperty("testModel", &model);
2974
2975     canvas->setSource(QUrl::fromLocalFile(TESTDATA("footer.qml")));
2976     canvas->show();
2977     qApp->processEvents();
2978
2979     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2980     QTRY_VERIFY(listview != 0);
2981     listview->setOrientation(orientation);
2982     listview->setLayoutDirection(layoutDirection);
2983
2984     QSGItem *contentItem = listview->contentItem();
2985     QTRY_VERIFY(contentItem != 0);
2986
2987     QSGText *footer = findItem<QSGText>(contentItem, "footer");
2988     QVERIFY(footer);
2989
2990     QVERIFY(footer == listview->footerItem());
2991
2992     QCOMPARE(footer->pos(), initialFooterPos);
2993     QCOMPARE(footer->width(), 100.);
2994     QCOMPARE(footer->height(), 30.);
2995     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2996
2997     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2998     QVERIFY(item);
2999     QCOMPARE(item->pos(), firstDelegatePos);
3000
3001     // remove one item
3002     model.removeItem(1);
3003
3004     if (orientation == QSGListView::Vertical) {
3005         QTRY_COMPARE(footer->y(), initialFooterPos.y() - 20);   // delegate height = 20
3006     } else {
3007         QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
3008                 initialFooterPos.x() - 40 : initialFooterPos.x() + 40);  // delegate width = 40
3009     }
3010
3011     // remove all items
3012     model.clear();
3013
3014     QPointF posWhenNoItems(0, 0);
3015     if (orientation == QSGListView::Horizontal && layoutDirection == Qt::RightToLeft)
3016         posWhenNoItems.setX(-100);
3017     QTRY_COMPARE(footer->pos(), posWhenNoItems);
3018
3019     // if header is present, it's at a negative pos, so the footer should not move
3020     canvas->rootObject()->setProperty("showHeader", true);
3021     QTRY_COMPARE(footer->pos(), posWhenNoItems);
3022     canvas->rootObject()->setProperty("showHeader", false);
3023
3024     // add 30 items
3025     for (int i = 0; i < 30; i++)
3026         model.addItem("Item" + QString::number(i), "");
3027
3028     QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
3029     QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
3030
3031     QCOMPARE(footerItemSpy.count(), 1);
3032
3033     footer = findItem<QSGText>(contentItem, "footer");
3034     QVERIFY(!footer);
3035     footer = findItem<QSGText>(contentItem, "footer2");
3036     QVERIFY(footer);
3037
3038     QVERIFY(footer == listview->footerItem());
3039
3040     QCOMPARE(footer->pos(), changedFooterPos);
3041     QCOMPARE(footer->width(), 50.);
3042     QCOMPARE(footer->height(), 20.);
3043     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3044
3045     item = findItem<QSGItem>(contentItem, "wrapper", 0);
3046     QVERIFY(item);
3047     QCOMPARE(item->pos(), firstDelegatePos);
3048
3049     listview->positionViewAtEnd();
3050     footer->setHeight(10);
3051     footer->setWidth(40);
3052     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3053
3054     delete canvas;
3055 }
3056
3057 void tst_QSGListView::footer_data()
3058 {
3059     QTest::addColumn<QSGListView::Orientation>("orientation");
3060     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3061     QTest::addColumn<QPointF>("initialFooterPos");
3062     QTest::addColumn<QPointF>("changedFooterPos");
3063     QTest::addColumn<QPointF>("initialContentPos");
3064     QTest::addColumn<QPointF>("changedContentPos");
3065     QTest::addColumn<QPointF>("firstDelegatePos");
3066     QTest::addColumn<QPointF>("resizeContentPos");
3067
3068     // footer1 = 100 x 30
3069     // footer2 = 50 x 20
3070     // delegates = 40 x 20
3071     // view width = 240
3072     // view height = 320
3073
3074     // footer below items, bottom left
3075     QTest::newRow("vertical, layout left to right") << QSGListView::Vertical << Qt::LeftToRight
3076         << QPointF(0, 3 * 20)
3077         << QPointF(0, 30 * 20)  // added 30 items
3078         << QPointF(0, 0)
3079         << QPointF(0, 0)
3080         << QPointF(0, 0)
3081         << QPointF(0, 30 * 20 - 320 + 10);
3082
3083     // footer below items, bottom right
3084     QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
3085         << QPointF(0, 3 * 20)
3086         << QPointF(0, 30 * 20)
3087         << QPointF(0, 0)
3088         << QPointF(0, 0)
3089         << QPointF(0, 0)
3090         << QPointF(0, 30 * 20 - 320 + 10);
3091
3092     // footer to right of items
3093     QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
3094         << QPointF(40 * 3, 0)
3095         << QPointF(40 * 30, 0)
3096         << QPointF(0, 0)
3097         << QPointF(0, 0)
3098         << QPointF(0, 0)
3099         << QPointF(40 * 30 - 240 + 40, 0);
3100
3101     // footer to left of items
3102     QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
3103         << QPointF(-(40 * 3) - 100, 0)
3104         << QPointF(-(40 * 30) - 50, 0)     // 50 = new footer width
3105         << QPointF(-240, 0)
3106         << QPointF(-240, 0)
3107         << QPointF(-40, 0)
3108         << QPointF(-(40 * 30) - 40, 0);
3109 }
3110
3111 class LVAccessor : public QSGListView
3112 {
3113 public:
3114     qreal minY() const { return minYExtent(); }
3115     qreal maxY() const { return maxYExtent(); }
3116     qreal minX() const { return minXExtent(); }
3117     qreal maxX() const { return maxXExtent(); }
3118 };
3119
3120 void tst_QSGListView::headerFooter()
3121 {
3122     {
3123         // Vertical
3124         QSGView *canvas = createView();
3125
3126         TestModel model;
3127         QDeclarativeContext *ctxt = canvas->rootContext();
3128         ctxt->setContextProperty("testModel", &model);
3129
3130         canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3131         qApp->processEvents();
3132
3133         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3134         QTRY_VERIFY(listview != 0);
3135
3136         QSGItem *contentItem = listview->contentItem();
3137         QTRY_VERIFY(contentItem != 0);
3138
3139         QSGItem *header = findItem<QSGItem>(contentItem, "header");
3140         QVERIFY(header);
3141         QCOMPARE(header->y(), -header->height());
3142
3143         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3144         QVERIFY(footer);
3145         QCOMPARE(footer->y(), 0.);
3146
3147         QCOMPARE(static_cast<LVAccessor*>(listview)->minY(), header->height());
3148         QCOMPARE(static_cast<LVAccessor*>(listview)->maxY(), header->height());
3149
3150         delete canvas;
3151     }
3152     {
3153         // Horizontal
3154         QSGView *canvas = createView();
3155
3156         TestModel model;
3157         QDeclarativeContext *ctxt = canvas->rootContext();
3158         ctxt->setContextProperty("testModel", &model);
3159
3160         canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3161         canvas->rootObject()->setProperty("horizontal", true);
3162         qApp->processEvents();
3163
3164         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3165         QTRY_VERIFY(listview != 0);
3166
3167         QSGItem *contentItem = listview->contentItem();
3168         QTRY_VERIFY(contentItem != 0);
3169
3170         QSGItem *header = findItem<QSGItem>(contentItem, "header");
3171         QVERIFY(header);
3172         QCOMPARE(header->x(), -header->width());
3173
3174         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3175         QVERIFY(footer);
3176         QCOMPARE(footer->x(), 0.);
3177
3178         QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), header->width());
3179         QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), header->width());
3180
3181         delete canvas;
3182     }
3183     {
3184         // Horizontal RTL
3185         QSGView *canvas = createView();
3186
3187         TestModel model;
3188         QDeclarativeContext *ctxt = canvas->rootContext();
3189         ctxt->setContextProperty("testModel", &model);
3190
3191         canvas->setSource(QUrl::fromLocalFile(TESTDATA("headerfooter.qml")));
3192         canvas->rootObject()->setProperty("horizontal", true);
3193         canvas->rootObject()->setProperty("rtl", true);
3194         qApp->processEvents();
3195
3196         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3197         QTRY_VERIFY(listview != 0);
3198
3199         QSGItem *contentItem = listview->contentItem();
3200         QTRY_VERIFY(contentItem != 0);
3201
3202         QSGItem *header = findItem<QSGItem>(contentItem, "header");
3203         QVERIFY(header);
3204         QCOMPARE(header->x(), 0.);
3205
3206         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
3207         QVERIFY(footer);
3208         QCOMPARE(footer->x(), -footer->width());
3209
3210         QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), 240. - header->width());
3211         QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), 240. - header->width());
3212
3213         delete canvas;
3214     }
3215 }
3216
3217 void tst_QSGListView::resizeView()
3218 {
3219     QSGView *canvas = createView();
3220
3221     TestModel model;
3222     for (int i = 0; i < 40; i++)
3223         model.addItem("Item" + QString::number(i), "");
3224
3225     QDeclarativeContext *ctxt = canvas->rootContext();
3226     ctxt->setContextProperty("testModel", &model);
3227
3228     TestObject *testObject = new TestObject;
3229     ctxt->setContextProperty("testObject", testObject);
3230
3231     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3232     qApp->processEvents();
3233
3234     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3235     QTRY_VERIFY(listview != 0);
3236
3237     QSGItem *contentItem = listview->contentItem();
3238     QTRY_VERIFY(contentItem != 0);
3239
3240     // Confirm items positioned correctly
3241     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
3242     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3243         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3244         if (!item) qWarning() << "Item" << i << "not found";
3245         QTRY_VERIFY(item);
3246         QTRY_COMPARE(item->y(), i*20.);
3247     }
3248
3249     QVariant heightRatio;
3250     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3251     QCOMPARE(heightRatio.toReal(), 0.4);
3252
3253     listview->setHeight(200);
3254
3255     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3256     QCOMPARE(heightRatio.toReal(), 0.25);
3257
3258     delete canvas;
3259     delete testObject;
3260 }
3261
3262 void tst_QSGListView::resizeViewAndRepaint()
3263 {
3264     QSGView *canvas = createView();
3265     canvas->show();
3266
3267     TestModel model;
3268     for (int i = 0; i < 40; i++)
3269         model.addItem("Item" + QString::number(i), "");
3270
3271     QDeclarativeContext *ctxt = canvas->rootContext();
3272     ctxt->setContextProperty("testModel", &model);
3273     ctxt->setContextProperty("initialHeight", 100);
3274
3275     canvas->setSource(QUrl::fromLocalFile(TESTDATA("resizeview.qml")));
3276     qApp->processEvents();
3277
3278     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3279     QTRY_VERIFY(listview != 0);
3280     QSGItem *contentItem = listview->contentItem();
3281     QTRY_VERIFY(contentItem != 0);
3282
3283     // item at index 10 should not be currently visible
3284     QVERIFY(!findItem<QSGItem>(contentItem, "wrapper", 10));
3285
3286     listview->setHeight(320);
3287     QTRY_VERIFY(findItem<QSGItem>(contentItem, "wrapper", 10));
3288
3289     listview->setHeight(100);
3290     QTRY_VERIFY(!findItem<QSGItem>(contentItem, "wrapper", 10));
3291
3292     delete canvas;
3293 }
3294
3295 void tst_QSGListView::sizeLessThan1()
3296 {
3297     QSGView *canvas = createView();
3298
3299     TestModel model;
3300     for (int i = 0; i < 30; i++)
3301         model.addItem("Item" + QString::number(i), "");
3302
3303     QDeclarativeContext *ctxt = canvas->rootContext();
3304     ctxt->setContextProperty("testModel", &model);
3305
3306     TestObject *testObject = new TestObject;
3307     ctxt->setContextProperty("testObject", testObject);
3308
3309     canvas->setSource(QUrl::fromLocalFile(TESTDATA("sizelessthan1.qml")));
3310     qApp->processEvents();
3311
3312     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3313     QTRY_VERIFY(listview != 0);
3314
3315     QSGItem *contentItem = listview->contentItem();
3316     QTRY_VERIFY(contentItem != 0);
3317
3318     // Confirm items positioned correctly
3319     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
3320     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3321         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3322         if (!item) qWarning() << "Item" << i << "not found";
3323         QTRY_VERIFY(item);
3324         QTRY_COMPARE(item->y(), i*0.5);
3325     }
3326
3327     delete canvas;
3328     delete testObject;
3329 }
3330
3331 void tst_QSGListView::QTBUG_14821()
3332 {
3333     QSGView *canvas = createView();
3334
3335     canvas->setSource(QUrl::fromLocalFile(TESTDATA("qtbug14821.qml")));
3336     qApp->processEvents();
3337
3338     QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
3339     QVERIFY(listview != 0);
3340
3341     QSGItem *contentItem = listview->contentItem();
3342     QVERIFY(contentItem != 0);
3343
3344     listview->decrementCurrentIndex();
3345     QCOMPARE(listview->currentIndex(), 99);
3346
3347     listview->incrementCurrentIndex();
3348     QCOMPARE(listview->currentIndex(), 0);
3349
3350     delete canvas;
3351 }
3352
3353 void tst_QSGListView::resizeDelegate()
3354 {
3355     QSGView *canvas = createView();
3356     canvas->show();
3357
3358     QStringList strings;
3359     for (int i = 0; i < 30; ++i)
3360         strings << QString::number(i);
3361     QStringListModel model(strings);
3362
3363     QDeclarativeContext *ctxt = canvas->rootContext();
3364     ctxt->setContextProperty("testModel", &model);
3365
3366     canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
3367     qApp->processEvents();
3368
3369     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3370     QVERIFY(listview != 0);
3371
3372     QSGItem *contentItem = listview->contentItem();
3373     QVERIFY(contentItem != 0);
3374
3375     QCOMPARE(listview->count(), model.rowCount());
3376
3377     listview->setCurrentIndex(25);
3378     listview->setContentY(0);
3379     QTest::qWait(300);
3380
3381     for (int i = 0; i < 16; ++i) {
3382         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3383         QVERIFY(item != 0);
3384         QCOMPARE(item->y(), i*20.0);
3385     }
3386
3387     QCOMPARE(listview->currentItem()->y(), 500.0);
3388     QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
3389
3390     canvas->rootObject()->setProperty("delegateHeight", 30);
3391     QTest::qWait(300);
3392
3393     for (int i = 0; i < 11; ++i) {
3394         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3395         QVERIFY(item != 0);
3396         QTRY_COMPARE(item->y(), i*30.0);
3397     }
3398
3399     QTRY_COMPARE(listview->currentItem()->y(), 750.0);
3400     QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
3401
3402     listview->setCurrentIndex(1);
3403     listview->positionViewAtIndex(25, QSGListView::Beginning);
3404     listview->positionViewAtIndex(5, QSGListView::Beginning);
3405
3406     for (int i = 5; i < 16; ++i) {
3407         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3408         QVERIFY(item != 0);
3409         QCOMPARE(item->y(), i*30.0);
3410     }
3411
3412     QTRY_COMPARE(listview->currentItem()->y(), 30.0);
3413     QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
3414
3415     canvas->rootObject()->setProperty("delegateHeight", 20);
3416     QTest::qWait(300);
3417
3418     for (int i = 5; i < 11; ++i) {
3419         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
3420         QVERIFY(item != 0);
3421         QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
3422     }
3423
3424     QTRY_COMPARE(listview->currentItem()->y(), 70.0);
3425     QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
3426
3427     delete canvas;
3428 }
3429
3430 void tst_QSGListView::resizeFirstDelegate()
3431 {
3432     // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
3433     // and other delegates have height > 0
3434
3435     QSGView *canvas = createView();
3436     canvas->show();
3437
3438     // bug only occurs when all items in the model are visible
3439     TestModel model;
3440     for (int i = 0; i < 10; i++)
3441         model.addItem("Item" + QString::number(i), "");
3442
3443     QDeclarativeContext *ctxt = canvas->rootContext();
3444     ctxt->setContextProperty("testModel", &model);
3445
3446     TestObject *testObject = new TestObject;
3447     ctxt->setContextProperty("testObject", testObject);
3448
3449     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3450     qApp->processEvents();
3451
3452     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3453     QVERIFY(listview != 0);
3454
3455     QSGItem *contentItem = listview->contentItem();
3456     QVERIFY(contentItem != 0);
3457
3458     QSGItem *item = 0;
3459     for (int i = 0; i < model.count(); ++i) {
3460         item = findItem<QSGItem>(contentItem, "wrapper", i);
3461         QVERIFY(item != 0);
3462         QCOMPARE(item->y(), i*20.0);
3463     }
3464
3465     item = findItem<QSGItem>(contentItem, "wrapper", 0);
3466     item->setHeight(0);
3467
3468     // check the content y has not jumped up and down
3469     QCOMPARE(listview->contentY(), 0.0);
3470     QSignalSpy spy(listview, SIGNAL(contentYChanged()));
3471     QTest::qWait(300);
3472     QCOMPARE(spy.count(), 0);
3473
3474     for (int i = 1; i < model.count(); ++i) {
3475         item = findItem<QSGItem>(contentItem, "wrapper", i);
3476         QVERIFY(item != 0);
3477         QTRY_COMPARE(item->y(), (i-1)*20.0);
3478     }
3479
3480     delete testObject;
3481     delete canvas;
3482 }
3483
3484 void tst_QSGListView::QTBUG_16037()
3485 {
3486     QSGView *canvas = createView();
3487     canvas->show();
3488
3489     canvas->setSource(QUrl::fromLocalFile(TESTDATA("qtbug16037.qml")));
3490     qApp->processEvents();
3491
3492     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "listview");
3493     QTRY_VERIFY(listview != 0);
3494
3495     QVERIFY(listview->contentHeight() <= 0.0);
3496
3497     QMetaObject::invokeMethod(canvas->rootObject(), "setModel");
3498
3499     QTRY_COMPARE(listview->contentHeight(), 80.0);
3500
3501     delete canvas;
3502 }
3503
3504 void tst_QSGListView::indexAt()
3505 {
3506     QSGView *canvas = createView();
3507
3508     TestModel model;
3509     for (int i = 0; i < 30; i++)
3510         model.addItem("Item" + QString::number(i), "");
3511
3512     QDeclarativeContext *ctxt = canvas->rootContext();
3513     ctxt->setContextProperty("testModel", &model);
3514
3515     TestObject *testObject = new TestObject;
3516     ctxt->setContextProperty("testObject", testObject);
3517
3518     canvas->setSource(QUrl::fromLocalFile(TESTDATA("listviewtest.qml")));
3519     qApp->processEvents();
3520
3521     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3522     QTRY_VERIFY(listview != 0);
3523
3524     QSGItem *contentItem = listview->contentItem();
3525     QTRY_VERIFY(contentItem != 0);
3526
3527     QCOMPARE(listview->indexAt(0,0), 0);
3528     QCOMPARE(listview->indexAt(0,19), 0);
3529     QCOMPARE(listview->indexAt(239,19), 0);
3530     QCOMPARE(listview->indexAt(0,20), 1);
3531     QCOMPARE(listview->indexAt(240,20), -1);
3532
3533     delete canvas;
3534     delete testObject;
3535 }
3536
3537 void tst_QSGListView::incrementalModel()
3538 {
3539     QSGView *canvas = createView();
3540
3541     IncrementalModel model;
3542     QDeclarativeContext *ctxt = canvas->rootContext();
3543     ctxt->setContextProperty("testModel", &model);
3544
3545     canvas->setSource(QUrl::fromLocalFile(TESTDATA("displaylist.qml")));
3546     qApp->processEvents();
3547
3548     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3549     QTRY_VERIFY(listview != 0);
3550
3551     QSGItem *contentItem = listview->contentItem();
3552     QTRY_VERIFY(contentItem != 0);
3553
3554     QTRY_COMPARE(listview->count(), 20);
3555
3556     listview->positionViewAtIndex(10, QSGListView::Beginning);
3557
3558     QTRY_COMPARE(listview->count(), 25);
3559
3560     delete canvas;
3561 }
3562
3563 void tst_QSGListView::onAdd()
3564 {
3565     QFETCH(int, initialItemCount);
3566     QFETCH(int, itemsToAdd);
3567
3568     const int delegateHeight = 10;
3569     TestModel2 model;
3570
3571     // these initial items should not trigger ListView.onAdd
3572     for (int i=0; i<initialItemCount; i++)
3573         model.addItem("dummy value", "dummy value");
3574
3575     QSGView *canvas = createView();
3576     canvas->setGeometry(0,0,200, delegateHeight * (initialItemCount + itemsToAdd));
3577     QDeclarativeContext *ctxt = canvas->rootContext();
3578     ctxt->setContextProperty("testModel", &model);
3579     ctxt->setContextProperty("delegateHeight", delegateHeight);
3580     canvas->setSource(QUrl::fromLocalFile(TESTDATA("attachedSignals.qml")));
3581
3582     QObject *object = canvas->rootObject();
3583     object->setProperty("width", canvas->width());
3584     object->setProperty("height", canvas->height());
3585     qApp->processEvents();
3586
3587     QList<QPair<QString, QString> > items;
3588     for (int i=0; i<itemsToAdd; i++)
3589         items << qMakePair(QString("value %1").arg(i), QString::number(i));
3590     model.addItems(items);
3591     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
3592
3593     QVariantList result = object->property("addedDelegates").toList();
3594     QCOMPARE(result.count(), items.count());
3595     for (int i=0; i<items.count(); i++)
3596         QCOMPARE(result[i].toString(), items[i].first);
3597
3598     delete canvas;
3599 }
3600
3601 void tst_QSGListView::onAdd_data()
3602 {
3603     QTest::addColumn<int>("initialItemCount");
3604     QTest::addColumn<int>("itemsToAdd");
3605
3606     QTest::newRow("0, add 1") << 0 << 1;
3607     QTest::newRow("0, add 2") << 0 << 2;
3608     QTest::newRow("0, add 10") << 0 << 10;
3609
3610     QTest::newRow("1, add 1") << 1 << 1;
3611     QTest::newRow("1, add 2") << 1 << 2;
3612     QTest::newRow("1, add 10") << 1 << 10;
3613
3614     QTest::newRow("5, add 1") << 5 << 1;
3615     QTest::newRow("5, add 2") << 5 << 2;
3616     QTest::newRow("5, add 10") << 5 << 10;
3617 }
3618
3619 void tst_QSGListView::onRemove()
3620 {
3621     QFETCH(int, initialItemCount);
3622     QFETCH(int, indexToRemove);
3623     QFETCH(int, removeCount);
3624
3625     const int delegateHeight = 10;
3626     TestModel2 model;
3627     for (int i=0; i<initialItemCount; i++)
3628         model.addItem(QString("value %1").arg(i), "dummy value");
3629
3630     QSGView *canvas = createView();
3631     QDeclarativeContext *ctxt = canvas->rootContext();
3632     ctxt->setContextProperty("testModel", &model);
3633     ctxt->setContextProperty("delegateHeight", delegateHeight);
3634     canvas->setSource(QUrl::fromLocalFile(TESTDATA("attachedSignals.qml")));
3635     QObject *object = canvas->rootObject();
3636
3637     model.removeItems(indexToRemove, removeCount);
3638     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
3639
3640     QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
3641
3642     delete canvas;
3643 }
3644
3645 void tst_QSGListView::onRemove_data()
3646 {
3647     QTest::addColumn<int>("initialItemCount");
3648     QTest::addColumn<int>("indexToRemove");
3649     QTest::addColumn<int>("removeCount");
3650
3651     QTest::newRow("remove first") << 1 << 0 << 1;
3652     QTest::newRow("two items, remove first") << 2 << 0 << 1;
3653     QTest::newRow("two items, remove last") << 2 << 1 << 1;
3654     QTest::newRow("two items, remove all") << 2 << 0 << 2;
3655
3656     QTest::newRow("four items, remove first") << 4 << 0 << 1;
3657     QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
3658     QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
3659     QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
3660     QTest::newRow("four items, remove last") << 4 << 3 << 1;
3661     QTest::newRow("four items, remove all") << 4 << 0 << 4;
3662
3663     QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
3664     QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
3665     QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
3666 }
3667
3668 void tst_QSGListView::rightToLeft()
3669 {
3670     QSGView *canvas = createView();
3671     canvas->setGeometry(0,0,640,320);
3672     canvas->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3673     qApp->processEvents();
3674
3675     QVERIFY(canvas->rootObject() != 0);
3676     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
3677     QTRY_VERIFY(listview != 0);
3678
3679     QSGItem *contentItem = listview->contentItem();
3680     QTRY_VERIFY(contentItem != 0);
3681
3682     QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
3683     QTRY_VERIFY(model != 0);
3684
3685     QTRY_VERIFY(model->count() == 3);
3686     QTRY_COMPARE(listview->currentIndex(), 0);
3687
3688     // initial position at first item, right edge aligned
3689     QCOMPARE(listview->contentX(), -640.);
3690
3691     QSGItem *item = findItem<QSGItem>(contentItem, "item1");
3692     QTRY_VERIFY(item);
3693     QTRY_COMPARE(item->x(), -100.0);
3694     QCOMPARE(item->height(), listview->height());
3695
3696     QSGText *text = findItem<QSGText>(contentItem, "text1");
3697     QTRY_VERIFY(text);
3698     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
3699
3700     listview->setCurrentIndex(2);
3701
3702     item = findItem<QSGItem>(contentItem, "item3");
3703     QTRY_VERIFY(item);
3704     QTRY_COMPARE(item->x(), -540.0);
3705
3706     text = findItem<QSGText>(contentItem, "text3");
3707     QTRY_VERIFY(text);
3708     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
3709
3710     QCOMPARE(listview->contentX(), -640.);
3711
3712     // Ensure resizing maintains position relative to right edge
3713     qobject_cast<QSGItem*>(canvas->rootObject())->setWidth(600);
3714     QTRY_COMPARE(listview->contentX(), -600.);
3715
3716     delete canvas;
3717 }
3718
3719 void tst_QSGListView::test_mirroring()
3720 {
3721     QSGView *canvasA = createView();
3722     canvasA->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3723     QSGListView *listviewA = findItem<QSGListView>(canvasA->rootObject(), "view");
3724     QTRY_VERIFY(listviewA != 0);
3725
3726     QSGView *canvasB = createView();
3727     canvasB->setSource(QUrl::fromLocalFile(TESTDATA("rightToLeft.qml")));
3728     QSGListView *listviewB = findItem<QSGListView>(canvasB->rootObject(), "view");
3729     QTRY_VERIFY(listviewA != 0);
3730     qApp->processEvents();
3731
3732     QList<QString> objectNames;
3733     objectNames << "item1" << "item2"; // << "item3"
3734
3735     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3736     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3737     QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
3738
3739     // LTR != RTL
3740     foreach (const QString objectName, objectNames)
3741         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3742
3743     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3744     listviewB->setProperty("layoutDirection", Qt::LeftToRight);
3745
3746     // LTR == LTR
3747     foreach (const QString objectName, objectNames)
3748         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3749
3750     QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
3751     QSGItemPrivate::get(listviewB)->setLayoutMirror(true);
3752     QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
3753
3754     // LTR != LTR+mirror
3755     foreach (const QString objectName, objectNames)
3756         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3757
3758     listviewA->setProperty("layoutDirection", Qt::RightToLeft);
3759
3760     // RTL == LTR+mirror
3761     foreach (const QString objectName, objectNames)
3762         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3763
3764     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3765
3766     // RTL != RTL+mirror
3767     foreach (const QString objectName, objectNames)
3768         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3769
3770     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3771
3772     // LTR == RTL+mirror
3773     foreach (const QString objectName, objectNames)
3774         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3775
3776     delete canvasA;
3777     delete canvasB;
3778 }
3779
3780 void tst_QSGListView::margins()
3781 {
3782     QSGView *canvas = createView();
3783
3784     TestModel2 model;
3785     for (int i = 0; i < 50; i++)
3786         model.addItem("Item" + QString::number(i), "");
3787
3788     QDeclarativeContext *ctxt = canvas->rootContext();
3789     ctxt->setContextProperty("testModel", &model);
3790
3791     canvas->setSource(QUrl::fromLocalFile(TESTDATA("margins.qml")));
3792     canvas->show();
3793     qApp->processEvents();
3794
3795     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3796     QTRY_VERIFY(listview != 0);
3797
3798     QSGItem *contentItem = listview->contentItem();
3799     QTRY_VERIFY(contentItem != 0);
3800
3801     QCOMPARE(listview->contentY(), -30.);
3802     QCOMPARE(listview->yOrigin(), 0.);
3803
3804     // check end bound
3805     listview->positionViewAtEnd();
3806     qreal pos = listview->contentY();
3807     listview->setContentY(pos + 80);
3808     listview->returnToBounds();
3809     QTRY_COMPARE(listview->contentY(), pos + 50);
3810
3811     // remove item before visible and check that top margin is maintained
3812     // and yOrigin is updated
3813     listview->setContentY(100);
3814     model.removeItem(1);
3815     QTest::qWait(100);
3816     listview->setContentY(-50);
3817     listview->returnToBounds();
3818     QCOMPARE(listview->yOrigin(), 20.);
3819     QTRY_COMPARE(listview->contentY(), -10.);
3820
3821     // reduce top margin
3822     listview->setTopMargin(20);
3823     QCOMPARE(listview->yOrigin(), 20.);
3824     QTRY_COMPARE(listview->contentY(), 0.);
3825
3826     // check end bound
3827     listview->positionViewAtEnd();
3828     pos = listview->contentY();
3829     listview->setContentY(pos + 80);
3830     listview->returnToBounds();
3831     QTRY_COMPARE(listview->contentY(), pos + 50);
3832
3833     // reduce bottom margin
3834     pos = listview->contentY();
3835     listview->setBottomMargin(40);
3836     QCOMPARE(listview->yOrigin(), 20.);
3837     QTRY_COMPARE(listview->contentY(), pos-10);
3838
3839     delete canvas;
3840 }
3841
3842 void tst_QSGListView::snapToItem_data()
3843 {
3844     QTest::addColumn<QSGListView::Orientation>("orientation");
3845     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3846     QTest::addColumn<int>("highlightRangeMode");
3847     QTest::addColumn<QPoint>("flickStart");
3848     QTest::addColumn<QPoint>("flickEnd");
3849     QTest::addColumn<qreal>("snapAlignment");
3850     QTest::addColumn<qreal>("endExtent");
3851     QTest::addColumn<qreal>("startExtent");
3852
3853     QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight << int(QSGItemView::NoHighlightRange)
3854         << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 1200.0 << 0.0;
3855
3856     QTest::newRow("horizontal, left to right") << QSGListView::Horizontal << Qt::LeftToRight << int(QSGItemView::NoHighlightRange)
3857         << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 1200.0 << 0.0;
3858
3859     QTest::newRow("horizontal, right to left") << QSGListView::Horizontal << Qt::RightToLeft << int(QSGItemView::NoHighlightRange)
3860         << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -1200.0 - 240.0 << -240.0;
3861
3862     QTest::newRow("vertical, left to right, enforce range") << QSGListView::Vertical << Qt::LeftToRight << int(QSGItemView::StrictlyEnforceRange)
3863         << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 1340.0 << -20.0;
3864
3865     QTest::newRow("horizontal, left to right, enforce range") << QSGListView::Horizontal << Qt::LeftToRight << int(QSGItemView::StrictlyEnforceRange)
3866         << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 1340.0 << -20.0;
3867
3868     QTest::newRow("horizontal, right to left, enforce range") << QSGListView::Horizontal << Qt::RightToLeft << int(QSGItemView::StrictlyEnforceRange)
3869         << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -1200.0 - 240.0 - 140.0 << -220.0;
3870 }
3871
3872 void tst_QSGListView::snapToItem()
3873 {
3874     QFETCH(QSGListView::Orientation, orientation);
3875     QFETCH(Qt::LayoutDirection, layoutDirection);
3876     QFETCH(int, highlightRangeMode);
3877     QFETCH(QPoint, flickStart);
3878     QFETCH(QPoint, flickEnd);
3879     QFETCH(qreal, snapAlignment);
3880     QFETCH(qreal, endExtent);
3881     QFETCH(qreal, startExtent);
3882
3883     QSGView *canvas = createView();
3884
3885     canvas->setSource(QUrl::fromLocalFile(TESTDATA("snapToItem.qml")));
3886     canvas->show();
3887     qApp->processEvents();
3888
3889     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
3890     QTRY_VERIFY(listview != 0);
3891
3892     listview->setOrientation(orientation);
3893     listview->setLayoutDirection(layoutDirection);
3894     listview->setHighlightRangeMode(QSGItemView::HighlightRangeMode(highlightRangeMode));
3895
3896     QSGItem *contentItem = listview->contentItem();
3897     QTRY_VERIFY(contentItem != 0);
3898
3899     // confirm that a flick hits an item boundary
3900     flick(canvas, flickStart, flickEnd, 180);
3901     QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3902     if (orientation == QSGListView::Vertical)
3903         QCOMPARE(qreal(fmod(listview->contentY(),80.0)), snapAlignment);
3904     else
3905         QCOMPARE(qreal(fmod(listview->contentX(),80.0)), snapAlignment);
3906
3907     // flick to end
3908     do {
3909         flick(canvas, flickStart, flickEnd, 180);
3910         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3911     } while (orientation == QSGListView::Vertical
3912            ? !listview->isAtYEnd()
3913            : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
3914
3915     if (orientation == QSGListView::Vertical)
3916         QCOMPARE(listview->contentY(), endExtent);
3917     else
3918         QCOMPARE(listview->contentX(), endExtent);
3919
3920     // flick to start
3921     do {
3922         flick(canvas, flickEnd, flickStart, 180);
3923         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
3924     } while (orientation == QSGListView::Vertical
3925            ? !listview->isAtYBeginning()
3926            : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
3927
3928     if (orientation == QSGListView::Vertical)
3929         QCOMPARE(listview->contentY(), startExtent);
3930     else
3931         QCOMPARE(listview->contentX(), startExtent);
3932
3933     delete canvas;
3934 }
3935
3936 void tst_QSGListView::qListModelInterface_items()
3937 {
3938     items<TestModel>();
3939 }
3940
3941 void tst_QSGListView::qAbstractItemModel_items()
3942 {
3943     items<TestModel2>();
3944 }
3945
3946 void tst_QSGListView::qListModelInterface_changed()
3947 {
3948     changed<TestModel>();
3949 }
3950
3951 void tst_QSGListView::qAbstractItemModel_changed()
3952 {
3953     changed<TestModel2>();
3954 }
3955
3956 void tst_QSGListView::qListModelInterface_inserted()
3957 {
3958     inserted<TestModel>();
3959 }
3960
3961 void tst_QSGListView::qListModelInterface_inserted_more()
3962 {
3963     inserted_more<TestModel>();
3964 }
3965
3966 void tst_QSGListView::qListModelInterface_inserted_more_data()
3967 {
3968     inserted_more_data();
3969 }
3970
3971 void tst_QSGListView::qAbstractItemModel_inserted()
3972 {
3973     inserted<TestModel2>();
3974 }
3975
3976 void tst_QSGListView::qAbstractItemModel_inserted_more()
3977 {
3978     inserted_more<TestModel2>();
3979 }
3980
3981 void tst_QSGListView::qAbstractItemModel_inserted_more_data()
3982 {
3983     inserted_more_data();
3984 }
3985
3986 void tst_QSGListView::qListModelInterface_removed()
3987 {
3988     removed<TestModel>(false);
3989     removed<TestModel>(true);
3990 }
3991
3992 void tst_QSGListView::qAbstractItemModel_removed()
3993 {
3994     removed<TestModel2>(false);
3995     removed<TestModel2>(true);
3996 }
3997
3998 void tst_QSGListView::qListModelInterface_moved()
3999 {
4000     moved<TestModel>();
4001 }
4002
4003 void tst_QSGListView::qListModelInterface_moved_data()
4004 {
4005     moved_data();
4006 }
4007
4008 void tst_QSGListView::qAbstractItemModel_moved()
4009 {
4010     moved<TestModel2>();
4011 }
4012
4013 void tst_QSGListView::qAbstractItemModel_moved_data()
4014 {
4015     moved_data();
4016 }
4017
4018 void tst_QSGListView::qListModelInterface_clear()
4019 {
4020     clear<TestModel>();
4021 }
4022
4023 void tst_QSGListView::qAbstractItemModel_clear()
4024 {
4025     clear<TestModel2>();
4026 }
4027
4028 void tst_QSGListView::creationContext()
4029 {
4030     QSGView canvas;
4031     canvas.setGeometry(0,0,240,320);
4032     canvas.setSource(QUrl::fromLocalFile(TESTDATA("creationContext.qml")));
4033     qApp->processEvents();
4034
4035     QSGItem *rootItem = qobject_cast<QSGItem *>(canvas.rootObject());
4036     QVERIFY(rootItem);
4037     QVERIFY(rootItem->property("count").toInt() > 0);
4038
4039     QSGItem *item;
4040     QVERIFY(item = rootItem->findChild<QSGItem *>("listItem"));
4041     QCOMPARE(item->property("text").toString(), QString("Hello!"));
4042     QVERIFY(item = rootItem->findChild<QSGItem *>("header"));
4043     QCOMPARE(item->property("text").toString(), QString("Hello!"));
4044     QVERIFY(item = rootItem->findChild<QSGItem *>("footer"));
4045     QCOMPARE(item->property("text").toString(), QString("Hello!"));
4046     QVERIFY(item = rootItem->findChild<QSGItem *>("section"));
4047     QCOMPARE(item->property("text").toString(), QString("Hello!"));
4048 }
4049
4050 void tst_QSGListView::QTBUG_21742()
4051 {
4052     QSGView canvas;
4053     canvas.setGeometry(0,0,200,200);
4054     canvas.setSource(QUrl::fromLocalFile(TESTDATA("qtbug-21742.qml")));
4055     qApp->processEvents();
4056
4057     QSGItem *rootItem = qobject_cast<QSGItem *>(canvas.rootObject());
4058     QVERIFY(rootItem);
4059     QCOMPARE(rootItem->property("count").toInt(), 1);
4060 }
4061
4062 QSGView *tst_QSGListView::createView()
4063 {
4064     QSGView *canvas = new QSGView(0);
4065     canvas->setGeometry(0,0,240,320);
4066
4067     return canvas;
4068 }
4069
4070 void tst_QSGListView::flick(QSGView *canvas, const QPoint &from, const QPoint &to, int duration)
4071 {
4072     const int pointCount = 5;
4073     QPoint diff = to - from;
4074
4075     // send press, five equally spaced moves, and release.
4076     QTest::mousePress(canvas, Qt::LeftButton, 0, from);
4077
4078     for (int i = 0; i < pointCount; ++i) {
4079         QMouseEvent mv(QEvent::MouseMove, from + (i+1)*diff/pointCount, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
4080         QApplication::sendEvent(canvas, &mv);
4081         QTest::qWait(duration/pointCount);
4082         QCoreApplication::processEvents();
4083     }
4084
4085     QTest::mouseRelease(canvas, Qt::LeftButton, 0, to);
4086 }
4087
4088
4089 QSGItem *tst_QSGListView::findVisibleChild(QSGItem *parent, const QString &objectName)
4090 {
4091     QSGItem *item = 0;
4092     QList<QSGItem*> items = parent->findChildren<QSGItem*>(objectName);
4093     for (int i = 0; i < items.count(); ++i) {
4094         if (items.at(i)->isVisible()) {
4095             item = items.at(i);
4096             break;
4097         }
4098     }
4099     return item;
4100 }
4101 /*
4102    Find an item with the specified objectName.  If index is supplied then the
4103    item must also evaluate the {index} expression equal to index
4104 */
4105 template<typename T>
4106 T *tst_QSGListView::findItem(QSGItem *parent, const QString &objectName, int index)
4107 {
4108     const QMetaObject &mo = T::staticMetaObject;
4109     //qDebug() << parent->childItems().count() << "children";
4110     for (int i = 0; i < parent->childItems().count(); ++i) {
4111         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4112         if (!item)
4113             continue;
4114         //qDebug() << "try" << item;
4115         if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName)) {
4116             if (index != -1) {
4117                 QDeclarativeExpression e(qmlContext(item), item, "index");
4118                 if (e.evaluate().toInt() == index)
4119                     return static_cast<T*>(item);
4120             } else {
4121                 return static_cast<T*>(item);
4122             }
4123         }
4124         item = findItem<T>(item, objectName, index);
4125         if (item)
4126             return static_cast<T*>(item);
4127     }
4128
4129     return 0;
4130 }
4131
4132 template<typename T>
4133 QList<T*> tst_QSGListView::findItems(QSGItem *parent, const QString &objectName)
4134 {
4135     QList<T*> items;
4136     const QMetaObject &mo = T::staticMetaObject;
4137     //qDebug() << parent->childItems().count() << "children";
4138     for (int i = 0; i < parent->childItems().count(); ++i) {
4139         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4140         if (!item || !item->isVisible())
4141             continue;
4142         //qDebug() << "try" << item;
4143         if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName))
4144             items.append(static_cast<T*>(item));
4145         items += findItems<T>(item, objectName);
4146     }
4147
4148     return items;
4149 }
4150
4151 void tst_QSGListView::dumpTree(QSGItem *parent, int depth)
4152 {
4153     static QString padding("                       ");
4154     for (int i = 0; i < parent->childItems().count(); ++i) {
4155         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
4156         if (!item)
4157             continue;
4158         qDebug() << padding.left(depth*2) << item;
4159         dumpTree(item, depth+1);
4160     }
4161 }
4162
4163 QTEST_MAIN(tst_QSGListView)
4164
4165 #include "tst_qsglistview.moc"
4166