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