Show header/footer if current index is set to first/last item or row
[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 <QtGui/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 "../../../shared/util.h"
55 #include "incrementalmodel.h"
56 #include <QtOpenGL/QGLShaderProgram>
57
58 #ifdef Q_OS_SYMBIAN
59 // In Symbian OS test data is located in applications private dir
60 #define SRCDIR "."
61 #endif
62
63 Q_DECLARE_METATYPE(Qt::LayoutDirection)
64 Q_DECLARE_METATYPE(QSGListView::Orientation)
65
66 class tst_QSGListView : public QObject
67 {
68     Q_OBJECT
69 public:
70     tst_QSGListView();
71
72 private slots:
73     void initTestCase();
74     void cleanupTestCase();
75     // Test both QListModelInterface and QAbstractItemModel model types
76     void qListModelInterface_items();
77     void qAbstractItemModel_items();
78
79     void qListModelInterface_changed();
80     void qAbstractItemModel_changed();
81
82     void qListModelInterface_inserted();
83     void qAbstractItemModel_inserted();
84
85     void qListModelInterface_removed();
86     void qAbstractItemModel_removed();
87
88     void qListModelInterface_moved();
89     void qListModelInterface_moved_data();
90     void qAbstractItemModel_moved();
91     void qAbstractItemModel_moved_data();
92
93     void qListModelInterface_clear();
94     void qAbstractItemModel_clear();
95
96     void swapWithFirstItem();
97     void itemList();
98     void currentIndex();
99     void noCurrentIndex();
100     void enforceRange();
101     void enforceRange_withoutHighlight();
102     void spacing();
103     void sections();
104     void sectionsDelegate();
105     void cacheBuffer();
106     void positionViewAtIndex();
107     void resetModel();
108     void propertyChanges();
109     void componentChanges();
110     void modelChanges();
111     void QTBUG_9791();
112     void manualHighlight();
113     void QTBUG_11105();
114     void header();
115     void header_data();
116     void header_delayItemCreation();
117     void footer();
118     void footer_data();
119     void headerFooter();
120     void resizeView();
121     void sizeLessThan1();
122     void QTBUG_14821();
123     void resizeDelegate();
124     void resizeFirstDelegate();
125     void QTBUG_16037();
126     void indexAt();
127     void incrementalModel();
128     void onAdd();
129     void onAdd_data();
130     void onRemove();
131     void onRemove_data();
132     void rightToLeft();
133     void test_mirroring();
134
135 private:
136     template <class T> void items();
137     template <class T> void changed();
138     template <class T> void inserted();
139     template <class T> void removed(bool animated);
140     template <class T> void moved();
141     template <class T> void clear();
142     QSGView *createView();
143     template<typename T>
144     T *findItem(QSGItem *parent, const QString &id, int index=-1);
145     template<typename T>
146     QList<T*> findItems(QSGItem *parent, const QString &objectName);
147     void dumpTree(QSGItem *parent, int depth = 0);
148
149     void moved_data();
150 };
151
152 void tst_QSGListView::initTestCase()
153 {
154     QSGView canvas;
155     if (!QGLShaderProgram::hasOpenGLShaderPrograms(canvas.context()))
156         QSKIP("QSGListView needs OpenGL 2.0", SkipAll);
157 }
158
159 void tst_QSGListView::cleanupTestCase()
160 {
161
162 }
163 class TestObject : public QObject
164 {
165     Q_OBJECT
166
167     Q_PROPERTY(bool error READ error WRITE setError NOTIFY changedError)
168     Q_PROPERTY(bool animate READ animate NOTIFY changedAnim)
169     Q_PROPERTY(bool invalidHighlight READ invalidHighlight NOTIFY changedHl)
170     Q_PROPERTY(int cacheBuffer READ cacheBuffer NOTIFY changedCacheBuffer)
171
172 public:
173     TestObject(QObject *parent = 0)
174         : QObject(parent), mError(true), mAnimate(false), mInvalidHighlight(false)
175         , mCacheBuffer(0) {}
176
177     bool error() const { return mError; }
178     void setError(bool err) { mError = err; emit changedError(); }
179
180     bool animate() const { return mAnimate; }
181     void setAnimate(bool anim) { mAnimate = anim; emit changedAnim(); }
182
183     bool invalidHighlight() const { return mInvalidHighlight; }
184     void setInvalidHighlight(bool invalid) { mInvalidHighlight = invalid; emit changedHl(); }
185
186     int cacheBuffer() const { return mCacheBuffer; }
187     void setCacheBuffer(int buffer) { mCacheBuffer = buffer; emit changedCacheBuffer(); }
188
189 signals:
190     void changedError();
191     void changedAnim();
192     void changedHl();
193     void changedCacheBuffer();
194
195 public:
196     bool mError;
197     bool mAnimate;
198     bool mInvalidHighlight;
199     int mCacheBuffer;
200 };
201
202 template<typename T>
203 void tst_qsglistview_move(int from, int to, int n, T *items)
204 {
205     if (from > to) {
206         // Only move forwards - flip if backwards moving
207         int tfrom = from;
208         int tto = to;
209         from = tto;
210         to = tto+n;
211         n = tfrom-tto;
212     }
213     if (n == 1) {
214         items->move(from, to);
215     } else {
216         T replaced;
217         int i=0;
218         typename T::ConstIterator it=items->begin(); it += from+n;
219         for (; i<to-from; ++i,++it)
220             replaced.append(*it);
221         i=0;
222         it=items->begin(); it += from;
223         for (; i<n; ++i,++it)
224             replaced.append(*it);
225         typename T::ConstIterator f=replaced.begin();
226         typename T::Iterator t=items->begin(); t += from;
227         for (; f != replaced.end(); ++f, ++t)
228             *t = *f;
229     }
230 }
231
232 class TestModel : public QListModelInterface
233 {
234     Q_OBJECT
235 public:
236     TestModel(QObject *parent = 0) : QListModelInterface(parent) {}
237     ~TestModel() {}
238
239     enum Roles { Name, Number };
240
241     QString name(int index) const { return list.at(index).first; }
242     QString number(int index) const { return list.at(index).second; }
243
244     int count() const { return list.count(); }
245
246     QList<int> roles() const { return QList<int>() << Name << Number; }
247     QString toString(int role) const {
248         switch(role) {
249         case Name:
250             return "name";
251         case Number:
252             return "number";
253         default:
254             return "";
255         }
256     }
257
258     QVariant data(int index, int role) const
259     {
260         if (role==0)
261             return list.at(index).first;
262         if (role==1)
263             return list.at(index).second;
264         return QVariant();
265     }
266     QHash<int, QVariant> data(int index, const QList<int> &roles) const {
267         QHash<int,QVariant> returnHash;
268
269         for (int i = 0; i < roles.size(); ++i) {
270             int role = roles.at(i);
271             QVariant info;
272             switch(role) {
273             case Name:
274                 info = list.at(index).first;
275                 break;
276             case Number:
277                 info = list.at(index).second;
278                 break;
279             default:
280                 break;
281             }
282             returnHash.insert(role, info);
283         }
284         return returnHash;
285     }
286
287     void addItem(const QString &name, const QString &number) {
288         list.append(QPair<QString,QString>(name, number));
289         emit itemsInserted(list.count()-1, 1);
290     }
291
292     void insertItem(int index, const QString &name, const QString &number) {
293         list.insert(index, QPair<QString,QString>(name, number));
294         emit itemsInserted(index, 1);
295     }
296
297     void removeItem(int index) {
298         list.removeAt(index);
299         emit itemsRemoved(index, 1);
300     }
301
302     void removeItems(int index, int count) {
303         int c = count;
304         while (c--)
305             list.removeAt(index);
306         emit itemsRemoved(index, count);
307     }
308
309     void moveItem(int from, int to) {
310         list.move(from, to);
311         emit itemsMoved(from, to, 1);
312     }
313
314     void moveItems(int from, int to, int count) {
315         tst_qsglistview_move(from, to, count, &list);
316         emit itemsMoved(from, to, count);
317     }
318
319     void modifyItem(int index, const QString &name, const QString &number) {
320         list[index] = QPair<QString,QString>(name, number);
321         emit itemsChanged(index, 1, roles());
322     }
323
324     void clear() {
325         int count = list.count();
326         list.clear();
327         emit itemsRemoved(0, count);
328     }
329
330 private:
331     QList<QPair<QString,QString> > list;
332 };
333
334
335 class TestModel2 : public QAbstractListModel
336 {
337 public:
338     enum Roles { Name = Qt::UserRole+1, Number = Qt::UserRole+2 };
339
340     TestModel2(QObject *parent=0) : QAbstractListModel(parent) {
341         QHash<int, QByteArray> roles;
342         roles[Name] = "name";
343         roles[Number] = "number";
344         setRoleNames(roles);
345     }
346
347     int rowCount(const QModelIndex &parent=QModelIndex()) const { Q_UNUSED(parent); return list.count(); }
348     QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const {
349         QVariant rv;
350         if (role == Name)
351             rv = list.at(index.row()).first;
352         else if (role == Number)
353             rv = list.at(index.row()).second;
354
355         return rv;
356     }
357
358     int count() const { return rowCount(); }
359     QString name(int index) const { return list.at(index).first; }
360     QString number(int index) const { return list.at(index).second; }
361
362     void addItem(const QString &name, const QString &number) {
363         emit beginInsertRows(QModelIndex(), list.count(), list.count());
364         list.append(QPair<QString,QString>(name, number));
365         emit endInsertRows();
366     }
367
368     void addItems(const QList<QPair<QString, QString> > &items) {
369         emit beginInsertRows(QModelIndex(), list.count(), list.count()+items.count()-1);
370         for (int i=0; i<items.count(); i++)
371             list.append(QPair<QString,QString>(items[i].first, items[i].second));
372         emit endInsertRows();
373     }
374
375     void insertItem(int index, const QString &name, const QString &number) {
376         emit beginInsertRows(QModelIndex(), index, index);
377         list.insert(index, QPair<QString,QString>(name, number));
378         emit endInsertRows();
379     }
380
381     void removeItem(int index) {
382         emit beginRemoveRows(QModelIndex(), index, index);
383         list.removeAt(index);
384         emit endRemoveRows();
385     }
386
387     void removeItems(int index, int count) {
388         emit beginRemoveRows(QModelIndex(), index, index+count-1);
389         while (count--)
390             list.removeAt(index);
391         emit endRemoveRows();
392     }
393
394     void moveItem(int from, int to) {
395         emit beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
396         list.move(from, to);
397         emit endMoveRows();
398     }
399
400     void moveItems(int from, int to, int count) {
401         emit beginMoveRows(QModelIndex(), from, from+count-1, QModelIndex(), to > from ? to+count : to);
402         tst_qsglistview_move(from, to, count, &list);
403         emit endMoveRows();
404     }
405
406     void modifyItem(int idx, const QString &name, const QString &number) {
407         list[idx] = QPair<QString,QString>(name, number);
408         emit dataChanged(index(idx,0), index(idx,0));
409     }
410
411     void clear() {
412         int count = list.count();
413         emit beginRemoveRows(QModelIndex(), 0, count-1);
414         list.clear();
415         emit endRemoveRows();
416     }
417
418 private:
419     QList<QPair<QString,QString> > list;
420 };
421
422 tst_QSGListView::tst_QSGListView()
423 {
424 }
425
426 template <class T>
427 void tst_QSGListView::items()
428 {
429     QSGView *canvas = createView();
430
431     T model;
432     model.addItem("Fred", "12345");
433     model.addItem("John", "2345");
434     model.addItem("Bob", "54321");
435
436     QDeclarativeContext *ctxt = canvas->rootContext();
437     ctxt->setContextProperty("testModel", &model);
438
439     TestObject *testObject = new TestObject;
440     ctxt->setContextProperty("testObject", testObject);
441
442     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
443     qApp->processEvents();
444
445     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
446     QTRY_VERIFY(listview != 0);
447
448     QSGItem *contentItem = listview->contentItem();
449     QTRY_VERIFY(contentItem != 0);
450
451     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
452     QTRY_VERIFY(testObject->error() == false);
453
454     QTRY_VERIFY(listview->highlightItem() != 0);
455     QTRY_COMPARE(listview->count(), model.count());
456     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
457     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
458
459     // current item should be first item
460     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
461
462     for (int i = 0; i < model.count(); ++i) {
463         QSGText *name = findItem<QSGText>(contentItem, "textName", i);
464         QTRY_VERIFY(name != 0);
465         QTRY_COMPARE(name->text(), model.name(i));
466         QSGText *number = findItem<QSGText>(contentItem, "textNumber", i);
467         QTRY_VERIFY(number != 0);
468         QTRY_COMPARE(number->text(), model.number(i));
469     }
470
471     // switch to other delegate
472     testObject->setAnimate(true);
473     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
474     QTRY_VERIFY(testObject->error() == false);
475     QTRY_VERIFY(listview->currentItem());
476
477     // set invalid highlight
478     testObject->setInvalidHighlight(true);
479     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
480     QTRY_VERIFY(testObject->error() == false);
481     QTRY_VERIFY(listview->currentItem());
482     QTRY_VERIFY(listview->highlightItem() == 0);
483
484     // back to normal highlight
485     testObject->setInvalidHighlight(false);
486     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
487     QTRY_VERIFY(testObject->error() == false);
488     QTRY_VERIFY(listview->currentItem());
489     QTRY_VERIFY(listview->highlightItem() != 0);
490
491     // set an empty model and confirm that items are destroyed
492     T model2;
493     ctxt->setContextProperty("testModel", &model2);
494
495     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
496     QTRY_VERIFY(itemCount == 0);
497
498     QTRY_COMPARE(listview->highlightResizeSpeed(), 1000.0);
499     QTRY_COMPARE(listview->highlightMoveSpeed(), 1000.0);
500
501     delete canvas;
502     delete testObject;
503 }
504
505
506 template <class T>
507 void tst_QSGListView::changed()
508 {
509     QSGView *canvas = createView();
510
511     T model;
512     model.addItem("Fred", "12345");
513     model.addItem("John", "2345");
514     model.addItem("Bob", "54321");
515
516     QDeclarativeContext *ctxt = canvas->rootContext();
517     ctxt->setContextProperty("testModel", &model);
518
519     TestObject *testObject = new TestObject;
520     ctxt->setContextProperty("testObject", testObject);
521
522     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
523     qApp->processEvents();
524
525     QSGFlickable *listview = findItem<QSGFlickable>(canvas->rootObject(), "list");
526     QTRY_VERIFY(listview != 0);
527
528     QSGItem *contentItem = listview->contentItem();
529     QTRY_VERIFY(contentItem != 0);
530
531     model.modifyItem(1, "Will", "9876");
532     QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
533     QTRY_VERIFY(name != 0);
534     QTRY_COMPARE(name->text(), model.name(1));
535     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
536     QTRY_VERIFY(number != 0);
537     QTRY_COMPARE(number->text(), model.number(1));
538
539     delete canvas;
540     delete testObject;
541 }
542
543 template <class T>
544 void tst_QSGListView::inserted()
545 {
546     QSGView *canvas = createView();
547
548     T model;
549     model.addItem("Fred", "12345");
550     model.addItem("John", "2345");
551     model.addItem("Bob", "54321");
552
553     QDeclarativeContext *ctxt = canvas->rootContext();
554     ctxt->setContextProperty("testModel", &model);
555
556     TestObject *testObject = new TestObject;
557     ctxt->setContextProperty("testObject", testObject);
558
559     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
560     qApp->processEvents();
561
562     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
563     QTRY_VERIFY(listview != 0);
564
565     QSGItem *contentItem = listview->contentItem();
566     QTRY_VERIFY(contentItem != 0);
567
568     model.insertItem(1, "Will", "9876");
569
570     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
571
572     QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
573     QTRY_VERIFY(name != 0);
574     QTRY_COMPARE(name->text(), model.name(1));
575     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
576     QTRY_VERIFY(number != 0);
577     QTRY_COMPARE(number->text(), model.number(1));
578
579     // Confirm items positioned correctly
580     for (int i = 0; i < model.count(); ++i) {
581         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
582         QTRY_COMPARE(item->y(), i*20.0);
583     }
584
585     model.insertItem(0, "Foo", "1111"); // zero index, and current item
586
587     QCOMPARE(canvas->rootObject()->property("count").toInt(), model.count());
588     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
589
590     name = findItem<QSGText>(contentItem, "textName", 0);
591     QTRY_VERIFY(name != 0);
592     QTRY_COMPARE(name->text(), model.name(0));
593     number = findItem<QSGText>(contentItem, "textNumber", 0);
594     QTRY_VERIFY(number != 0);
595     QTRY_COMPARE(number->text(), model.number(0));
596
597     QTRY_COMPARE(listview->currentIndex(), 1);
598
599     // Confirm items positioned correctly
600     for (int i = 0; i < model.count(); ++i) {
601         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
602         QTRY_COMPARE(item->y(), i*20.0);
603     }
604
605     for (int i = model.count(); i < 30; ++i)
606         model.insertItem(i, "Hello", QString::number(i));
607
608     listview->setContentY(80);
609
610     // Insert item outside visible area
611     model.insertItem(1, "Hello", "1324");
612
613     QTRY_VERIFY(listview->contentY() == 80);
614
615     // Confirm items positioned correctly
616     for (int i = 5; i < 5+15; ++i) {
617         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
618         if (!item) qWarning() << "Item" << i << "not found";
619         QTRY_VERIFY(item);
620         QTRY_COMPARE(item->y(), i*20.0 - 20.0);
621     }
622
623 //    QTRY_COMPARE(listview->contentItemHeight(), model.count() * 20.0);
624
625     // QTBUG-19675
626     model.clear();
627     model.insertItem(0, "Hello", "1234");
628     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
629     QVERIFY(item);
630     QCOMPARE(item->y(), 0.);
631     QVERIFY(listview->contentY() == 0);
632
633     delete canvas;
634     delete testObject;
635 }
636
637 template <class T>
638 void tst_QSGListView::removed(bool animated)
639 {
640     QSGView *canvas = createView();
641
642     T model;
643     for (int i = 0; i < 50; i++)
644         model.addItem("Item" + QString::number(i), "");
645
646     QDeclarativeContext *ctxt = canvas->rootContext();
647     ctxt->setContextProperty("testModel", &model);
648
649     TestObject *testObject = new TestObject;
650     ctxt->setContextProperty("testObject", testObject);
651
652     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
653     canvas->show();
654     qApp->processEvents();
655
656     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
657     QTRY_VERIFY(listview != 0);
658
659     QSGItem *contentItem = listview->contentItem();
660     QTRY_VERIFY(contentItem != 0);
661
662     model.removeItem(1);
663     QCOMPARE(canvas->rootObject()->property("count").toInt(), model.count());
664
665     QSGText *name = findItem<QSGText>(contentItem, "textName", 1);
666     QTRY_VERIFY(name != 0);
667     QTRY_COMPARE(name->text(), model.name(1));
668     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 1);
669     QTRY_VERIFY(number != 0);
670     QTRY_COMPARE(number->text(), model.number(1));
671
672     // Confirm items positioned correctly
673     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
674     for (int i = 0; i < model.count() && i < itemCount; ++i) {
675         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
676         if (!item) qWarning() << "Item" << i << "not found";
677         QTRY_VERIFY(item);
678         QTRY_VERIFY(item->y() == i*20);
679     }
680
681     // Remove first item (which is the current item);
682     model.removeItem(0);  // post: top item starts at 20
683
684     QTest::qWait(300);
685
686     name = findItem<QSGText>(contentItem, "textName", 0);
687     QTRY_VERIFY(name != 0);
688     QTRY_COMPARE(name->text(), model.name(0));
689     number = findItem<QSGText>(contentItem, "textNumber", 0);
690     QTRY_VERIFY(number != 0);
691     QTRY_COMPARE(number->text(), model.number(0));
692
693     // Confirm items positioned correctly
694     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
695     for (int i = 0; i < model.count() && i < itemCount; ++i) {
696         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
697         if (!item) qWarning() << "Item" << i << "not found";
698         QTRY_VERIFY(item);
699         QTRY_COMPARE(item->y(),i*20.0 + 20.0);
700     }
701
702     // Remove items not visible
703     model.removeItem(18);
704     qApp->processEvents();
705
706     // Confirm items positioned correctly
707     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
708     for (int i = 0; i < model.count() && i < itemCount; ++i) {
709         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
710         if (!item) qWarning() << "Item" << i << "not found";
711         QTRY_VERIFY(item);
712         QTRY_COMPARE(item->y(),i*20.0+20.0);
713     }
714
715     // Remove items before visible
716     listview->setContentY(80);
717     listview->setCurrentIndex(10);
718
719     model.removeItem(1); // post: top item will be at 40
720     qApp->processEvents();
721
722     // Confirm items positioned correctly
723     for (int i = 2; i < 18; ++i) {
724         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
725         if (!item) qWarning() << "Item" << i << "not found";
726         QTRY_VERIFY(item);
727         QTRY_COMPARE(item->y(),40+i*20.0);
728     }
729
730     // Remove current index
731     QTRY_VERIFY(listview->currentIndex() == 9);
732     QSGItem *oldCurrent = listview->currentItem();
733     model.removeItem(9);
734
735     QTRY_COMPARE(listview->currentIndex(), 9);
736     QTRY_VERIFY(listview->currentItem() != oldCurrent);
737
738     listview->setContentY(40); // That's the top now
739     // let transitions settle.
740     QTest::qWait(300);
741
742     // Confirm items positioned correctly
743     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
744     for (int i = 0; i < model.count() && i < itemCount; ++i) {
745         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
746         if (!item) qWarning() << "Item" << i << "not found";
747         QTRY_VERIFY(item);
748         QTRY_COMPARE(item->y(),40+i*20.0);
749     }
750
751     // remove current item beyond visible items.
752     listview->setCurrentIndex(20);
753     listview->setContentY(40);
754     model.removeItem(20);
755
756     QTRY_COMPARE(listview->currentIndex(), 20);
757     QTRY_VERIFY(listview->currentItem() != 0);
758
759     // remove item before current, but visible
760     listview->setCurrentIndex(8);
761     oldCurrent = listview->currentItem();
762     model.removeItem(6);
763
764     QTRY_COMPARE(listview->currentIndex(), 7);
765     QTRY_VERIFY(listview->currentItem() == oldCurrent);
766
767     listview->setContentY(80);
768     QTest::qWait(300);
769
770     // remove all visible items
771     model.removeItems(1, 18);
772     QTest::qWait(300);
773
774     // Confirm items positioned correctly
775     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
776     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
777         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i+2);
778         if (!item) qWarning() << "Item" << i+2 << "not found";
779         QTRY_VERIFY(item);
780         QTRY_COMPARE(item->y(),80+i*20.0);
781     }
782
783     model.removeItems(1, 17);
784 //    QTest::qWait(300);
785
786     model.removeItems(2, 1);
787     model.addItem("New", "1");
788
789     QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", model.count()-1));
790     QCOMPARE(name->text(), QString("New"));
791
792     // Add some more items so that we don't run out
793     model.clear();
794     for (int i = 0; i < 50; i++)
795         model.addItem("Item" + QString::number(i), "");
796
797     // QTBUG-QTBUG-20575
798     listview->setCurrentIndex(0);
799     listview->setContentY(30);
800     model.removeItem(0);
801     QTRY_VERIFY(name = findItem<QSGText>(contentItem, "textName", 0));
802
803     // QTBUG-19198 move to end and remove all visible items one at a time.
804     listview->positionViewAtEnd();
805     for (int i = 0; i < 18; ++i)
806         model.removeItems(model.count() - 1, 1);
807     QTRY_VERIFY(findItems<QSGItem>(contentItem, "wrapper").count() > 16);
808
809     delete canvas;
810     delete testObject;
811 }
812
813 template <class T>
814 void tst_QSGListView::clear()
815 {
816     QSGView *canvas = createView();
817
818     T model;
819     for (int i = 0; i < 30; i++)
820         model.addItem("Item" + QString::number(i), "");
821
822     QDeclarativeContext *ctxt = canvas->rootContext();
823     ctxt->setContextProperty("testModel", &model);
824
825     TestObject *testObject = new TestObject;
826     ctxt->setContextProperty("testObject", testObject);
827
828     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
829     qApp->processEvents();
830
831     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
832     QTRY_VERIFY(listview != 0);
833
834     QSGItem *contentItem = listview->contentItem();
835     QTRY_VERIFY(contentItem != 0);
836
837     model.clear();
838
839     QTRY_VERIFY(listview->count() == 0);
840     QTRY_VERIFY(listview->currentItem() == 0);
841     QTRY_VERIFY(listview->contentY() == 0);
842     QVERIFY(listview->currentIndex() == -1);
843
844     // confirm sanity when adding an item to cleared list
845     model.addItem("New", "1");
846     QTRY_VERIFY(listview->count() == 1);
847     QVERIFY(listview->currentItem() != 0);
848     QVERIFY(listview->currentIndex() == 0);
849
850     delete canvas;
851     delete testObject;
852 }
853
854 template <class T>
855 void tst_QSGListView::moved()
856 {
857     QFETCH(qreal, contentY);
858     QFETCH(int, from);
859     QFETCH(int, to);
860     QFETCH(int, count);
861     QFETCH(qreal, itemsOffsetAfterMove);
862
863     QSGText *name;
864     QSGText *number;
865     QSGView *canvas = createView();
866     canvas->show();
867
868     T model;
869     for (int i = 0; i < 30; i++)
870         model.addItem("Item" + QString::number(i), "");
871
872     QDeclarativeContext *ctxt = canvas->rootContext();
873     ctxt->setContextProperty("testModel", &model);
874
875     TestObject *testObject = new TestObject;
876     ctxt->setContextProperty("testObject", testObject);
877
878     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
879     qApp->processEvents();
880
881     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
882     QTRY_VERIFY(listview != 0);
883
884     QSGItem *contentItem = listview->contentItem();
885     QTRY_VERIFY(contentItem != 0);
886
887     QSGItem *currentItem = listview->currentItem();
888     QTRY_VERIFY(currentItem != 0);
889
890     listview->setContentY(contentY);
891     model.moveItems(from, to, count);
892     qApp->processEvents();
893
894     // Confirm items positioned correctly and indexes correct
895     int firstVisibleIndex = qCeil(contentY / 20.0);
896     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
897
898     for (int i = firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
899         if (i >= firstVisibleIndex + 16)    // index has moved out of view
900             continue;
901         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
902         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
903         QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
904         name = findItem<QSGText>(contentItem, "textName", i);
905         QVERIFY(name != 0);
906         QTRY_COMPARE(name->text(), model.name(i));
907         number = findItem<QSGText>(contentItem, "textNumber", i);
908         QVERIFY(number != 0);
909         QTRY_COMPARE(number->text(), model.number(i));
910
911         // current index should have been updated
912         if (item == currentItem)
913             QTRY_COMPARE(listview->currentIndex(), i);
914     }
915
916     delete canvas;
917     delete testObject;
918 }
919
920 void tst_QSGListView::moved_data()
921 {
922     QTest::addColumn<qreal>("contentY");
923     QTest::addColumn<int>("from");
924     QTest::addColumn<int>("to");
925     QTest::addColumn<int>("count");
926     QTest::addColumn<qreal>("itemsOffsetAfterMove");
927
928     // model starts with 30 items, each 20px high, in area 320px high
929     // 16 items should be visible at a time
930     // itemsOffsetAfterMove should be > 0 whenever items above the visible pos have moved
931
932     QTest::newRow("move 1 forwards, within visible items")
933             << 0.0
934             << 1 << 4 << 1
935             << 0.0;
936
937     QTest::newRow("move 1 forwards, from non-visible -> visible")
938             << 80.0     // show 4-19
939             << 1 << 18 << 1
940             << 20.0;
941
942     QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)")
943             << 80.0     // show 4-19
944             << 0 << 4 << 1
945             << 20.0;
946
947     QTest::newRow("move 1 forwards, from visible -> non-visible")
948             << 0.0
949             << 1 << 16 << 1
950             << 0.0;
951
952     QTest::newRow("move 1 forwards, from visible -> non-visible (move first item)")
953             << 0.0
954             << 0 << 16 << 1
955             << 20.0;
956
957
958     QTest::newRow("move 1 backwards, within visible items")
959             << 0.0
960             << 4 << 1 << 1
961             << 0.0;
962
963     QTest::newRow("move 1 backwards, from non-visible -> visible")
964             << 0.0
965             << 20 << 4 << 1
966             << 0.0;
967
968     QTest::newRow("move 1 backwards, from non-visible -> visible (move last item)")
969             << 0.0
970             << 29 << 15 << 1
971             << 0.0;
972
973     QTest::newRow("move 1 backwards, from visible -> non-visible")
974             << 80.0     // show 4-19
975             << 16 << 1 << 1
976             << 20.0 * 15;   // this results in a forward movement that removes 15 items
977
978     QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)")
979             << 80.0     // show 4-19
980             << 16 << 0 << 1
981             << 20.0 * 16;   // everything should move to after item 16
982
983
984     QTest::newRow("move multiple forwards, within visible items")
985             << 0.0
986             << 0 << 5 << 3
987             << 20.0 * 3;
988
989     QTest::newRow("move multiple forwards, from non-visible -> visible")
990             << 80.0     // show 4-19
991             << 1 << 5 << 3
992             << 20.0 * 3;    // moving 3 from above the content y should adjust y positions accordingly
993
994     QTest::newRow("move multiple forwards, from non-visible -> visible (move first item)")
995             << 80.0     // show 4-19
996             << 0 << 5 << 3
997             << 20.0 * 3;        // moving 3 from above the content y should adjust y positions accordingly
998
999     QTest::newRow("move multiple forwards, from visible -> non-visible")
1000             << 0.0
1001             << 1 << 16 << 3
1002             << 0.0;
1003
1004     QTest::newRow("move multiple forwards, from visible -> non-visible (move first item)")
1005             << 0.0
1006             << 0 << 16 << 3
1007             << 20.0 * 3;
1008
1009
1010     QTest::newRow("move multiple backwards, within visible items")
1011             << 0.0
1012             << 4 << 1 << 3
1013             << 0.0;
1014
1015     QTest::newRow("move multiple backwards, from non-visible -> visible")
1016             << 0.0
1017             << 20 << 4 << 3
1018             << 0.0;
1019
1020     QTest::newRow("move multiple backwards, from non-visible -> visible (move last item)")
1021             << 0.0
1022             << 27 << 10 << 3
1023             << 0.0;
1024
1025     QTest::newRow("move multiple backwards, from visible -> non-visible")
1026             << 80.0     // show 4-19
1027             << 16 << 1 << 3
1028             << 20.0 * 15;   // this results in a forward movement that removes 15 items
1029
1030     QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)")
1031             << 80.0     // show 4-19
1032             << 16 << 0 << 3
1033             << 20.0 * 16;
1034 }
1035
1036 void tst_QSGListView::swapWithFirstItem()
1037 {
1038     QSGView *canvas = createView();
1039     canvas->show();
1040
1041     TestModel model;
1042     for (int i = 0; i < 30; i++)
1043         model.addItem("Item" + QString::number(i), "");
1044
1045     QDeclarativeContext *ctxt = canvas->rootContext();
1046     ctxt->setContextProperty("testModel", &model);
1047
1048     TestObject *testObject = new TestObject;
1049     ctxt->setContextProperty("testObject", testObject);
1050
1051     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
1052     qApp->processEvents();
1053
1054     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1055     QTRY_VERIFY(listview != 0);
1056
1057     // ensure content position is stable
1058     listview->setContentY(0);
1059     model.moveItem(1, 0);
1060     QTRY_VERIFY(listview->contentY() == 0);
1061
1062     delete testObject;
1063     delete canvas;
1064 }
1065
1066 void tst_QSGListView::enforceRange()
1067 {
1068     QSGView *canvas = createView();
1069
1070     TestModel model;
1071     for (int i = 0; i < 30; i++)
1072         model.addItem("Item" + QString::number(i), "");
1073
1074     QDeclarativeContext *ctxt = canvas->rootContext();
1075     ctxt->setContextProperty("testModel", &model);
1076
1077     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listview-enforcerange.qml"));
1078     qApp->processEvents();
1079
1080     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1081     QTRY_VERIFY(listview != 0);
1082
1083     QTRY_COMPARE(listview->preferredHighlightBegin(), 100.0);
1084     QTRY_COMPARE(listview->preferredHighlightEnd(), 100.0);
1085     QTRY_COMPARE(listview->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
1086
1087     QSGItem *contentItem = listview->contentItem();
1088     QTRY_VERIFY(contentItem != 0);
1089
1090     // view should be positioned at the top of the range.
1091     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
1092     QTRY_VERIFY(item);
1093     QTRY_COMPARE(listview->contentY(), -100.0);
1094
1095     QSGText *name = findItem<QSGText>(contentItem, "textName", 0);
1096     QTRY_VERIFY(name != 0);
1097     QTRY_COMPARE(name->text(), model.name(0));
1098     QSGText *number = findItem<QSGText>(contentItem, "textNumber", 0);
1099     QTRY_VERIFY(number != 0);
1100     QTRY_COMPARE(number->text(), model.number(0));
1101
1102     // Check currentIndex is updated when contentItem moves
1103     listview->setContentY(20);
1104
1105     QTRY_COMPARE(listview->currentIndex(), 6);
1106
1107     // change model
1108     TestModel model2;
1109     for (int i = 0; i < 5; i++)
1110         model2.addItem("Item" + QString::number(i), "");
1111
1112     ctxt->setContextProperty("testModel", &model2);
1113     QCOMPARE(listview->count(), 5);
1114
1115     delete canvas;
1116 }
1117
1118 void tst_QSGListView::enforceRange_withoutHighlight()
1119 {
1120     // QTBUG-20287
1121     // If no highlight is set but StrictlyEnforceRange is used, the content should still move
1122     // to the correct position (i.e. to the next/previous item, not next/previous section)
1123     // when moving up/down via incrementCurrentIndex() and decrementCurrentIndex()
1124
1125     QSGView *canvas = createView();
1126     canvas->show();
1127
1128     TestModel model;
1129     model.addItem("Item 0", "a");
1130     model.addItem("Item 1", "b");
1131     model.addItem("Item 2", "b");
1132     model.addItem("Item 3", "c");
1133
1134     QDeclarativeContext *ctxt = canvas->rootContext();
1135     ctxt->setContextProperty("testModel", &model);
1136
1137     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listview-enforcerange-nohighlight.qml"));
1138     qApp->processEvents();
1139
1140     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1141     QTRY_VERIFY(listview != 0);
1142
1143     qreal expectedPos = -100.0;
1144
1145     expectedPos += 10.0;    // scroll past 1st section's delegate (10px height)
1146     QTRY_COMPARE(listview->contentY(), expectedPos);
1147
1148     expectedPos += 20 + 10;     // scroll past 1st section and section delegate of 2nd section
1149     QTest::keyClick(canvas, Qt::Key_Down);
1150     QTRY_COMPARE(listview->contentY(), expectedPos);
1151
1152     expectedPos += 20;     // scroll past 1st item of 2nd section
1153     QTest::keyClick(canvas, Qt::Key_Down);
1154     QTRY_COMPARE(listview->contentY(), expectedPos);
1155
1156     expectedPos += 20 + 10;     // scroll past 2nd item of 2nd section and section delegate of 3rd section
1157     QTest::keyClick(canvas, Qt::Key_Down);
1158     QTRY_COMPARE(listview->contentY(), expectedPos);
1159
1160     delete canvas;
1161 }
1162
1163 void tst_QSGListView::spacing()
1164 {
1165     QSGView *canvas = createView();
1166
1167     TestModel model;
1168     for (int i = 0; i < 30; i++)
1169         model.addItem("Item" + QString::number(i), "");
1170
1171     QDeclarativeContext *ctxt = canvas->rootContext();
1172     ctxt->setContextProperty("testModel", &model);
1173
1174     TestObject *testObject = new TestObject;
1175     ctxt->setContextProperty("testObject", testObject);
1176
1177     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
1178     qApp->processEvents();
1179
1180     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1181     QTRY_VERIFY(listview != 0);
1182
1183     QSGItem *contentItem = listview->contentItem();
1184     QTRY_VERIFY(contentItem != 0);
1185
1186     // Confirm items positioned correctly
1187     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1188     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1189         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1190         if (!item) qWarning() << "Item" << i << "not found";
1191         QTRY_VERIFY(item);
1192         QTRY_VERIFY(item->y() == i*20);
1193     }
1194
1195     listview->setSpacing(10);
1196     QTRY_VERIFY(listview->spacing() == 10);
1197
1198     // Confirm items positioned correctly
1199     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1200     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1201         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1202         if (!item) qWarning() << "Item" << i << "not found";
1203         QTRY_VERIFY(item);
1204         QTRY_VERIFY(item->y() == i*30);
1205     }
1206
1207     listview->setSpacing(0);
1208
1209     // Confirm items positioned correctly
1210     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1211     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1212         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1213         if (!item) qWarning() << "Item" << i << "not found";
1214         QTRY_VERIFY(item);
1215         QTRY_COMPARE(item->y(), i*20.0);
1216     }
1217
1218     delete canvas;
1219     delete testObject;
1220 }
1221
1222 void tst_QSGListView::sections()
1223 {
1224     QSGView *canvas = createView();
1225
1226     TestModel model;
1227     for (int i = 0; i < 30; i++)
1228         model.addItem("Item" + QString::number(i), QString::number(i/5));
1229
1230     QDeclarativeContext *ctxt = canvas->rootContext();
1231     ctxt->setContextProperty("testModel", &model);
1232
1233     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listview-sections.qml"));
1234     qApp->processEvents();
1235
1236     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1237     QTRY_VERIFY(listview != 0);
1238
1239     QSGItem *contentItem = listview->contentItem();
1240     QTRY_VERIFY(contentItem != 0);
1241
1242     // Confirm items positioned correctly
1243     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1244     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1245         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1246         QTRY_VERIFY(item);
1247         QTRY_COMPARE(item->y(), qreal(i*20 + ((i+4)/5) * 20));
1248         QSGText *next = findItem<QSGText>(item, "nextSection");
1249         QCOMPARE(next->text().toInt(), (i+1)/5);
1250     }
1251
1252     QSignalSpy currentSectionChangedSpy(listview, SIGNAL(currentSectionChanged()));
1253
1254     // Remove section boundary
1255     model.removeItem(5);
1256
1257     // New section header created
1258     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 5);
1259     QTRY_VERIFY(item);
1260     QTRY_COMPARE(item->height(), 40.0);
1261
1262     model.insertItem(3, "New Item", "0");
1263
1264     // Section header moved
1265     item = findItem<QSGItem>(contentItem, "wrapper", 5);
1266     QTRY_VERIFY(item);
1267     QTRY_COMPARE(item->height(), 20.0);
1268
1269     item = findItem<QSGItem>(contentItem, "wrapper", 6);
1270     QTRY_VERIFY(item);
1271     QTRY_COMPARE(item->height(), 40.0);
1272
1273     // insert item which will become a section header
1274     model.insertItem(6, "Replace header", "1");
1275
1276     item = findItem<QSGItem>(contentItem, "wrapper", 6);
1277     QTRY_VERIFY(item);
1278     QTRY_COMPARE(item->height(), 40.0);
1279
1280     item = findItem<QSGItem>(contentItem, "wrapper", 7);
1281     QTRY_VERIFY(item);
1282     QTRY_COMPARE(item->height(), 20.0);
1283
1284     QTRY_COMPARE(listview->currentSection(), QString("0"));
1285
1286     listview->setContentY(140);
1287     QTRY_COMPARE(listview->currentSection(), QString("1"));
1288
1289     QTRY_COMPARE(currentSectionChangedSpy.count(), 1);
1290
1291     listview->setContentY(20);
1292     QTRY_COMPARE(listview->currentSection(), QString("0"));
1293
1294     QTRY_COMPARE(currentSectionChangedSpy.count(), 2);
1295
1296     item = findItem<QSGItem>(contentItem, "wrapper", 1);
1297     QTRY_VERIFY(item);
1298     QTRY_COMPARE(item->height(), 20.0);
1299
1300     // check that headers change when item changes
1301     listview->setContentY(0);
1302     model.modifyItem(0, "changed", "2");
1303
1304     item = findItem<QSGItem>(contentItem, "wrapper", 1);
1305     QTRY_VERIFY(item);
1306     QTRY_COMPARE(item->height(), 40.0);
1307
1308     delete canvas;
1309 }
1310
1311 void tst_QSGListView::sectionsDelegate()
1312 {
1313     QSGView *canvas = createView();
1314
1315     TestModel model;
1316     for (int i = 0; i < 30; i++)
1317         model.addItem("Item" + QString::number(i), QString::number(i/5));
1318
1319     QDeclarativeContext *ctxt = canvas->rootContext();
1320     ctxt->setContextProperty("testModel", &model);
1321
1322     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listview-sections_delegate.qml"));
1323     qApp->processEvents();
1324
1325     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1326     QTRY_VERIFY(listview != 0);
1327
1328     QSGItem *contentItem = listview->contentItem();
1329     QTRY_VERIFY(contentItem != 0);
1330
1331     // Confirm items positioned correctly
1332     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1333     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1334         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1335         QTRY_VERIFY(item);
1336         QTRY_COMPARE(item->y(), qreal(i*20 + ((i+5)/5) * 20));
1337         QSGText *next = findItem<QSGText>(item, "nextSection");
1338         QCOMPARE(next->text().toInt(), (i+1)/5);
1339     }
1340
1341     for (int i = 0; i < 3; ++i) {
1342         QSGItem *item = findItem<QSGItem>(contentItem, "sect_" + QString::number(i));
1343         QVERIFY(item);
1344         QTRY_COMPARE(item->y(), qreal(i*20*6));
1345     }
1346
1347     model.modifyItem(0, "One", "aaa");
1348     model.modifyItem(1, "Two", "aaa");
1349     model.modifyItem(2, "Three", "aaa");
1350     model.modifyItem(3, "Four", "aaa");
1351     model.modifyItem(4, "Five", "aaa");
1352
1353     for (int i = 0; i < 3; ++i) {
1354         QSGItem *item = findItem<QSGItem>(contentItem,
1355                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1356         QVERIFY(item);
1357         QTRY_COMPARE(item->y(), qreal(i*20*6));
1358     }
1359
1360     // remove section boundary
1361     model.removeItem(5);
1362     qApp->processEvents();
1363     for (int i = 0; i < 3; ++i) {
1364         QSGItem *item = findItem<QSGItem>(contentItem,
1365                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
1366         QVERIFY(item);
1367     }
1368
1369     // QTBUG-17606
1370     QList<QSGItem*> items = findItems<QSGItem>(contentItem, "sect_1");
1371     QCOMPARE(items.count(), 1);
1372
1373     // QTBUG-17759
1374     model.modifyItem(0, "One", "aaa");
1375     model.modifyItem(1, "One", "aaa");
1376     model.modifyItem(2, "One", "aaa");
1377     model.modifyItem(3, "Four", "aaa");
1378     model.modifyItem(4, "Four", "aaa");
1379     model.modifyItem(5, "Four", "aaa");
1380     model.modifyItem(6, "Five", "aaa");
1381     model.modifyItem(7, "Five", "aaa");
1382     model.modifyItem(8, "Five", "aaa");
1383     model.modifyItem(9, "Two", "aaa");
1384     model.modifyItem(10, "Two", "aaa");
1385     model.modifyItem(11, "Two", "aaa");
1386     QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_aaa").count(), 1);
1387     canvas->rootObject()->setProperty("sectionProperty", "name");
1388     // ensure view has settled.
1389     QTRY_COMPARE(findItems<QSGItem>(contentItem, "sect_Four").count(), 1);
1390     for (int i = 0; i < 4; ++i) {
1391         QSGItem *item = findItem<QSGItem>(contentItem,
1392                 "sect_" + model.name(i*3));
1393         QVERIFY(item);
1394         QTRY_COMPARE(item->y(), qreal(i*20*4));
1395     }
1396
1397     // QTBUG-17769
1398     model.removeItems(10, 20);
1399     // ensure view has settled.
1400     QTRY_COMPARE(findItems<QSGItem>(contentItem, "wrapper").count(), 10);
1401     // Drag view up beyond bounds
1402     QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20,20));
1403     {
1404         QMouseEvent mv(QEvent::MouseMove, QPoint(20,0), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1405         QApplication::sendEvent(canvas, &mv);
1406     }
1407     {
1408         QMouseEvent mv(QEvent::MouseMove, QPoint(20,-50), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1409         QApplication::sendEvent(canvas, &mv);
1410     }
1411     {
1412         QMouseEvent mv(QEvent::MouseMove, QPoint(20,-200), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1413         QApplication::sendEvent(canvas, &mv);
1414     }
1415     QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,-200));
1416     // view should settle back at 0
1417     QTRY_COMPARE(listview->contentY(), 0.0);
1418
1419     delete canvas;
1420 }
1421
1422 void tst_QSGListView::currentIndex()
1423 {
1424     TestModel model;
1425     for (int i = 0; i < 30; i++)
1426         model.addItem("Item" + QString::number(i), QString::number(i));
1427
1428     QSGView *canvas = new QSGView(0);
1429     canvas->setFixedSize(240,320);
1430
1431     QDeclarativeContext *ctxt = canvas->rootContext();
1432     ctxt->setContextProperty("testModel", &model);
1433     ctxt->setContextProperty("testWrap", QVariant(false));
1434
1435     QString filename(SRCDIR "/data/listview-initCurrent.qml");
1436     canvas->setSource(QUrl::fromLocalFile(filename));
1437
1438     qApp->processEvents();
1439
1440     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1441     QTRY_VERIFY(listview != 0);
1442
1443     QSGItem *contentItem = listview->contentItem();
1444     QTRY_VERIFY(contentItem != 0);
1445
1446     // current item should be 20th item at startup
1447     // and current item should be in view
1448     QCOMPARE(listview->currentIndex(), 20);
1449     QCOMPARE(listview->contentY(), 100.0);
1450     QCOMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 20));
1451     QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
1452
1453     // no wrap
1454     listview->setCurrentIndex(0);
1455     QCOMPARE(listview->currentIndex(), 0);
1456     // confirm that the velocity is updated
1457     QTRY_VERIFY(listview->verticalVelocity() != 0.0);
1458
1459     listview->incrementCurrentIndex();
1460     QCOMPARE(listview->currentIndex(), 1);
1461     listview->decrementCurrentIndex();
1462     QCOMPARE(listview->currentIndex(), 0);
1463
1464     listview->decrementCurrentIndex();
1465     QCOMPARE(listview->currentIndex(), 0);
1466
1467     // with wrap
1468     ctxt->setContextProperty("testWrap", QVariant(true));
1469     QVERIFY(listview->isWrapEnabled());
1470
1471     listview->decrementCurrentIndex();
1472     QCOMPARE(listview->currentIndex(), model.count()-1);
1473
1474     QTRY_COMPARE(listview->contentY(), 280.0);
1475
1476     listview->incrementCurrentIndex();
1477     QCOMPARE(listview->currentIndex(), 0);
1478
1479     QTRY_COMPARE(listview->contentY(), 0.0);
1480
1481
1482     // footer should become visible if it is out of view, and then current index is set to count-1
1483     canvas->rootObject()->setProperty("showFooter", true);
1484     QTRY_VERIFY(listview->footerItem());
1485     listview->setCurrentIndex(model.count()-2);
1486     QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
1487     listview->setCurrentIndex(model.count()-1);
1488     QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
1489     canvas->rootObject()->setProperty("showFooter", false);
1490
1491     // header should become visible if it is out of view, and then current index is set to 0
1492     canvas->rootObject()->setProperty("showHeader", true);
1493     QTRY_VERIFY(listview->headerItem());
1494     listview->setCurrentIndex(1);
1495     QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
1496     listview->setCurrentIndex(0);
1497     QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
1498     canvas->rootObject()->setProperty("showHeader", false);
1499
1500
1501     // Test keys
1502     canvas->show();
1503     qApp->setActiveWindow(canvas);
1504 #ifdef Q_WS_X11
1505     // to be safe and avoid failing setFocus with window managers
1506     qt_x11_wait_for_window_manager(canvas);
1507 #endif
1508     QTRY_VERIFY(canvas->hasFocus());
1509     qApp->processEvents();
1510
1511     listview->setCurrentIndex(0);
1512
1513     QTest::keyClick(canvas, Qt::Key_Down);
1514     QCOMPARE(listview->currentIndex(), 1);
1515
1516     QTest::keyClick(canvas, Qt::Key_Up);
1517     QCOMPARE(listview->currentIndex(), 0);
1518
1519     // hold down Key_Down
1520     for (int i=0; i<model.count()-1; i++) {
1521         QTest::simulateEvent(canvas, true, Qt::Key_Down, Qt::NoModifier, "", true);
1522         QTRY_COMPARE(listview->currentIndex(), i+1);
1523     }
1524     QTest::keyRelease(canvas, Qt::Key_Down);
1525     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
1526     QTRY_COMPARE(listview->contentY(), 280.0);
1527
1528     // hold down Key_Up
1529     for (int i=model.count()-1; i > 0; i--) {
1530         QTest::simulateEvent(canvas, true, Qt::Key_Up, Qt::NoModifier, "", true);
1531         QTRY_COMPARE(listview->currentIndex(), i-1);
1532     }
1533     QTest::keyRelease(canvas, Qt::Key_Up);
1534     QTRY_COMPARE(listview->currentIndex(), 0);
1535     QTRY_COMPARE(listview->contentY(), 0.0);
1536
1537
1538     // turn off auto highlight
1539     listview->setHighlightFollowsCurrentItem(false);
1540     QVERIFY(listview->highlightFollowsCurrentItem() == false);
1541
1542     QVERIFY(listview->highlightItem());
1543     qreal hlPos = listview->highlightItem()->y();
1544
1545     listview->setCurrentIndex(4);
1546     QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
1547
1548     // insert item before currentIndex
1549     listview->setCurrentIndex(28);
1550     model.insertItem(0, "Foo", "1111");
1551     QTRY_COMPARE(canvas->rootObject()->property("current").toInt(), 29);
1552
1553     // check removing highlight by setting currentIndex to -1;
1554     listview->setCurrentIndex(-1);
1555
1556     QCOMPARE(listview->currentIndex(), -1);
1557     QVERIFY(!listview->highlightItem());
1558     QVERIFY(!listview->currentItem());
1559
1560     delete canvas;
1561 }
1562
1563 void tst_QSGListView::noCurrentIndex()
1564 {
1565     TestModel model;
1566     for (int i = 0; i < 30; i++)
1567         model.addItem("Item" + QString::number(i), QString::number(i));
1568
1569     QSGView *canvas = new QSGView(0);
1570     canvas->setFixedSize(240,320);
1571
1572     QDeclarativeContext *ctxt = canvas->rootContext();
1573     ctxt->setContextProperty("testModel", &model);
1574
1575     QString filename(SRCDIR "/data/listview-noCurrent.qml");
1576     canvas->setSource(QUrl::fromLocalFile(filename));
1577
1578     qApp->processEvents();
1579
1580     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1581     QTRY_VERIFY(listview != 0);
1582
1583     QSGItem *contentItem = listview->contentItem();
1584     QTRY_VERIFY(contentItem != 0);
1585
1586     // current index should be -1 at startup
1587     // and we should not have a currentItem or highlightItem
1588     QCOMPARE(listview->currentIndex(), -1);
1589     QCOMPARE(listview->contentY(), 0.0);
1590     QVERIFY(!listview->highlightItem());
1591     QVERIFY(!listview->currentItem());
1592
1593     listview->setCurrentIndex(2);
1594     QCOMPARE(listview->currentIndex(), 2);
1595     QVERIFY(listview->highlightItem());
1596     QVERIFY(listview->currentItem());
1597
1598     delete canvas;
1599 }
1600
1601 void tst_QSGListView::itemList()
1602 {
1603     QSGView *canvas = createView();
1604
1605     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/itemlist.qml"));
1606     qApp->processEvents();
1607
1608     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
1609     QTRY_VERIFY(listview != 0);
1610
1611     QSGItem *contentItem = listview->contentItem();
1612     QTRY_VERIFY(contentItem != 0);
1613
1614     QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
1615     QTRY_VERIFY(model != 0);
1616
1617     QTRY_VERIFY(model->count() == 3);
1618     QTRY_COMPARE(listview->currentIndex(), 0);
1619
1620     QSGItem *item = findItem<QSGItem>(contentItem, "item1");
1621     QTRY_VERIFY(item);
1622     QTRY_COMPARE(item->x(), 0.0);
1623     QCOMPARE(item->height(), listview->height());
1624
1625     QSGText *text = findItem<QSGText>(contentItem, "text1");
1626     QTRY_VERIFY(text);
1627     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
1628
1629     listview->setCurrentIndex(2);
1630
1631     item = findItem<QSGItem>(contentItem, "item3");
1632     QTRY_VERIFY(item);
1633     QTRY_COMPARE(item->x(), 480.0);
1634
1635     text = findItem<QSGText>(contentItem, "text3");
1636     QTRY_VERIFY(text);
1637     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
1638
1639     delete canvas;
1640 }
1641
1642 void tst_QSGListView::cacheBuffer()
1643 {
1644     QSGView *canvas = createView();
1645
1646     TestModel model;
1647     for (int i = 0; i < 30; i++)
1648         model.addItem("Item" + QString::number(i), "");
1649
1650     QDeclarativeContext *ctxt = canvas->rootContext();
1651     ctxt->setContextProperty("testModel", &model);
1652
1653     TestObject *testObject = new TestObject;
1654     ctxt->setContextProperty("testObject", testObject);
1655
1656     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
1657     qApp->processEvents();
1658
1659     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1660     QTRY_VERIFY(listview != 0);
1661
1662     QSGItem *contentItem = listview->contentItem();
1663     QTRY_VERIFY(contentItem != 0);
1664     QTRY_VERIFY(listview->delegate() != 0);
1665     QTRY_VERIFY(listview->model() != 0);
1666     QTRY_VERIFY(listview->highlight() != 0);
1667
1668     // Confirm items positioned correctly
1669     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1670     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1671         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1672         if (!item) qWarning() << "Item" << i << "not found";
1673         QTRY_VERIFY(item);
1674         QTRY_VERIFY(item->y() == i*20);
1675     }
1676
1677     testObject->setCacheBuffer(400);
1678     QTRY_VERIFY(listview->cacheBuffer() == 400);
1679
1680     int newItemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1681     QTRY_VERIFY(newItemCount > itemCount);
1682
1683     // Confirm items positioned correctly
1684     for (int i = 0; i < model.count() && i < newItemCount; ++i) {
1685         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1686         if (!item) qWarning() << "Item" << i << "not found";
1687         QTRY_VERIFY(item);
1688         QTRY_VERIFY(item->y() == i*20);
1689     }
1690
1691     delete canvas;
1692     delete testObject;
1693 }
1694
1695 void tst_QSGListView::positionViewAtIndex()
1696 {
1697     QSGView *canvas = createView();
1698
1699     TestModel model;
1700     for (int i = 0; i < 40; i++)
1701         model.addItem("Item" + QString::number(i), "");
1702
1703     QDeclarativeContext *ctxt = canvas->rootContext();
1704     ctxt->setContextProperty("testModel", &model);
1705
1706     TestObject *testObject = new TestObject;
1707     ctxt->setContextProperty("testObject", testObject);
1708
1709     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
1710     qApp->processEvents();
1711
1712     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1713     QTRY_VERIFY(listview != 0);
1714
1715     QSGItem *contentItem = listview->contentItem();
1716     QTRY_VERIFY(contentItem != 0);
1717
1718     // Confirm items positioned correctly
1719     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1720     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1721         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1722         if (!item) qWarning() << "Item" << i << "not found";
1723         QTRY_VERIFY(item);
1724         QTRY_COMPARE(item->y(), i*20.);
1725     }
1726
1727     // Position on a currently visible item
1728     listview->positionViewAtIndex(3, QSGListView::Beginning);
1729     QTRY_COMPARE(listview->contentY(), 60.);
1730
1731     // Confirm items positioned correctly
1732     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1733     for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) {
1734         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1735         if (!item) qWarning() << "Item" << i << "not found";
1736         QTRY_VERIFY(item);
1737         QTRY_COMPARE(item->y(), i*20.);
1738     }
1739
1740     // Position on an item beyond the visible items
1741     listview->positionViewAtIndex(22, QSGListView::Beginning);
1742     QTRY_COMPARE(listview->contentY(), 440.);
1743
1744     // Confirm items positioned correctly
1745     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1746     for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) {
1747         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1748         if (!item) qWarning() << "Item" << i << "not found";
1749         QTRY_VERIFY(item);
1750         QTRY_COMPARE(item->y(), i*20.);
1751     }
1752
1753     // Position on an item that would leave empty space if positioned at the top
1754     listview->positionViewAtIndex(28, QSGListView::Beginning);
1755     QTRY_COMPARE(listview->contentY(), 480.);
1756
1757     // Confirm items positioned correctly
1758     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1759     for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) {
1760         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1761         if (!item) qWarning() << "Item" << i << "not found";
1762         QTRY_VERIFY(item);
1763         QTRY_COMPARE(item->y(), i*20.);
1764     }
1765
1766     // Position at the beginning again
1767     listview->positionViewAtIndex(0, QSGListView::Beginning);
1768     QTRY_COMPARE(listview->contentY(), 0.);
1769
1770     // Confirm items positioned correctly
1771     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1772     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
1773         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1774         if (!item) qWarning() << "Item" << i << "not found";
1775         QTRY_VERIFY(item);
1776         QTRY_COMPARE(item->y(), i*20.);
1777     }
1778
1779     // Position at End using last index
1780     listview->positionViewAtIndex(model.count()-1, QSGListView::End);
1781     QTRY_COMPARE(listview->contentY(), 480.);
1782
1783     // Confirm items positioned correctly
1784     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1785     for (int i = 24; i < model.count(); ++i) {
1786         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1787         if (!item) qWarning() << "Item" << i << "not found";
1788         QTRY_VERIFY(item);
1789         QTRY_COMPARE(item->y(), i*20.);
1790     }
1791
1792     // Position at End
1793     listview->positionViewAtIndex(20, QSGListView::End);
1794     QTRY_COMPARE(listview->contentY(), 100.);
1795
1796     // Position in Center
1797     listview->positionViewAtIndex(15, QSGListView::Center);
1798     QTRY_COMPARE(listview->contentY(), 150.);
1799
1800     // Ensure at least partially visible
1801     listview->positionViewAtIndex(15, QSGListView::Visible);
1802     QTRY_COMPARE(listview->contentY(), 150.);
1803
1804     listview->setContentY(302);
1805     listview->positionViewAtIndex(15, QSGListView::Visible);
1806     QTRY_COMPARE(listview->contentY(), 302.);
1807
1808     listview->setContentY(320);
1809     listview->positionViewAtIndex(15, QSGListView::Visible);
1810     QTRY_COMPARE(listview->contentY(), 300.);
1811
1812     listview->setContentY(85);
1813     listview->positionViewAtIndex(20, QSGListView::Visible);
1814     QTRY_COMPARE(listview->contentY(), 85.);
1815
1816     listview->setContentY(75);
1817     listview->positionViewAtIndex(20, QSGListView::Visible);
1818     QTRY_COMPARE(listview->contentY(), 100.);
1819
1820     // Ensure completely visible
1821     listview->setContentY(120);
1822     listview->positionViewAtIndex(20, QSGListView::Contain);
1823     QTRY_COMPARE(listview->contentY(), 120.);
1824
1825     listview->setContentY(302);
1826     listview->positionViewAtIndex(15, QSGListView::Contain);
1827     QTRY_COMPARE(listview->contentY(), 300.);
1828
1829     listview->setContentY(85);
1830     listview->positionViewAtIndex(20, QSGListView::Contain);
1831     QTRY_COMPARE(listview->contentY(), 100.);
1832
1833     // positionAtBeginnging
1834     listview->positionViewAtBeginning();
1835     QTRY_COMPARE(listview->contentY(), 0.);
1836
1837     listview->setContentY(80);
1838     canvas->rootObject()->setProperty("showHeader", true);
1839     listview->positionViewAtBeginning();
1840     QTRY_COMPARE(listview->contentY(), -30.);
1841
1842     // positionAtEnd
1843     listview->positionViewAtEnd();
1844     QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
1845
1846     listview->setContentY(80);
1847     canvas->rootObject()->setProperty("showFooter", true);
1848     listview->positionViewAtEnd();
1849     QTRY_COMPARE(listview->contentY(), 510.);
1850
1851     // set current item to outside visible view, position at beginning
1852     // and ensure highlight moves to current item
1853     listview->setCurrentIndex(1);
1854     listview->positionViewAtBeginning();
1855     QTRY_COMPARE(listview->contentY(), -30.);
1856     QVERIFY(listview->highlightItem());
1857     QCOMPARE(listview->highlightItem()->y(), 20.);
1858
1859     delete canvas;
1860     delete testObject;
1861 }
1862
1863 void tst_QSGListView::resetModel()
1864 {
1865     QSGView *canvas = createView();
1866
1867     QStringList strings;
1868     strings << "one" << "two" << "three";
1869     QStringListModel model(strings);
1870
1871     QDeclarativeContext *ctxt = canvas->rootContext();
1872     ctxt->setContextProperty("testModel", &model);
1873
1874     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/displaylist.qml"));
1875     qApp->processEvents();
1876
1877     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1878     QTRY_VERIFY(listview != 0);
1879
1880     QSGItem *contentItem = listview->contentItem();
1881     QTRY_VERIFY(contentItem != 0);
1882
1883     QTRY_COMPARE(listview->count(), model.rowCount());
1884
1885     for (int i = 0; i < model.rowCount(); ++i) {
1886         QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
1887         QTRY_VERIFY(display != 0);
1888         QTRY_COMPARE(display->text(), strings.at(i));
1889     }
1890
1891     strings.clear();
1892     strings << "four" << "five" << "six" << "seven";
1893     model.setStringList(strings);
1894
1895     QTRY_COMPARE(listview->count(), model.rowCount());
1896
1897     for (int i = 0; i < model.rowCount(); ++i) {
1898         QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
1899         QTRY_VERIFY(display != 0);
1900         QTRY_COMPARE(display->text(), strings.at(i));
1901     }
1902
1903     delete canvas;
1904 }
1905
1906 void tst_QSGListView::propertyChanges()
1907 {
1908     QSGView *canvas = createView();
1909     QTRY_VERIFY(canvas);
1910     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/propertychangestest.qml"));
1911
1912     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
1913     QTRY_VERIFY(listView);
1914
1915     QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
1916     QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
1917     QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
1918     QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
1919     QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
1920     QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
1921     QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
1922
1923     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
1924     QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
1925     QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
1926     QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::ApplyRange);
1927     QTRY_COMPARE(listView->isWrapEnabled(), true);
1928     QTRY_COMPARE(listView->cacheBuffer(), 10);
1929     QTRY_COMPARE(listView->snapMode(), QSGListView::SnapToItem);
1930
1931     listView->setHighlightFollowsCurrentItem(false);
1932     listView->setPreferredHighlightBegin(1.0);
1933     listView->setPreferredHighlightEnd(1.0);
1934     listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
1935     listView->setWrapEnabled(false);
1936     listView->setCacheBuffer(3);
1937     listView->setSnapMode(QSGListView::SnapOneItem);
1938
1939     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
1940     QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
1941     QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
1942     QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
1943     QTRY_COMPARE(listView->isWrapEnabled(), false);
1944     QTRY_COMPARE(listView->cacheBuffer(), 3);
1945     QTRY_COMPARE(listView->snapMode(), QSGListView::SnapOneItem);
1946
1947     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
1948     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
1949     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
1950     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
1951     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
1952     QTRY_COMPARE(cacheBufferSpy.count(),1);
1953     QTRY_COMPARE(snapModeSpy.count(),1);
1954
1955     listView->setHighlightFollowsCurrentItem(false);
1956     listView->setPreferredHighlightBegin(1.0);
1957     listView->setPreferredHighlightEnd(1.0);
1958     listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
1959     listView->setWrapEnabled(false);
1960     listView->setCacheBuffer(3);
1961     listView->setSnapMode(QSGListView::SnapOneItem);
1962
1963     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
1964     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
1965     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
1966     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
1967     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
1968     QTRY_COMPARE(cacheBufferSpy.count(),1);
1969     QTRY_COMPARE(snapModeSpy.count(),1);
1970
1971     delete canvas;
1972 }
1973
1974 void tst_QSGListView::componentChanges()
1975 {
1976     QSGView *canvas = createView();
1977     QTRY_VERIFY(canvas);
1978     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/propertychangestest.qml"));
1979
1980     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
1981     QTRY_VERIFY(listView);
1982
1983     QDeclarativeComponent component(canvas->engine());
1984     component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
1985
1986     QDeclarativeComponent delegateComponent(canvas->engine());
1987     delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
1988
1989     QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
1990     QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
1991     QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
1992     QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
1993
1994     listView->setHighlight(&component);
1995     listView->setHeader(&component);
1996     listView->setFooter(&component);
1997     listView->setDelegate(&delegateComponent);
1998
1999     QTRY_COMPARE(listView->highlight(), &component);
2000     QTRY_COMPARE(listView->header(), &component);
2001     QTRY_COMPARE(listView->footer(), &component);
2002     QTRY_COMPARE(listView->delegate(), &delegateComponent);
2003
2004     QTRY_COMPARE(highlightSpy.count(),1);
2005     QTRY_COMPARE(delegateSpy.count(),1);
2006     QTRY_COMPARE(headerSpy.count(),1);
2007     QTRY_COMPARE(footerSpy.count(),1);
2008
2009     listView->setHighlight(&component);
2010     listView->setHeader(&component);
2011     listView->setFooter(&component);
2012     listView->setDelegate(&delegateComponent);
2013
2014     QTRY_COMPARE(highlightSpy.count(),1);
2015     QTRY_COMPARE(delegateSpy.count(),1);
2016     QTRY_COMPARE(headerSpy.count(),1);
2017     QTRY_COMPARE(footerSpy.count(),1);
2018
2019     delete canvas;
2020 }
2021
2022 void tst_QSGListView::modelChanges()
2023 {
2024     QSGView *canvas = createView();
2025     QTRY_VERIFY(canvas);
2026     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/propertychangestest.qml"));
2027
2028     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2029     QTRY_VERIFY(listView);
2030
2031     QDeclarativeListModel *alternateModel = canvas->rootObject()->findChild<QDeclarativeListModel*>("alternateModel");
2032     QTRY_VERIFY(alternateModel);
2033     QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
2034     QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
2035
2036     listView->setModel(modelVariant);
2037     QTRY_COMPARE(listView->model(), modelVariant);
2038     QTRY_COMPARE(modelSpy.count(),1);
2039
2040     listView->setModel(modelVariant);
2041     QTRY_COMPARE(modelSpy.count(),1);
2042
2043     listView->setModel(QVariant());
2044     QTRY_COMPARE(modelSpy.count(),2);
2045
2046     delete canvas;
2047 }
2048
2049 void tst_QSGListView::QTBUG_9791()
2050 {
2051     QSGView *canvas = createView();
2052
2053     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/strictlyenforcerange.qml"));
2054     qApp->processEvents();
2055
2056     QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2057     QTRY_VERIFY(listview != 0);
2058
2059     QSGItem *contentItem = listview->contentItem();
2060     QTRY_VERIFY(contentItem != 0);
2061     QTRY_VERIFY(listview->delegate() != 0);
2062     QTRY_VERIFY(listview->model() != 0);
2063
2064     QMetaObject::invokeMethod(listview, "fillModel");
2065     qApp->processEvents();
2066
2067     // Confirm items positioned correctly
2068     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2069     QCOMPARE(itemCount, 3);
2070
2071     for (int i = 0; i < itemCount; ++i) {
2072         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2073         if (!item) qWarning() << "Item" << i << "not found";
2074         QTRY_VERIFY(item);
2075         QTRY_COMPARE(item->x(), i*300.0);
2076     }
2077
2078     // check that view is positioned correctly
2079     QTRY_COMPARE(listview->contentX(), 590.0);
2080
2081     delete canvas;
2082 }
2083
2084 void tst_QSGListView::manualHighlight()
2085 {
2086     QSGView *canvas = new QSGView(0);
2087     canvas->setFixedSize(240,320);
2088
2089     QString filename(SRCDIR "/data/manual-highlight.qml");
2090     canvas->setSource(QUrl::fromLocalFile(filename));
2091
2092     qApp->processEvents();
2093
2094     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2095     QTRY_VERIFY(listview != 0);
2096
2097     QSGItem *contentItem = listview->contentItem();
2098     QTRY_VERIFY(contentItem != 0);
2099
2100     QTRY_COMPARE(listview->currentIndex(), 0);
2101     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
2102     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2103
2104     listview->setCurrentIndex(2);
2105
2106     QTRY_COMPARE(listview->currentIndex(), 2);
2107     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2108     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2109
2110     // QTBUG-15972
2111     listview->positionViewAtIndex(3, QSGListView::Contain);
2112
2113     QTRY_COMPARE(listview->currentIndex(), 2);
2114     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2115     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2116
2117     delete canvas;
2118 }
2119
2120 void tst_QSGListView::QTBUG_11105()
2121 {
2122     QSGView *canvas = createView();
2123
2124     TestModel model;
2125     for (int i = 0; i < 30; i++)
2126         model.addItem("Item" + QString::number(i), "");
2127
2128     QDeclarativeContext *ctxt = canvas->rootContext();
2129     ctxt->setContextProperty("testModel", &model);
2130
2131     TestObject *testObject = new TestObject;
2132     ctxt->setContextProperty("testObject", testObject);
2133
2134     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2135     qApp->processEvents();
2136
2137     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2138     QTRY_VERIFY(listview != 0);
2139
2140     QSGItem *contentItem = listview->contentItem();
2141     QTRY_VERIFY(contentItem != 0);
2142
2143     // Confirm items positioned correctly
2144     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2145     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2146         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2147         if (!item) qWarning() << "Item" << i << "not found";
2148         QTRY_VERIFY(item);
2149         QTRY_VERIFY(item->y() == i*20);
2150     }
2151
2152     listview->positionViewAtIndex(20, QSGListView::Beginning);
2153     QCOMPARE(listview->contentY(), 280.);
2154
2155     TestModel model2;
2156     for (int i = 0; i < 5; i++)
2157         model2.addItem("Item" + QString::number(i), "");
2158
2159     ctxt->setContextProperty("testModel", &model2);
2160
2161     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2162     QCOMPARE(itemCount, 5);
2163
2164     delete canvas;
2165     delete testObject;
2166 }
2167
2168 void tst_QSGListView::header()
2169 {
2170     QFETCH(QSGListView::Orientation, orientation);
2171     QFETCH(Qt::LayoutDirection, layoutDirection);
2172     QFETCH(QPointF, initialHeaderPos);
2173     QFETCH(QPointF, firstDelegatePos);
2174     QFETCH(QPointF, initialContentPos);
2175     QFETCH(QPointF, changedHeaderPos);
2176     QFETCH(QPointF, changedContentPos);
2177
2178     QSGView *canvas = createView();
2179
2180     TestModel model;
2181     for (int i = 0; i < 30; i++)
2182         model.addItem("Item" + QString::number(i), "");
2183
2184     QDeclarativeContext *ctxt = canvas->rootContext();
2185     ctxt->setContextProperty("testModel", &model);
2186
2187     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header.qml"));
2188     qApp->processEvents();
2189
2190     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2191     QTRY_VERIFY(listview != 0);
2192     listview->setOrientation(orientation);
2193     listview->setLayoutDirection(layoutDirection);
2194
2195     QSGItem *contentItem = listview->contentItem();
2196     QTRY_VERIFY(contentItem != 0);
2197
2198     QSGText *header = findItem<QSGText>(contentItem, "header");
2199     QVERIFY(header);
2200
2201     QVERIFY(header == listview->headerItem());
2202
2203     QCOMPARE(header->width(), 100.);
2204     QCOMPARE(header->height(), 30.);
2205     QCOMPARE(header->pos(), initialHeaderPos);
2206     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2207
2208     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2209     QVERIFY(item);
2210     QCOMPARE(item->pos(), firstDelegatePos);
2211
2212     model.clear();
2213     QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
2214
2215     for (int i = 0; i < 30; i++)
2216         model.addItem("Item" + QString::number(i), "");
2217
2218     QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
2219     QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
2220
2221     QCOMPARE(headerItemSpy.count(), 1);
2222
2223     header = findItem<QSGText>(contentItem, "header");
2224     QVERIFY(!header);
2225     header = findItem<QSGText>(contentItem, "header2");
2226     QVERIFY(header);
2227
2228     QVERIFY(header == listview->headerItem());
2229
2230     QCOMPARE(header->pos(), changedHeaderPos);
2231     QCOMPARE(header->width(), 50.);
2232     QCOMPARE(header->height(), 20.);
2233     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2234     QCOMPARE(item->pos(), firstDelegatePos);
2235
2236     delete canvas;
2237 }
2238
2239 void tst_QSGListView::header_data()
2240 {
2241     QTest::addColumn<QSGListView::Orientation>("orientation");
2242     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2243     QTest::addColumn<QPointF>("initialHeaderPos");
2244     QTest::addColumn<QPointF>("changedHeaderPos");
2245     QTest::addColumn<QPointF>("initialContentPos");
2246     QTest::addColumn<QPointF>("changedContentPos");
2247     QTest::addColumn<QPointF>("firstDelegatePos");
2248
2249     // header1 = 100 x 30
2250     // header2 = 50 x 20
2251     // delegates = 240 x 20
2252     // view width = 240
2253
2254     // header above items, top left
2255     QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight
2256         << QPointF(0, -30)
2257         << QPointF(0, -20)
2258         << QPointF(0, -30)
2259         << QPointF(0, -20)
2260         << QPointF(0, 0);
2261
2262     // header above items, top right
2263     QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2264         << QPointF(0, -30)
2265         << QPointF(0, -20)
2266         << QPointF(0, -30)
2267         << QPointF(0, -20)
2268         << QPointF(0, 0);
2269
2270     // header to left of items
2271     QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2272         << QPointF(-100, 0)
2273         << QPointF(-50, 0)
2274         << QPointF(-100, 0)
2275         << QPointF(-50, 0)
2276         << QPointF(0, 0);
2277
2278     // header to right of items
2279     QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2280         << QPointF(0, 0)
2281         << QPointF(0, 0)
2282         << QPointF(-240 + 100, 0)
2283         << QPointF(-240 + 50, 0)
2284         << QPointF(-240, 0);
2285 }
2286
2287 void tst_QSGListView::header_delayItemCreation()
2288 {
2289     QSGView *canvas = createView();
2290
2291     TestModel model;
2292
2293     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header1.qml"));
2294     qApp->processEvents();
2295
2296     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2297     QTRY_VERIFY(listview != 0);
2298
2299     QSGItem *contentItem = listview->contentItem();
2300     QTRY_VERIFY(contentItem != 0);
2301
2302     QSGText *header = findItem<QSGText>(contentItem, "header");
2303     QVERIFY(header);
2304     QCOMPARE(header->y(), -header->height());
2305
2306     QCOMPARE(listview->contentY(), -header->height());
2307
2308     model.clear();
2309     QTRY_COMPARE(header->y(), -header->height());
2310
2311     delete canvas;
2312 }
2313
2314 void tst_QSGListView::footer()
2315 {
2316     QFETCH(QSGListView::Orientation, orientation);
2317     QFETCH(Qt::LayoutDirection, layoutDirection);
2318     QFETCH(QPointF, initialFooterPos);
2319     QFETCH(QPointF, firstDelegatePos);
2320     QFETCH(QPointF, initialContentPos);
2321     QFETCH(QPointF, changedFooterPos);
2322     QFETCH(QPointF, changedContentPos);
2323
2324     QSGView *canvas = createView();
2325
2326     TestModel model;
2327     for (int i = 0; i < 3; i++)
2328         model.addItem("Item" + QString::number(i), "");
2329
2330     QDeclarativeContext *ctxt = canvas->rootContext();
2331     ctxt->setContextProperty("testModel", &model);
2332
2333     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/footer.qml"));
2334     canvas->show();
2335     qApp->processEvents();
2336
2337     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2338     QTRY_VERIFY(listview != 0);
2339     listview->setOrientation(orientation);
2340     listview->setLayoutDirection(layoutDirection);
2341
2342     QSGItem *contentItem = listview->contentItem();
2343     QTRY_VERIFY(contentItem != 0);
2344
2345     QSGText *footer = findItem<QSGText>(contentItem, "footer");
2346     QVERIFY(footer);
2347
2348     QVERIFY(footer == listview->footerItem());
2349
2350     QCOMPARE(footer->pos(), initialFooterPos);
2351     QCOMPARE(footer->width(), 100.);
2352     QCOMPARE(footer->height(), 30.);
2353     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2354
2355     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2356     QVERIFY(item);
2357     QCOMPARE(item->pos(), firstDelegatePos);
2358
2359     // remove one item
2360     model.removeItem(1);
2361
2362     if (orientation == QSGListView::Vertical) {
2363         QTRY_COMPARE(footer->y(), initialFooterPos.y() - 20);   // delegate height = 20
2364     } else {
2365         QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
2366                 initialFooterPos.x() - 40 : initialFooterPos.x() + 40);  // delegate width = 40
2367     }
2368
2369     // remove all items
2370     model.clear();
2371
2372     QPointF posWhenNoItems(0, 0);
2373     if (orientation == QSGListView::Horizontal && layoutDirection == Qt::RightToLeft)
2374         posWhenNoItems.setX(-100);
2375     QTRY_COMPARE(footer->pos(), posWhenNoItems);
2376
2377     // if header is present, it's at a negative pos, so the footer should not move
2378     canvas->rootObject()->setProperty("showHeader", true);
2379     QTRY_COMPARE(footer->pos(), posWhenNoItems);
2380     canvas->rootObject()->setProperty("showHeader", false);
2381
2382     // add 30 items
2383     for (int i = 0; i < 30; i++)
2384         model.addItem("Item" + QString::number(i), "");
2385
2386     QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
2387     QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
2388
2389     QCOMPARE(footerItemSpy.count(), 1);
2390
2391     footer = findItem<QSGText>(contentItem, "footer");
2392     QVERIFY(!footer);
2393     footer = findItem<QSGText>(contentItem, "footer2");
2394     QVERIFY(footer);
2395
2396     QVERIFY(footer == listview->footerItem());
2397
2398     QCOMPARE(footer->pos(), changedFooterPos);
2399     QCOMPARE(footer->width(), 50.);
2400     QCOMPARE(footer->height(), 20.);
2401     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2402
2403     item = findItem<QSGItem>(contentItem, "wrapper", 0);
2404     QVERIFY(item);
2405     QCOMPARE(item->pos(), firstDelegatePos);
2406
2407     delete canvas;
2408 }
2409
2410 void tst_QSGListView::footer_data()
2411 {
2412     QTest::addColumn<QSGListView::Orientation>("orientation");
2413     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2414     QTest::addColumn<QPointF>("initialFooterPos");
2415     QTest::addColumn<QPointF>("changedFooterPos");
2416     QTest::addColumn<QPointF>("initialContentPos");
2417     QTest::addColumn<QPointF>("changedContentPos");
2418     QTest::addColumn<QPointF>("firstDelegatePos");
2419
2420     // footer1 = 100 x 30
2421     // footer2 = 100 x 20
2422     // delegates = 40 x 20
2423     // view width = 240
2424
2425     // footer below items, bottom left
2426     QTest::newRow("vertical, layout left to right") << QSGListView::Vertical << Qt::LeftToRight
2427         << QPointF(0, 3 * 20)
2428         << QPointF(0, 30 * 20)  // added 30 items
2429         << QPointF(0, 0)
2430         << QPointF(0, 0)
2431         << QPointF(0, 0);
2432
2433     // footer below items, bottom right
2434     QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2435         << QPointF(0, 3 * 20)
2436         << QPointF(0, 30 * 20)
2437         << QPointF(0, 0)
2438         << QPointF(0, 0)
2439         << QPointF(0, 0);
2440
2441     // footer to right of items
2442     QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2443         << QPointF(40 * 3, 0)
2444         << QPointF(40 * 30, 0)
2445         << QPointF(0, 0)
2446         << QPointF(0, 0)
2447         << QPointF(0, 0);
2448
2449     // footer to left of items
2450     QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2451         << QPointF(-(40 * 3) - 100, 0)
2452         << QPointF(-(40 * 30) - 50, 0)     // 50 = new footer width
2453         << QPointF(-240, 0)
2454         << QPointF(-240, 0)
2455         << QPointF(-40, 0);
2456 }
2457
2458 class LVAccessor : public QSGListView
2459 {
2460 public:
2461     qreal minY() const { return minYExtent(); }
2462     qreal maxY() const { return maxYExtent(); }
2463     qreal minX() const { return minXExtent(); }
2464     qreal maxX() const { return maxXExtent(); }
2465 };
2466
2467 void tst_QSGListView::headerFooter()
2468 {
2469     {
2470         // Vertical
2471         QSGView *canvas = createView();
2472
2473         TestModel model;
2474         QDeclarativeContext *ctxt = canvas->rootContext();
2475         ctxt->setContextProperty("testModel", &model);
2476
2477         canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/headerfooter.qml"));
2478         qApp->processEvents();
2479
2480         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2481         QTRY_VERIFY(listview != 0);
2482
2483         QSGItem *contentItem = listview->contentItem();
2484         QTRY_VERIFY(contentItem != 0);
2485
2486         QSGItem *header = findItem<QSGItem>(contentItem, "header");
2487         QVERIFY(header);
2488         QCOMPARE(header->y(), -header->height());
2489
2490         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
2491         QVERIFY(footer);
2492         QCOMPARE(footer->y(), 0.);
2493
2494         QVERIFY(static_cast<LVAccessor*>(listview)->minY() == 0);
2495         QVERIFY(static_cast<LVAccessor*>(listview)->maxY() == 0);
2496
2497         delete canvas;
2498     }
2499     {
2500         // Horizontal
2501         QSGView *canvas = createView();
2502
2503         TestModel model;
2504         QDeclarativeContext *ctxt = canvas->rootContext();
2505         ctxt->setContextProperty("testModel", &model);
2506
2507         canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/headerfooter.qml"));
2508         canvas->rootObject()->setProperty("horizontal", true);
2509         qApp->processEvents();
2510
2511         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2512         QTRY_VERIFY(listview != 0);
2513
2514         QSGItem *contentItem = listview->contentItem();
2515         QTRY_VERIFY(contentItem != 0);
2516
2517         QSGItem *header = findItem<QSGItem>(contentItem, "header");
2518         QVERIFY(header);
2519         QCOMPARE(header->x(), -header->width());
2520
2521         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
2522         QVERIFY(footer);
2523         QCOMPARE(footer->x(), 0.);
2524
2525         QVERIFY(static_cast<LVAccessor*>(listview)->minX() == 0);
2526         QVERIFY(static_cast<LVAccessor*>(listview)->maxX() == 0);
2527
2528         delete canvas;
2529     }
2530     {
2531         // Horizontal RTL
2532         QSGView *canvas = createView();
2533
2534         TestModel model;
2535         QDeclarativeContext *ctxt = canvas->rootContext();
2536         ctxt->setContextProperty("testModel", &model);
2537
2538         canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/headerfooter.qml"));
2539         canvas->rootObject()->setProperty("horizontal", true);
2540         canvas->rootObject()->setProperty("rtl", true);
2541         qApp->processEvents();
2542
2543         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2544         QTRY_VERIFY(listview != 0);
2545
2546         QSGItem *contentItem = listview->contentItem();
2547         QTRY_VERIFY(contentItem != 0);
2548
2549         QSGItem *header = findItem<QSGItem>(contentItem, "header");
2550         QVERIFY(header);
2551         QCOMPARE(header->x(), 0.);
2552
2553         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
2554         QVERIFY(footer);
2555         QCOMPARE(footer->x(), -footer->width());
2556
2557         QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), 240.);
2558         QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), 240.);
2559
2560         delete canvas;
2561     }
2562 }
2563
2564 void tst_QSGListView::resizeView()
2565 {
2566     QSGView *canvas = createView();
2567
2568     TestModel model;
2569     for (int i = 0; i < 40; i++)
2570         model.addItem("Item" + QString::number(i), "");
2571
2572     QDeclarativeContext *ctxt = canvas->rootContext();
2573     ctxt->setContextProperty("testModel", &model);
2574
2575     TestObject *testObject = new TestObject;
2576     ctxt->setContextProperty("testObject", testObject);
2577
2578     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2579     qApp->processEvents();
2580
2581     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2582     QTRY_VERIFY(listview != 0);
2583
2584     QSGItem *contentItem = listview->contentItem();
2585     QTRY_VERIFY(contentItem != 0);
2586
2587     // Confirm items positioned correctly
2588     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2589     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2590         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2591         if (!item) qWarning() << "Item" << i << "not found";
2592         QTRY_VERIFY(item);
2593         QTRY_COMPARE(item->y(), i*20.);
2594     }
2595
2596     QVariant heightRatio;
2597     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
2598     QCOMPARE(heightRatio.toReal(), 0.4);
2599
2600     listview->setHeight(200);
2601
2602     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
2603     QCOMPARE(heightRatio.toReal(), 0.25);
2604
2605     delete canvas;
2606     delete testObject;
2607 }
2608
2609 void tst_QSGListView::sizeLessThan1()
2610 {
2611     QSGView *canvas = createView();
2612
2613     TestModel model;
2614     for (int i = 0; i < 30; i++)
2615         model.addItem("Item" + QString::number(i), "");
2616
2617     QDeclarativeContext *ctxt = canvas->rootContext();
2618     ctxt->setContextProperty("testModel", &model);
2619
2620     TestObject *testObject = new TestObject;
2621     ctxt->setContextProperty("testObject", testObject);
2622
2623     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/sizelessthan1.qml"));
2624     qApp->processEvents();
2625
2626     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2627     QTRY_VERIFY(listview != 0);
2628
2629     QSGItem *contentItem = listview->contentItem();
2630     QTRY_VERIFY(contentItem != 0);
2631
2632     // Confirm items positioned correctly
2633     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2634     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2635         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2636         if (!item) qWarning() << "Item" << i << "not found";
2637         QTRY_VERIFY(item);
2638         QTRY_COMPARE(item->y(), i*0.5);
2639     }
2640
2641     delete canvas;
2642     delete testObject;
2643 }
2644
2645 void tst_QSGListView::QTBUG_14821()
2646 {
2647     QSGView *canvas = createView();
2648
2649     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/qtbug14821.qml"));
2650     qApp->processEvents();
2651
2652     QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2653     QVERIFY(listview != 0);
2654
2655     QSGItem *contentItem = listview->contentItem();
2656     QVERIFY(contentItem != 0);
2657
2658     listview->decrementCurrentIndex();
2659     QCOMPARE(listview->currentIndex(), 99);
2660
2661     listview->incrementCurrentIndex();
2662     QCOMPARE(listview->currentIndex(), 0);
2663
2664     delete canvas;
2665 }
2666
2667 void tst_QSGListView::resizeDelegate()
2668 {
2669     QSGView *canvas = createView();
2670     canvas->show();
2671
2672     QStringList strings;
2673     for (int i = 0; i < 30; ++i)
2674         strings << QString::number(i);
2675     QStringListModel model(strings);
2676
2677     QDeclarativeContext *ctxt = canvas->rootContext();
2678     ctxt->setContextProperty("testModel", &model);
2679
2680     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/displaylist.qml"));
2681     qApp->processEvents();
2682
2683     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2684     QVERIFY(listview != 0);
2685
2686     QSGItem *contentItem = listview->contentItem();
2687     QVERIFY(contentItem != 0);
2688
2689     QCOMPARE(listview->count(), model.rowCount());
2690
2691     listview->setCurrentIndex(25);
2692     listview->setContentY(0);
2693
2694     for (int i = 0; i < 16; ++i) {
2695         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2696         QVERIFY(item != 0);
2697         QCOMPARE(item->y(), i*20.0);
2698     }
2699
2700     QCOMPARE(listview->currentItem()->y(), 500.0);
2701     QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
2702
2703     canvas->rootObject()->setProperty("delegateHeight", 30);
2704     QTest::qWait(300);
2705
2706     for (int i = 0; i < 11; ++i) {
2707         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2708         QVERIFY(item != 0);
2709         QTRY_COMPARE(item->y(), i*30.0);
2710     }
2711
2712     QTRY_COMPARE(listview->currentItem()->y(), 750.0);
2713     QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
2714
2715     listview->setCurrentIndex(1);
2716     listview->positionViewAtIndex(25, QSGListView::Beginning);
2717     listview->positionViewAtIndex(5, QSGListView::Beginning);
2718
2719     for (int i = 5; i < 16; ++i) {
2720         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2721         QVERIFY(item != 0);
2722         QCOMPARE(item->y(), i*30.0);
2723     }
2724
2725     QTRY_COMPARE(listview->currentItem()->y(), 30.0);
2726     QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
2727
2728     canvas->rootObject()->setProperty("delegateHeight", 20);
2729     QTest::qWait(300);
2730
2731     for (int i = 5; i < 11; ++i) {
2732         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2733         QVERIFY(item != 0);
2734         QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
2735     }
2736
2737     QTRY_COMPARE(listview->currentItem()->y(), 70.0);
2738     QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
2739
2740     delete canvas;
2741 }
2742
2743 void tst_QSGListView::resizeFirstDelegate()
2744 {
2745     // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
2746     // and other delegates have height > 0
2747
2748     QSGView *canvas = createView();
2749     canvas->show();
2750
2751     // bug only occurs when all items in the model are visible
2752     TestModel model;
2753     for (int i = 0; i < 10; i++)
2754         model.addItem("Item" + QString::number(i), "");
2755
2756     QDeclarativeContext *ctxt = canvas->rootContext();
2757     ctxt->setContextProperty("testModel", &model);
2758
2759     TestObject *testObject = new TestObject;
2760     ctxt->setContextProperty("testObject", testObject);
2761
2762     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2763     qApp->processEvents();
2764
2765     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2766     QVERIFY(listview != 0);
2767
2768     QSGItem *contentItem = listview->contentItem();
2769     QVERIFY(contentItem != 0);
2770
2771     QSGItem *item = 0;
2772     for (int i = 0; i < model.count(); ++i) {
2773         item = findItem<QSGItem>(contentItem, "wrapper", i);
2774         QVERIFY(item != 0);
2775         QCOMPARE(item->y(), i*20.0);
2776     }
2777
2778     item = findItem<QSGItem>(contentItem, "wrapper", 0);
2779     item->setHeight(0);
2780
2781     // check the content y has not jumped up and down
2782     QCOMPARE(listview->contentY(), 0.0);
2783     QSignalSpy spy(listview, SIGNAL(contentYChanged()));
2784     QTest::qWait(300);
2785     QCOMPARE(spy.count(), 0);
2786
2787     for (int i = 1; i < model.count(); ++i) {
2788         item = findItem<QSGItem>(contentItem, "wrapper", i);
2789         QVERIFY(item != 0);
2790         QTRY_COMPARE(item->y(), (i-1)*20.0);
2791     }
2792
2793     delete testObject;
2794     delete canvas;
2795 }
2796
2797 void tst_QSGListView::QTBUG_16037()
2798 {
2799     QSGView *canvas = createView();
2800     canvas->show();
2801
2802     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/qtbug16037.qml"));
2803     qApp->processEvents();
2804
2805     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "listview");
2806     QTRY_VERIFY(listview != 0);
2807
2808     QVERIFY(listview->contentHeight() <= 0.0);
2809
2810     QMetaObject::invokeMethod(canvas->rootObject(), "setModel");
2811
2812     QTRY_COMPARE(listview->contentHeight(), 80.0);
2813
2814     delete canvas;
2815 }
2816
2817 void tst_QSGListView::indexAt()
2818 {
2819     QSGView *canvas = createView();
2820
2821     TestModel model;
2822     for (int i = 0; i < 30; i++)
2823         model.addItem("Item" + QString::number(i), "");
2824
2825     QDeclarativeContext *ctxt = canvas->rootContext();
2826     ctxt->setContextProperty("testModel", &model);
2827
2828     TestObject *testObject = new TestObject;
2829     ctxt->setContextProperty("testObject", testObject);
2830
2831     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2832     qApp->processEvents();
2833
2834     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2835     QTRY_VERIFY(listview != 0);
2836
2837     QSGItem *contentItem = listview->contentItem();
2838     QTRY_VERIFY(contentItem != 0);
2839
2840     QCOMPARE(listview->indexAt(0,0), 0);
2841     QCOMPARE(listview->indexAt(0,19), 0);
2842     QCOMPARE(listview->indexAt(239,19), 0);
2843     QCOMPARE(listview->indexAt(0,20), 1);
2844     QCOMPARE(listview->indexAt(240,20), -1);
2845
2846     delete canvas;
2847     delete testObject;
2848 }
2849
2850 void tst_QSGListView::incrementalModel()
2851 {
2852     QSGView *canvas = createView();
2853
2854     IncrementalModel model;
2855     QDeclarativeContext *ctxt = canvas->rootContext();
2856     ctxt->setContextProperty("testModel", &model);
2857
2858     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/displaylist.qml"));
2859     qApp->processEvents();
2860
2861     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2862     QTRY_VERIFY(listview != 0);
2863
2864     QSGItem *contentItem = listview->contentItem();
2865     QTRY_VERIFY(contentItem != 0);
2866
2867     QTRY_COMPARE(listview->count(), 20);
2868
2869     listview->positionViewAtIndex(10, QSGListView::Beginning);
2870
2871     QTRY_COMPARE(listview->count(), 25);
2872
2873     delete canvas;
2874 }
2875
2876 void tst_QSGListView::onAdd()
2877 {
2878     QFETCH(int, initialItemCount);
2879     QFETCH(int, itemsToAdd);
2880
2881     const int delegateHeight = 10;
2882     TestModel2 model;
2883
2884     // these initial items should not trigger ListView.onAdd
2885     for (int i=0; i<initialItemCount; i++)
2886         model.addItem("dummy value", "dummy value");
2887
2888     QSGView *canvas = createView();
2889     canvas->setFixedSize(200, delegateHeight * (initialItemCount + itemsToAdd));
2890     QDeclarativeContext *ctxt = canvas->rootContext();
2891     ctxt->setContextProperty("testModel", &model);
2892     ctxt->setContextProperty("delegateHeight", delegateHeight);
2893     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/attachedSignals.qml"));
2894
2895     QObject *object = canvas->rootObject();
2896     object->setProperty("width", canvas->width());
2897     object->setProperty("height", canvas->height());
2898     qApp->processEvents();
2899
2900     QList<QPair<QString, QString> > items;
2901     for (int i=0; i<itemsToAdd; i++)
2902         items << qMakePair(QString("value %1").arg(i), QString::number(i));
2903     model.addItems(items);
2904
2905     qApp->processEvents();
2906
2907     QVariantList result = object->property("addedDelegates").toList();
2908     QCOMPARE(result.count(), items.count());
2909     for (int i=0; i<items.count(); i++)
2910         QCOMPARE(result[i].toString(), items[i].first);
2911
2912     delete canvas;
2913 }
2914
2915 void tst_QSGListView::onAdd_data()
2916 {
2917     QTest::addColumn<int>("initialItemCount");
2918     QTest::addColumn<int>("itemsToAdd");
2919
2920     QTest::newRow("0, add 1") << 0 << 1;
2921     QTest::newRow("0, add 2") << 0 << 2;
2922     QTest::newRow("0, add 10") << 0 << 10;
2923
2924     QTest::newRow("1, add 1") << 1 << 1;
2925     QTest::newRow("1, add 2") << 1 << 2;
2926     QTest::newRow("1, add 10") << 1 << 10;
2927
2928     QTest::newRow("5, add 1") << 5 << 1;
2929     QTest::newRow("5, add 2") << 5 << 2;
2930     QTest::newRow("5, add 10") << 5 << 10;
2931 }
2932
2933 void tst_QSGListView::onRemove()
2934 {
2935     QFETCH(int, initialItemCount);
2936     QFETCH(int, indexToRemove);
2937     QFETCH(int, removeCount);
2938
2939     const int delegateHeight = 10;
2940     TestModel2 model;
2941     for (int i=0; i<initialItemCount; i++)
2942         model.addItem(QString("value %1").arg(i), "dummy value");
2943
2944     QSGView *canvas = createView();
2945     QDeclarativeContext *ctxt = canvas->rootContext();
2946     ctxt->setContextProperty("testModel", &model);
2947     ctxt->setContextProperty("delegateHeight", delegateHeight);
2948     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/attachedSignals.qml"));
2949     QObject *object = canvas->rootObject();
2950
2951     qApp->processEvents();
2952
2953     model.removeItems(indexToRemove, removeCount);
2954     qApp->processEvents();
2955     QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
2956
2957     delete canvas;
2958 }
2959
2960 void tst_QSGListView::onRemove_data()
2961 {
2962     QTest::addColumn<int>("initialItemCount");
2963     QTest::addColumn<int>("indexToRemove");
2964     QTest::addColumn<int>("removeCount");
2965
2966     QTest::newRow("remove first") << 1 << 0 << 1;
2967     QTest::newRow("two items, remove first") << 2 << 0 << 1;
2968     QTest::newRow("two items, remove last") << 2 << 1 << 1;
2969     QTest::newRow("two items, remove all") << 2 << 0 << 2;
2970
2971     QTest::newRow("four items, remove first") << 4 << 0 << 1;
2972     QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
2973     QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
2974     QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
2975     QTest::newRow("four items, remove last") << 4 << 3 << 1;
2976     QTest::newRow("four items, remove all") << 4 << 0 << 4;
2977
2978     QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
2979     QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
2980     QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
2981 }
2982
2983 void tst_QSGListView::rightToLeft()
2984 {
2985     QSGView *canvas = createView();
2986     canvas->setFixedSize(640,320);
2987     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/rightToLeft.qml"));
2988     qApp->processEvents();
2989
2990     QVERIFY(canvas->rootObject() != 0);
2991     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
2992     QTRY_VERIFY(listview != 0);
2993
2994     QSGItem *contentItem = listview->contentItem();
2995     QTRY_VERIFY(contentItem != 0);
2996
2997     QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
2998     QTRY_VERIFY(model != 0);
2999
3000     QTRY_VERIFY(model->count() == 3);
3001     QTRY_COMPARE(listview->currentIndex(), 0);
3002
3003     // initial position at first item, right edge aligned
3004     QCOMPARE(listview->contentX(), -640.);
3005
3006     QSGItem *item = findItem<QSGItem>(contentItem, "item1");
3007     QTRY_VERIFY(item);
3008     QTRY_COMPARE(item->x(), -100.0);
3009     QCOMPARE(item->height(), listview->height());
3010
3011     QSGText *text = findItem<QSGText>(contentItem, "text1");
3012     QTRY_VERIFY(text);
3013     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
3014
3015     listview->setCurrentIndex(2);
3016
3017     item = findItem<QSGItem>(contentItem, "item3");
3018     QTRY_VERIFY(item);
3019     QTRY_COMPARE(item->x(), -540.0);
3020
3021     text = findItem<QSGText>(contentItem, "text3");
3022     QTRY_VERIFY(text);
3023     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
3024
3025     QCOMPARE(listview->contentX(), -640.);
3026
3027     // Ensure resizing maintains position relative to right edge
3028     qobject_cast<QSGItem*>(canvas->rootObject())->setWidth(600);
3029     QTRY_COMPARE(listview->contentX(), -600.);
3030
3031     delete canvas;
3032 }
3033
3034 void tst_QSGListView::test_mirroring()
3035 {
3036     QSGView *canvasA = createView();
3037     canvasA->setSource(QUrl::fromLocalFile(SRCDIR "/data/rightToLeft.qml"));
3038     QSGListView *listviewA = findItem<QSGListView>(canvasA->rootObject(), "view");
3039     QTRY_VERIFY(listviewA != 0);
3040
3041     QSGView *canvasB = createView();
3042     canvasB->setSource(QUrl::fromLocalFile(SRCDIR "/data/rightToLeft.qml"));
3043     QSGListView *listviewB = findItem<QSGListView>(canvasB->rootObject(), "view");
3044     QTRY_VERIFY(listviewA != 0);
3045     qApp->processEvents();
3046
3047     QList<QString> objectNames;
3048     objectNames << "item1" << "item2"; // << "item3"
3049
3050     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3051     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3052     QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
3053
3054     // LTR != RTL
3055     foreach(const QString objectName, objectNames)
3056         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3057
3058     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3059     listviewB->setProperty("layoutDirection", Qt::LeftToRight);
3060
3061     // LTR == LTR
3062     foreach(const QString objectName, objectNames)
3063         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3064
3065     QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
3066     QSGItemPrivate::get(listviewB)->setLayoutMirror(true);
3067     QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
3068
3069     // LTR != LTR+mirror
3070     foreach(const QString objectName, objectNames)
3071         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3072
3073     listviewA->setProperty("layoutDirection", Qt::RightToLeft);
3074
3075     // RTL == LTR+mirror
3076     foreach(const QString objectName, objectNames)
3077         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3078
3079     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3080
3081     // RTL != RTL+mirror
3082     foreach(const QString objectName, objectNames)
3083         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3084
3085     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3086
3087     // LTR == RTL+mirror
3088     foreach(const QString objectName, objectNames)
3089         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3090
3091     delete canvasA;
3092     delete canvasB;
3093 }
3094
3095 void tst_QSGListView::qListModelInterface_items()
3096 {
3097     items<TestModel>();
3098 }
3099
3100 void tst_QSGListView::qAbstractItemModel_items()
3101 {
3102     items<TestModel2>();
3103 }
3104
3105 void tst_QSGListView::qListModelInterface_changed()
3106 {
3107     changed<TestModel>();
3108 }
3109
3110 void tst_QSGListView::qAbstractItemModel_changed()
3111 {
3112     changed<TestModel2>();
3113 }
3114
3115 void tst_QSGListView::qListModelInterface_inserted()
3116 {
3117     inserted<TestModel>();
3118 }
3119
3120 void tst_QSGListView::qAbstractItemModel_inserted()
3121 {
3122     inserted<TestModel2>();
3123 }
3124
3125 void tst_QSGListView::qListModelInterface_removed()
3126 {
3127     removed<TestModel>(false);
3128     removed<TestModel>(true);
3129 }
3130
3131 void tst_QSGListView::qAbstractItemModel_removed()
3132 {
3133     removed<TestModel2>(false);
3134     removed<TestModel2>(true);
3135 }
3136
3137 void tst_QSGListView::qListModelInterface_moved()
3138 {
3139     moved<TestModel>();
3140 }
3141
3142 void tst_QSGListView::qListModelInterface_moved_data()
3143 {
3144     moved_data();
3145 }
3146
3147 void tst_QSGListView::qAbstractItemModel_moved()
3148 {
3149     moved<TestModel2>();
3150 }
3151
3152 void tst_QSGListView::qAbstractItemModel_moved_data()
3153 {
3154     moved_data();
3155 }
3156
3157 void tst_QSGListView::qListModelInterface_clear()
3158 {
3159     clear<TestModel>();
3160 }
3161
3162 void tst_QSGListView::qAbstractItemModel_clear()
3163 {
3164     clear<TestModel2>();
3165 }
3166
3167 QSGView *tst_QSGListView::createView()
3168 {
3169     QSGView *canvas = new QSGView(0);
3170     canvas->setFixedSize(240,320);
3171
3172     return canvas;
3173 }
3174
3175 /*
3176    Find an item with the specified objectName.  If index is supplied then the
3177    item must also evaluate the {index} expression equal to index
3178 */
3179 template<typename T>
3180 T *tst_QSGListView::findItem(QSGItem *parent, const QString &objectName, int index)
3181 {
3182     const QMetaObject &mo = T::staticMetaObject;
3183     //qDebug() << parent->childItems().count() << "children";
3184     for (int i = 0; i < parent->childItems().count(); ++i) {
3185         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
3186         if(!item)
3187             continue;
3188         //qDebug() << "try" << item;
3189         if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName)) {
3190             if (index != -1) {
3191                 QDeclarativeExpression e(qmlContext(item), item, "index");
3192                 if (e.evaluate().toInt() == index)
3193                     return static_cast<T*>(item);
3194             } else {
3195                 return static_cast<T*>(item);
3196             }
3197         }
3198         item = findItem<T>(item, objectName, index);
3199         if (item)
3200             return static_cast<T*>(item);
3201     }
3202
3203     return 0;
3204 }
3205
3206 template<typename T>
3207 QList<T*> tst_QSGListView::findItems(QSGItem *parent, const QString &objectName)
3208 {
3209     QList<T*> items;
3210     const QMetaObject &mo = T::staticMetaObject;
3211     //qDebug() << parent->childItems().count() << "children";
3212     for (int i = 0; i < parent->childItems().count(); ++i) {
3213         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
3214         if(!item || !item->isVisible())
3215             continue;
3216         //qDebug() << "try" << item;
3217         if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName))
3218             items.append(static_cast<T*>(item));
3219         items += findItems<T>(item, objectName);
3220     }
3221
3222     return items;
3223 }
3224
3225 void tst_QSGListView::dumpTree(QSGItem *parent, int depth)
3226 {
3227     static QString padding("                       ");
3228     for (int i = 0; i < parent->childItems().count(); ++i) {
3229         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
3230         if(!item)
3231             continue;
3232         qDebug() << padding.left(depth*2) << item;
3233         dumpTree(item, depth+1);
3234     }
3235 }
3236
3237
3238 QTEST_MAIN(tst_QSGListView)
3239
3240 #include "tst_qsglistview.moc"
3241