59847d37f59847992ba5293cf9c7732a1c31b462
[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     // Test keys
1482     canvas->show();
1483     qApp->setActiveWindow(canvas);
1484 #ifdef Q_WS_X11
1485     // to be safe and avoid failing setFocus with window managers
1486     qt_x11_wait_for_window_manager(canvas);
1487 #endif
1488     QTRY_VERIFY(canvas->hasFocus());
1489     qApp->processEvents();
1490
1491     QTest::keyClick(canvas, Qt::Key_Down);
1492     QCOMPARE(listview->currentIndex(), 1);
1493
1494     QTest::keyClick(canvas, Qt::Key_Up);
1495     QCOMPARE(listview->currentIndex(), 0);
1496
1497     // hold down Key_Down
1498     for (int i=0; i<model.count()-1; i++) {
1499         QTest::simulateEvent(canvas, true, Qt::Key_Down, Qt::NoModifier, "", true);
1500         QTRY_COMPARE(listview->currentIndex(), i+1);
1501     }
1502     QTest::keyRelease(canvas, Qt::Key_Down);
1503     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
1504     QTRY_COMPARE(listview->contentY(), 280.0);
1505
1506     // hold down Key_Up
1507     for (int i=model.count()-1; i > 0; i--) {
1508         QTest::simulateEvent(canvas, true, Qt::Key_Up, Qt::NoModifier, "", true);
1509         QTRY_COMPARE(listview->currentIndex(), i-1);
1510     }
1511     QTest::keyRelease(canvas, Qt::Key_Up);
1512     QTRY_COMPARE(listview->currentIndex(), 0);
1513     QTRY_COMPARE(listview->contentY(), 0.0);
1514
1515
1516     // turn off auto highlight
1517     listview->setHighlightFollowsCurrentItem(false);
1518     QVERIFY(listview->highlightFollowsCurrentItem() == false);
1519
1520     QVERIFY(listview->highlightItem());
1521     qreal hlPos = listview->highlightItem()->y();
1522
1523     listview->setCurrentIndex(4);
1524     QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
1525
1526     // insert item before currentIndex
1527     listview->setCurrentIndex(28);
1528     model.insertItem(0, "Foo", "1111");
1529     QTRY_COMPARE(canvas->rootObject()->property("current").toInt(), 29);
1530
1531     // check removing highlight by setting currentIndex to -1;
1532     listview->setCurrentIndex(-1);
1533
1534     QCOMPARE(listview->currentIndex(), -1);
1535     QVERIFY(!listview->highlightItem());
1536     QVERIFY(!listview->currentItem());
1537
1538     delete canvas;
1539 }
1540
1541 void tst_QSGListView::noCurrentIndex()
1542 {
1543     TestModel model;
1544     for (int i = 0; i < 30; i++)
1545         model.addItem("Item" + QString::number(i), QString::number(i));
1546
1547     QSGView *canvas = new QSGView(0);
1548     canvas->setFixedSize(240,320);
1549
1550     QDeclarativeContext *ctxt = canvas->rootContext();
1551     ctxt->setContextProperty("testModel", &model);
1552
1553     QString filename(SRCDIR "/data/listview-noCurrent.qml");
1554     canvas->setSource(QUrl::fromLocalFile(filename));
1555
1556     qApp->processEvents();
1557
1558     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1559     QTRY_VERIFY(listview != 0);
1560
1561     QSGItem *contentItem = listview->contentItem();
1562     QTRY_VERIFY(contentItem != 0);
1563
1564     // current index should be -1 at startup
1565     // and we should not have a currentItem or highlightItem
1566     QCOMPARE(listview->currentIndex(), -1);
1567     QCOMPARE(listview->contentY(), 0.0);
1568     QVERIFY(!listview->highlightItem());
1569     QVERIFY(!listview->currentItem());
1570
1571     listview->setCurrentIndex(2);
1572     QCOMPARE(listview->currentIndex(), 2);
1573     QVERIFY(listview->highlightItem());
1574     QVERIFY(listview->currentItem());
1575
1576     delete canvas;
1577 }
1578
1579 void tst_QSGListView::itemList()
1580 {
1581     QSGView *canvas = createView();
1582
1583     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/itemlist.qml"));
1584     qApp->processEvents();
1585
1586     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
1587     QTRY_VERIFY(listview != 0);
1588
1589     QSGItem *contentItem = listview->contentItem();
1590     QTRY_VERIFY(contentItem != 0);
1591
1592     QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
1593     QTRY_VERIFY(model != 0);
1594
1595     QTRY_VERIFY(model->count() == 3);
1596     QTRY_COMPARE(listview->currentIndex(), 0);
1597
1598     QSGItem *item = findItem<QSGItem>(contentItem, "item1");
1599     QTRY_VERIFY(item);
1600     QTRY_COMPARE(item->x(), 0.0);
1601     QCOMPARE(item->height(), listview->height());
1602
1603     QSGText *text = findItem<QSGText>(contentItem, "text1");
1604     QTRY_VERIFY(text);
1605     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
1606
1607     listview->setCurrentIndex(2);
1608
1609     item = findItem<QSGItem>(contentItem, "item3");
1610     QTRY_VERIFY(item);
1611     QTRY_COMPARE(item->x(), 480.0);
1612
1613     text = findItem<QSGText>(contentItem, "text3");
1614     QTRY_VERIFY(text);
1615     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
1616
1617     delete canvas;
1618 }
1619
1620 void tst_QSGListView::cacheBuffer()
1621 {
1622     QSGView *canvas = createView();
1623
1624     TestModel model;
1625     for (int i = 0; i < 30; i++)
1626         model.addItem("Item" + QString::number(i), "");
1627
1628     QDeclarativeContext *ctxt = canvas->rootContext();
1629     ctxt->setContextProperty("testModel", &model);
1630
1631     TestObject *testObject = new TestObject;
1632     ctxt->setContextProperty("testObject", testObject);
1633
1634     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
1635     qApp->processEvents();
1636
1637     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1638     QTRY_VERIFY(listview != 0);
1639
1640     QSGItem *contentItem = listview->contentItem();
1641     QTRY_VERIFY(contentItem != 0);
1642     QTRY_VERIFY(listview->delegate() != 0);
1643     QTRY_VERIFY(listview->model() != 0);
1644     QTRY_VERIFY(listview->highlight() != 0);
1645
1646     // Confirm items positioned correctly
1647     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1648     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1649         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1650         if (!item) qWarning() << "Item" << i << "not found";
1651         QTRY_VERIFY(item);
1652         QTRY_VERIFY(item->y() == i*20);
1653     }
1654
1655     testObject->setCacheBuffer(400);
1656     QTRY_VERIFY(listview->cacheBuffer() == 400);
1657
1658     int newItemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1659     QTRY_VERIFY(newItemCount > itemCount);
1660
1661     // Confirm items positioned correctly
1662     for (int i = 0; i < model.count() && i < newItemCount; ++i) {
1663         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1664         if (!item) qWarning() << "Item" << i << "not found";
1665         QTRY_VERIFY(item);
1666         QTRY_VERIFY(item->y() == i*20);
1667     }
1668
1669     delete canvas;
1670     delete testObject;
1671 }
1672
1673 void tst_QSGListView::positionViewAtIndex()
1674 {
1675     QSGView *canvas = createView();
1676
1677     TestModel model;
1678     for (int i = 0; i < 40; i++)
1679         model.addItem("Item" + QString::number(i), "");
1680
1681     QDeclarativeContext *ctxt = canvas->rootContext();
1682     ctxt->setContextProperty("testModel", &model);
1683
1684     TestObject *testObject = new TestObject;
1685     ctxt->setContextProperty("testObject", testObject);
1686
1687     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
1688     qApp->processEvents();
1689
1690     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1691     QTRY_VERIFY(listview != 0);
1692
1693     QSGItem *contentItem = listview->contentItem();
1694     QTRY_VERIFY(contentItem != 0);
1695
1696     // Confirm items positioned correctly
1697     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1698     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1699         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1700         if (!item) qWarning() << "Item" << i << "not found";
1701         QTRY_VERIFY(item);
1702         QTRY_COMPARE(item->y(), i*20.);
1703     }
1704
1705     // Position on a currently visible item
1706     listview->positionViewAtIndex(3, QSGListView::Beginning);
1707     QTRY_COMPARE(listview->contentY(), 60.);
1708
1709     // Confirm items positioned correctly
1710     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1711     for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) {
1712         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1713         if (!item) qWarning() << "Item" << i << "not found";
1714         QTRY_VERIFY(item);
1715         QTRY_COMPARE(item->y(), i*20.);
1716     }
1717
1718     // Position on an item beyond the visible items
1719     listview->positionViewAtIndex(22, QSGListView::Beginning);
1720     QTRY_COMPARE(listview->contentY(), 440.);
1721
1722     // Confirm items positioned correctly
1723     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1724     for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) {
1725         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1726         if (!item) qWarning() << "Item" << i << "not found";
1727         QTRY_VERIFY(item);
1728         QTRY_COMPARE(item->y(), i*20.);
1729     }
1730
1731     // Position on an item that would leave empty space if positioned at the top
1732     listview->positionViewAtIndex(28, QSGListView::Beginning);
1733     QTRY_COMPARE(listview->contentY(), 480.);
1734
1735     // Confirm items positioned correctly
1736     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1737     for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) {
1738         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1739         if (!item) qWarning() << "Item" << i << "not found";
1740         QTRY_VERIFY(item);
1741         QTRY_COMPARE(item->y(), i*20.);
1742     }
1743
1744     // Position at the beginning again
1745     listview->positionViewAtIndex(0, QSGListView::Beginning);
1746     QTRY_COMPARE(listview->contentY(), 0.);
1747
1748     // Confirm items positioned correctly
1749     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1750     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
1751         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1752         if (!item) qWarning() << "Item" << i << "not found";
1753         QTRY_VERIFY(item);
1754         QTRY_COMPARE(item->y(), i*20.);
1755     }
1756
1757     // Position at End using last index
1758     listview->positionViewAtIndex(model.count()-1, QSGListView::End);
1759     QTRY_COMPARE(listview->contentY(), 480.);
1760
1761     // Confirm items positioned correctly
1762     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
1763     for (int i = 24; i < model.count(); ++i) {
1764         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
1765         if (!item) qWarning() << "Item" << i << "not found";
1766         QTRY_VERIFY(item);
1767         QTRY_COMPARE(item->y(), i*20.);
1768     }
1769
1770     // Position at End
1771     listview->positionViewAtIndex(20, QSGListView::End);
1772     QTRY_COMPARE(listview->contentY(), 100.);
1773
1774     // Position in Center
1775     listview->positionViewAtIndex(15, QSGListView::Center);
1776     QTRY_COMPARE(listview->contentY(), 150.);
1777
1778     // Ensure at least partially visible
1779     listview->positionViewAtIndex(15, QSGListView::Visible);
1780     QTRY_COMPARE(listview->contentY(), 150.);
1781
1782     listview->setContentY(302);
1783     listview->positionViewAtIndex(15, QSGListView::Visible);
1784     QTRY_COMPARE(listview->contentY(), 302.);
1785
1786     listview->setContentY(320);
1787     listview->positionViewAtIndex(15, QSGListView::Visible);
1788     QTRY_COMPARE(listview->contentY(), 300.);
1789
1790     listview->setContentY(85);
1791     listview->positionViewAtIndex(20, QSGListView::Visible);
1792     QTRY_COMPARE(listview->contentY(), 85.);
1793
1794     listview->setContentY(75);
1795     listview->positionViewAtIndex(20, QSGListView::Visible);
1796     QTRY_COMPARE(listview->contentY(), 100.);
1797
1798     // Ensure completely visible
1799     listview->setContentY(120);
1800     listview->positionViewAtIndex(20, QSGListView::Contain);
1801     QTRY_COMPARE(listview->contentY(), 120.);
1802
1803     listview->setContentY(302);
1804     listview->positionViewAtIndex(15, QSGListView::Contain);
1805     QTRY_COMPARE(listview->contentY(), 300.);
1806
1807     listview->setContentY(85);
1808     listview->positionViewAtIndex(20, QSGListView::Contain);
1809     QTRY_COMPARE(listview->contentY(), 100.);
1810
1811     // positionAtBeginnging
1812     listview->positionViewAtBeginning();
1813     QTRY_COMPARE(listview->contentY(), 0.);
1814
1815     listview->setContentY(80);
1816     canvas->rootObject()->setProperty("showHeader", true);
1817     listview->positionViewAtBeginning();
1818     QTRY_COMPARE(listview->contentY(), -30.);
1819
1820     // positionAtEnd
1821     listview->positionViewAtEnd();
1822     QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
1823
1824     listview->setContentY(80);
1825     canvas->rootObject()->setProperty("showFooter", true);
1826     listview->positionViewAtEnd();
1827     QTRY_COMPARE(listview->contentY(), 510.);
1828
1829     // set current item to outside visible view, position at beginning
1830     // and ensure highlight moves to current item
1831     listview->setCurrentIndex(1);
1832     listview->positionViewAtBeginning();
1833     QTRY_COMPARE(listview->contentY(), -30.);
1834     QVERIFY(listview->highlightItem());
1835     QCOMPARE(listview->highlightItem()->y(), 20.);
1836
1837     delete canvas;
1838     delete testObject;
1839 }
1840
1841 void tst_QSGListView::resetModel()
1842 {
1843     QSGView *canvas = createView();
1844
1845     QStringList strings;
1846     strings << "one" << "two" << "three";
1847     QStringListModel model(strings);
1848
1849     QDeclarativeContext *ctxt = canvas->rootContext();
1850     ctxt->setContextProperty("testModel", &model);
1851
1852     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/displaylist.qml"));
1853     qApp->processEvents();
1854
1855     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
1856     QTRY_VERIFY(listview != 0);
1857
1858     QSGItem *contentItem = listview->contentItem();
1859     QTRY_VERIFY(contentItem != 0);
1860
1861     QTRY_COMPARE(listview->count(), model.rowCount());
1862
1863     for (int i = 0; i < model.rowCount(); ++i) {
1864         QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
1865         QTRY_VERIFY(display != 0);
1866         QTRY_COMPARE(display->text(), strings.at(i));
1867     }
1868
1869     strings.clear();
1870     strings << "four" << "five" << "six" << "seven";
1871     model.setStringList(strings);
1872
1873     QTRY_COMPARE(listview->count(), model.rowCount());
1874
1875     for (int i = 0; i < model.rowCount(); ++i) {
1876         QSGText *display = findItem<QSGText>(contentItem, "displayText", i);
1877         QTRY_VERIFY(display != 0);
1878         QTRY_COMPARE(display->text(), strings.at(i));
1879     }
1880
1881     delete canvas;
1882 }
1883
1884 void tst_QSGListView::propertyChanges()
1885 {
1886     QSGView *canvas = createView();
1887     QTRY_VERIFY(canvas);
1888     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/propertychangestest.qml"));
1889
1890     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
1891     QTRY_VERIFY(listView);
1892
1893     QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
1894     QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
1895     QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
1896     QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
1897     QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
1898     QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
1899     QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
1900
1901     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
1902     QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
1903     QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
1904     QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::ApplyRange);
1905     QTRY_COMPARE(listView->isWrapEnabled(), true);
1906     QTRY_COMPARE(listView->cacheBuffer(), 10);
1907     QTRY_COMPARE(listView->snapMode(), QSGListView::SnapToItem);
1908
1909     listView->setHighlightFollowsCurrentItem(false);
1910     listView->setPreferredHighlightBegin(1.0);
1911     listView->setPreferredHighlightEnd(1.0);
1912     listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
1913     listView->setWrapEnabled(false);
1914     listView->setCacheBuffer(3);
1915     listView->setSnapMode(QSGListView::SnapOneItem);
1916
1917     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
1918     QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
1919     QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
1920     QTRY_COMPARE(listView->highlightRangeMode(), QSGListView::StrictlyEnforceRange);
1921     QTRY_COMPARE(listView->isWrapEnabled(), false);
1922     QTRY_COMPARE(listView->cacheBuffer(), 3);
1923     QTRY_COMPARE(listView->snapMode(), QSGListView::SnapOneItem);
1924
1925     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
1926     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
1927     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
1928     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
1929     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
1930     QTRY_COMPARE(cacheBufferSpy.count(),1);
1931     QTRY_COMPARE(snapModeSpy.count(),1);
1932
1933     listView->setHighlightFollowsCurrentItem(false);
1934     listView->setPreferredHighlightBegin(1.0);
1935     listView->setPreferredHighlightEnd(1.0);
1936     listView->setHighlightRangeMode(QSGListView::StrictlyEnforceRange);
1937     listView->setWrapEnabled(false);
1938     listView->setCacheBuffer(3);
1939     listView->setSnapMode(QSGListView::SnapOneItem);
1940
1941     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
1942     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
1943     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
1944     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
1945     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
1946     QTRY_COMPARE(cacheBufferSpy.count(),1);
1947     QTRY_COMPARE(snapModeSpy.count(),1);
1948
1949     delete canvas;
1950 }
1951
1952 void tst_QSGListView::componentChanges()
1953 {
1954     QSGView *canvas = createView();
1955     QTRY_VERIFY(canvas);
1956     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/propertychangestest.qml"));
1957
1958     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
1959     QTRY_VERIFY(listView);
1960
1961     QDeclarativeComponent component(canvas->engine());
1962     component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
1963
1964     QDeclarativeComponent delegateComponent(canvas->engine());
1965     delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
1966
1967     QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
1968     QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
1969     QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
1970     QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
1971
1972     listView->setHighlight(&component);
1973     listView->setHeader(&component);
1974     listView->setFooter(&component);
1975     listView->setDelegate(&delegateComponent);
1976
1977     QTRY_COMPARE(listView->highlight(), &component);
1978     QTRY_COMPARE(listView->header(), &component);
1979     QTRY_COMPARE(listView->footer(), &component);
1980     QTRY_COMPARE(listView->delegate(), &delegateComponent);
1981
1982     QTRY_COMPARE(highlightSpy.count(),1);
1983     QTRY_COMPARE(delegateSpy.count(),1);
1984     QTRY_COMPARE(headerSpy.count(),1);
1985     QTRY_COMPARE(footerSpy.count(),1);
1986
1987     listView->setHighlight(&component);
1988     listView->setHeader(&component);
1989     listView->setFooter(&component);
1990     listView->setDelegate(&delegateComponent);
1991
1992     QTRY_COMPARE(highlightSpy.count(),1);
1993     QTRY_COMPARE(delegateSpy.count(),1);
1994     QTRY_COMPARE(headerSpy.count(),1);
1995     QTRY_COMPARE(footerSpy.count(),1);
1996
1997     delete canvas;
1998 }
1999
2000 void tst_QSGListView::modelChanges()
2001 {
2002     QSGView *canvas = createView();
2003     QTRY_VERIFY(canvas);
2004     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/propertychangestest.qml"));
2005
2006     QSGListView *listView = canvas->rootObject()->findChild<QSGListView*>("listView");
2007     QTRY_VERIFY(listView);
2008
2009     QDeclarativeListModel *alternateModel = canvas->rootObject()->findChild<QDeclarativeListModel*>("alternateModel");
2010     QTRY_VERIFY(alternateModel);
2011     QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
2012     QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
2013
2014     listView->setModel(modelVariant);
2015     QTRY_COMPARE(listView->model(), modelVariant);
2016     QTRY_COMPARE(modelSpy.count(),1);
2017
2018     listView->setModel(modelVariant);
2019     QTRY_COMPARE(modelSpy.count(),1);
2020
2021     listView->setModel(QVariant());
2022     QTRY_COMPARE(modelSpy.count(),2);
2023
2024     delete canvas;
2025 }
2026
2027 void tst_QSGListView::QTBUG_9791()
2028 {
2029     QSGView *canvas = createView();
2030
2031     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/strictlyenforcerange.qml"));
2032     qApp->processEvents();
2033
2034     QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2035     QTRY_VERIFY(listview != 0);
2036
2037     QSGItem *contentItem = listview->contentItem();
2038     QTRY_VERIFY(contentItem != 0);
2039     QTRY_VERIFY(listview->delegate() != 0);
2040     QTRY_VERIFY(listview->model() != 0);
2041
2042     QMetaObject::invokeMethod(listview, "fillModel");
2043     qApp->processEvents();
2044
2045     // Confirm items positioned correctly
2046     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2047     QCOMPARE(itemCount, 3);
2048
2049     for (int i = 0; i < itemCount; ++i) {
2050         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2051         if (!item) qWarning() << "Item" << i << "not found";
2052         QTRY_VERIFY(item);
2053         QTRY_COMPARE(item->x(), i*300.0);
2054     }
2055
2056     // check that view is positioned correctly
2057     QTRY_COMPARE(listview->contentX(), 590.0);
2058
2059     delete canvas;
2060 }
2061
2062 void tst_QSGListView::manualHighlight()
2063 {
2064     QSGView *canvas = new QSGView(0);
2065     canvas->setFixedSize(240,320);
2066
2067     QString filename(SRCDIR "/data/manual-highlight.qml");
2068     canvas->setSource(QUrl::fromLocalFile(filename));
2069
2070     qApp->processEvents();
2071
2072     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2073     QTRY_VERIFY(listview != 0);
2074
2075     QSGItem *contentItem = listview->contentItem();
2076     QTRY_VERIFY(contentItem != 0);
2077
2078     QTRY_COMPARE(listview->currentIndex(), 0);
2079     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 0));
2080     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2081
2082     listview->setCurrentIndex(2);
2083
2084     QTRY_COMPARE(listview->currentIndex(), 2);
2085     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2086     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2087
2088     // QTBUG-15972
2089     listview->positionViewAtIndex(3, QSGListView::Contain);
2090
2091     QTRY_COMPARE(listview->currentIndex(), 2);
2092     QTRY_COMPARE(listview->currentItem(), findItem<QSGItem>(contentItem, "wrapper", 2));
2093     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
2094
2095     delete canvas;
2096 }
2097
2098 void tst_QSGListView::QTBUG_11105()
2099 {
2100     QSGView *canvas = createView();
2101
2102     TestModel model;
2103     for (int i = 0; i < 30; i++)
2104         model.addItem("Item" + QString::number(i), "");
2105
2106     QDeclarativeContext *ctxt = canvas->rootContext();
2107     ctxt->setContextProperty("testModel", &model);
2108
2109     TestObject *testObject = new TestObject;
2110     ctxt->setContextProperty("testObject", testObject);
2111
2112     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2113     qApp->processEvents();
2114
2115     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2116     QTRY_VERIFY(listview != 0);
2117
2118     QSGItem *contentItem = listview->contentItem();
2119     QTRY_VERIFY(contentItem != 0);
2120
2121     // Confirm items positioned correctly
2122     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2123     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2124         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2125         if (!item) qWarning() << "Item" << i << "not found";
2126         QTRY_VERIFY(item);
2127         QTRY_VERIFY(item->y() == i*20);
2128     }
2129
2130     listview->positionViewAtIndex(20, QSGListView::Beginning);
2131     QCOMPARE(listview->contentY(), 280.);
2132
2133     TestModel model2;
2134     for (int i = 0; i < 5; i++)
2135         model2.addItem("Item" + QString::number(i), "");
2136
2137     ctxt->setContextProperty("testModel", &model2);
2138
2139     itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2140     QCOMPARE(itemCount, 5);
2141
2142     delete canvas;
2143     delete testObject;
2144 }
2145
2146 void tst_QSGListView::header()
2147 {
2148     QFETCH(QSGListView::Orientation, orientation);
2149     QFETCH(Qt::LayoutDirection, layoutDirection);
2150     QFETCH(QPointF, initialHeaderPos);
2151     QFETCH(QPointF, firstDelegatePos);
2152     QFETCH(QPointF, initialContentPos);
2153     QFETCH(QPointF, changedHeaderPos);
2154     QFETCH(QPointF, changedContentPos);
2155
2156     QSGView *canvas = createView();
2157
2158     TestModel model;
2159     for (int i = 0; i < 30; i++)
2160         model.addItem("Item" + QString::number(i), "");
2161
2162     QDeclarativeContext *ctxt = canvas->rootContext();
2163     ctxt->setContextProperty("testModel", &model);
2164
2165     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header.qml"));
2166     qApp->processEvents();
2167
2168     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2169     QTRY_VERIFY(listview != 0);
2170     listview->setOrientation(orientation);
2171     listview->setLayoutDirection(layoutDirection);
2172
2173     QSGItem *contentItem = listview->contentItem();
2174     QTRY_VERIFY(contentItem != 0);
2175
2176     QSGText *header = findItem<QSGText>(contentItem, "header");
2177     QVERIFY(header);
2178
2179     QVERIFY(header == listview->headerItem());
2180
2181     QCOMPARE(header->width(), 100.);
2182     QCOMPARE(header->height(), 30.);
2183     QCOMPARE(header->pos(), initialHeaderPos);
2184     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2185
2186     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2187     QVERIFY(item);
2188     QCOMPARE(item->pos(), firstDelegatePos);
2189
2190     model.clear();
2191     QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
2192
2193     for (int i = 0; i < 30; i++)
2194         model.addItem("Item" + QString::number(i), "");
2195
2196     QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
2197     QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
2198
2199     QCOMPARE(headerItemSpy.count(), 1);
2200
2201     header = findItem<QSGText>(contentItem, "header");
2202     QVERIFY(!header);
2203     header = findItem<QSGText>(contentItem, "header2");
2204     QVERIFY(header);
2205
2206     QVERIFY(header == listview->headerItem());
2207
2208     QCOMPARE(header->pos(), changedHeaderPos);
2209     QCOMPARE(header->width(), 50.);
2210     QCOMPARE(header->height(), 20.);
2211     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2212     QCOMPARE(item->pos(), firstDelegatePos);
2213
2214     delete canvas;
2215 }
2216
2217 void tst_QSGListView::header_data()
2218 {
2219     QTest::addColumn<QSGListView::Orientation>("orientation");
2220     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2221     QTest::addColumn<QPointF>("initialHeaderPos");
2222     QTest::addColumn<QPointF>("changedHeaderPos");
2223     QTest::addColumn<QPointF>("initialContentPos");
2224     QTest::addColumn<QPointF>("changedContentPos");
2225     QTest::addColumn<QPointF>("firstDelegatePos");
2226
2227     // header1 = 100 x 30
2228     // header2 = 50 x 20
2229     // delegates = 240 x 20
2230     // view width = 240
2231
2232     // header above items, top left
2233     QTest::newRow("vertical, left to right") << QSGListView::Vertical << Qt::LeftToRight
2234         << QPointF(0, -30)
2235         << QPointF(0, -20)
2236         << QPointF(0, -30)
2237         << QPointF(0, -20)
2238         << QPointF(0, 0);
2239
2240     // header above items, top right
2241     QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2242         << QPointF(0, -30)
2243         << QPointF(0, -20)
2244         << QPointF(0, -30)
2245         << QPointF(0, -20)
2246         << QPointF(0, 0);
2247
2248     // header to left of items
2249     QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2250         << QPointF(-100, 0)
2251         << QPointF(-50, 0)
2252         << QPointF(-100, 0)
2253         << QPointF(-50, 0)
2254         << QPointF(0, 0);
2255
2256     // header to right of items
2257     QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2258         << QPointF(0, 0)
2259         << QPointF(0, 0)
2260         << QPointF(-240 + 100, 0)
2261         << QPointF(-240 + 50, 0)
2262         << QPointF(-240, 0);
2263 }
2264
2265 void tst_QSGListView::header_delayItemCreation()
2266 {
2267     QSGView *canvas = createView();
2268
2269     TestModel model;
2270
2271     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/header1.qml"));
2272     qApp->processEvents();
2273
2274     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2275     QTRY_VERIFY(listview != 0);
2276
2277     QSGItem *contentItem = listview->contentItem();
2278     QTRY_VERIFY(contentItem != 0);
2279
2280     QSGText *header = findItem<QSGText>(contentItem, "header");
2281     QVERIFY(header);
2282     QCOMPARE(header->y(), -header->height());
2283
2284     QCOMPARE(listview->contentY(), -header->height());
2285
2286     model.clear();
2287     QTRY_COMPARE(header->y(), -header->height());
2288
2289     delete canvas;
2290 }
2291
2292 void tst_QSGListView::footer()
2293 {
2294     QFETCH(QSGListView::Orientation, orientation);
2295     QFETCH(Qt::LayoutDirection, layoutDirection);
2296     QFETCH(QPointF, initialFooterPos);
2297     QFETCH(QPointF, firstDelegatePos);
2298     QFETCH(QPointF, initialContentPos);
2299     QFETCH(QPointF, changedFooterPos);
2300     QFETCH(QPointF, changedContentPos);
2301
2302     QSGView *canvas = createView();
2303
2304     TestModel model;
2305     for (int i = 0; i < 3; i++)
2306         model.addItem("Item" + QString::number(i), "");
2307
2308     QDeclarativeContext *ctxt = canvas->rootContext();
2309     ctxt->setContextProperty("testModel", &model);
2310
2311     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/footer.qml"));
2312     canvas->show();
2313     qApp->processEvents();
2314
2315     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2316     QTRY_VERIFY(listview != 0);
2317     listview->setOrientation(orientation);
2318     listview->setLayoutDirection(layoutDirection);
2319
2320     QSGItem *contentItem = listview->contentItem();
2321     QTRY_VERIFY(contentItem != 0);
2322
2323     QSGText *footer = findItem<QSGText>(contentItem, "footer");
2324     QVERIFY(footer);
2325
2326     QVERIFY(footer == listview->footerItem());
2327
2328     QCOMPARE(footer->pos(), initialFooterPos);
2329     QCOMPARE(footer->width(), 100.);
2330     QCOMPARE(footer->height(), 30.);
2331     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
2332
2333     QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", 0);
2334     QVERIFY(item);
2335     QCOMPARE(item->pos(), firstDelegatePos);
2336
2337     // remove one item
2338     model.removeItem(1);
2339
2340     if (orientation == QSGListView::Vertical) {
2341         QTRY_COMPARE(footer->y(), initialFooterPos.y() - 20);   // delegate height = 20
2342     } else {
2343         QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
2344                 initialFooterPos.x() - 40 : initialFooterPos.x() + 40);  // delegate width = 40
2345     }
2346
2347     // remove all items
2348     model.clear();
2349
2350     QPointF posWhenNoItems(0, 0);
2351     if (orientation == QSGListView::Horizontal && layoutDirection == Qt::RightToLeft)
2352         posWhenNoItems.setX(-100);
2353     QTRY_COMPARE(footer->pos(), posWhenNoItems);
2354
2355     // if header is present, it's at a negative pos, so the footer should not move
2356     canvas->rootObject()->setProperty("showHeader", true);
2357     QTRY_COMPARE(footer->pos(), posWhenNoItems);
2358     canvas->rootObject()->setProperty("showHeader", false);
2359
2360     // add 30 items
2361     for (int i = 0; i < 30; i++)
2362         model.addItem("Item" + QString::number(i), "");
2363
2364     QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
2365     QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
2366
2367     QCOMPARE(footerItemSpy.count(), 1);
2368
2369     footer = findItem<QSGText>(contentItem, "footer");
2370     QVERIFY(!footer);
2371     footer = findItem<QSGText>(contentItem, "footer2");
2372     QVERIFY(footer);
2373
2374     QVERIFY(footer == listview->footerItem());
2375
2376     QCOMPARE(footer->pos(), changedFooterPos);
2377     QCOMPARE(footer->width(), 50.);
2378     QCOMPARE(footer->height(), 20.);
2379     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
2380
2381     item = findItem<QSGItem>(contentItem, "wrapper", 0);
2382     QVERIFY(item);
2383     QCOMPARE(item->pos(), firstDelegatePos);
2384
2385     delete canvas;
2386 }
2387
2388 void tst_QSGListView::footer_data()
2389 {
2390     QTest::addColumn<QSGListView::Orientation>("orientation");
2391     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2392     QTest::addColumn<QPointF>("initialFooterPos");
2393     QTest::addColumn<QPointF>("changedFooterPos");
2394     QTest::addColumn<QPointF>("initialContentPos");
2395     QTest::addColumn<QPointF>("changedContentPos");
2396     QTest::addColumn<QPointF>("firstDelegatePos");
2397
2398     // footer1 = 100 x 30
2399     // footer2 = 100 x 20
2400     // delegates = 40 x 20
2401     // view width = 240
2402
2403     // footer below items, bottom left
2404     QTest::newRow("vertical, layout left to right") << QSGListView::Vertical << Qt::LeftToRight
2405         << QPointF(0, 3 * 20)
2406         << QPointF(0, 30 * 20)  // added 30 items
2407         << QPointF(0, 0)
2408         << QPointF(0, 0)
2409         << QPointF(0, 0);
2410
2411     // footer below items, bottom right
2412     QTest::newRow("vertical, layout right to left") << QSGListView::Vertical << Qt::RightToLeft
2413         << QPointF(0, 3 * 20)
2414         << QPointF(0, 30 * 20)
2415         << QPointF(0, 0)
2416         << QPointF(0, 0)
2417         << QPointF(0, 0);
2418
2419     // footer to right of items
2420     QTest::newRow("horizontal, layout left to right") << QSGListView::Horizontal << Qt::LeftToRight
2421         << QPointF(40 * 3, 0)
2422         << QPointF(40 * 30, 0)
2423         << QPointF(0, 0)
2424         << QPointF(0, 0)
2425         << QPointF(0, 0);
2426
2427     // footer to left of items
2428     QTest::newRow("horizontal, layout right to left") << QSGListView::Horizontal << Qt::RightToLeft
2429         << QPointF(-(40 * 3) - 100, 0)
2430         << QPointF(-(40 * 30) - 50, 0)     // 50 = new footer width
2431         << QPointF(-240, 0)
2432         << QPointF(-240, 0)
2433         << QPointF(-40, 0);
2434 }
2435
2436 class LVAccessor : public QSGListView
2437 {
2438 public:
2439     qreal minY() const { return minYExtent(); }
2440     qreal maxY() const { return maxYExtent(); }
2441     qreal minX() const { return minXExtent(); }
2442     qreal maxX() const { return maxXExtent(); }
2443 };
2444
2445 void tst_QSGListView::headerFooter()
2446 {
2447     {
2448         // Vertical
2449         QSGView *canvas = createView();
2450
2451         TestModel model;
2452         QDeclarativeContext *ctxt = canvas->rootContext();
2453         ctxt->setContextProperty("testModel", &model);
2454
2455         canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/headerfooter.qml"));
2456         qApp->processEvents();
2457
2458         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2459         QTRY_VERIFY(listview != 0);
2460
2461         QSGItem *contentItem = listview->contentItem();
2462         QTRY_VERIFY(contentItem != 0);
2463
2464         QSGItem *header = findItem<QSGItem>(contentItem, "header");
2465         QVERIFY(header);
2466         QCOMPARE(header->y(), -header->height());
2467
2468         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
2469         QVERIFY(footer);
2470         QCOMPARE(footer->y(), 0.);
2471
2472         QVERIFY(static_cast<LVAccessor*>(listview)->minY() == 0);
2473         QVERIFY(static_cast<LVAccessor*>(listview)->maxY() == 0);
2474
2475         delete canvas;
2476     }
2477     {
2478         // Horizontal
2479         QSGView *canvas = createView();
2480
2481         TestModel model;
2482         QDeclarativeContext *ctxt = canvas->rootContext();
2483         ctxt->setContextProperty("testModel", &model);
2484
2485         canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/headerfooter.qml"));
2486         canvas->rootObject()->setProperty("horizontal", true);
2487         qApp->processEvents();
2488
2489         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2490         QTRY_VERIFY(listview != 0);
2491
2492         QSGItem *contentItem = listview->contentItem();
2493         QTRY_VERIFY(contentItem != 0);
2494
2495         QSGItem *header = findItem<QSGItem>(contentItem, "header");
2496         QVERIFY(header);
2497         QCOMPARE(header->x(), -header->width());
2498
2499         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
2500         QVERIFY(footer);
2501         QCOMPARE(footer->x(), 0.);
2502
2503         QVERIFY(static_cast<LVAccessor*>(listview)->minX() == 0);
2504         QVERIFY(static_cast<LVAccessor*>(listview)->maxX() == 0);
2505
2506         delete canvas;
2507     }
2508     {
2509         // Horizontal RTL
2510         QSGView *canvas = createView();
2511
2512         TestModel model;
2513         QDeclarativeContext *ctxt = canvas->rootContext();
2514         ctxt->setContextProperty("testModel", &model);
2515
2516         canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/headerfooter.qml"));
2517         canvas->rootObject()->setProperty("horizontal", true);
2518         canvas->rootObject()->setProperty("rtl", true);
2519         qApp->processEvents();
2520
2521         QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2522         QTRY_VERIFY(listview != 0);
2523
2524         QSGItem *contentItem = listview->contentItem();
2525         QTRY_VERIFY(contentItem != 0);
2526
2527         QSGItem *header = findItem<QSGItem>(contentItem, "header");
2528         QVERIFY(header);
2529         QCOMPARE(header->x(), 0.);
2530
2531         QSGItem *footer = findItem<QSGItem>(contentItem, "footer");
2532         QVERIFY(footer);
2533         QCOMPARE(footer->x(), -footer->width());
2534
2535         QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), 240.);
2536         QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), 240.);
2537
2538         delete canvas;
2539     }
2540 }
2541
2542 void tst_QSGListView::resizeView()
2543 {
2544     QSGView *canvas = createView();
2545
2546     TestModel model;
2547     for (int i = 0; i < 40; i++)
2548         model.addItem("Item" + QString::number(i), "");
2549
2550     QDeclarativeContext *ctxt = canvas->rootContext();
2551     ctxt->setContextProperty("testModel", &model);
2552
2553     TestObject *testObject = new TestObject;
2554     ctxt->setContextProperty("testObject", testObject);
2555
2556     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2557     qApp->processEvents();
2558
2559     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2560     QTRY_VERIFY(listview != 0);
2561
2562     QSGItem *contentItem = listview->contentItem();
2563     QTRY_VERIFY(contentItem != 0);
2564
2565     // Confirm items positioned correctly
2566     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2567     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2568         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2569         if (!item) qWarning() << "Item" << i << "not found";
2570         QTRY_VERIFY(item);
2571         QTRY_COMPARE(item->y(), i*20.);
2572     }
2573
2574     QVariant heightRatio;
2575     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
2576     QCOMPARE(heightRatio.toReal(), 0.4);
2577
2578     listview->setHeight(200);
2579
2580     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
2581     QCOMPARE(heightRatio.toReal(), 0.25);
2582
2583     delete canvas;
2584     delete testObject;
2585 }
2586
2587 void tst_QSGListView::sizeLessThan1()
2588 {
2589     QSGView *canvas = createView();
2590
2591     TestModel model;
2592     for (int i = 0; i < 30; i++)
2593         model.addItem("Item" + QString::number(i), "");
2594
2595     QDeclarativeContext *ctxt = canvas->rootContext();
2596     ctxt->setContextProperty("testModel", &model);
2597
2598     TestObject *testObject = new TestObject;
2599     ctxt->setContextProperty("testObject", testObject);
2600
2601     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/sizelessthan1.qml"));
2602     qApp->processEvents();
2603
2604     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2605     QTRY_VERIFY(listview != 0);
2606
2607     QSGItem *contentItem = listview->contentItem();
2608     QTRY_VERIFY(contentItem != 0);
2609
2610     // Confirm items positioned correctly
2611     int itemCount = findItems<QSGItem>(contentItem, "wrapper").count();
2612     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2613         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2614         if (!item) qWarning() << "Item" << i << "not found";
2615         QTRY_VERIFY(item);
2616         QTRY_COMPARE(item->y(), i*0.5);
2617     }
2618
2619     delete canvas;
2620     delete testObject;
2621 }
2622
2623 void tst_QSGListView::QTBUG_14821()
2624 {
2625     QSGView *canvas = createView();
2626
2627     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/qtbug14821.qml"));
2628     qApp->processEvents();
2629
2630     QSGListView *listview = qobject_cast<QSGListView*>(canvas->rootObject());
2631     QVERIFY(listview != 0);
2632
2633     QSGItem *contentItem = listview->contentItem();
2634     QVERIFY(contentItem != 0);
2635
2636     listview->decrementCurrentIndex();
2637     QCOMPARE(listview->currentIndex(), 99);
2638
2639     listview->incrementCurrentIndex();
2640     QCOMPARE(listview->currentIndex(), 0);
2641
2642     delete canvas;
2643 }
2644
2645 void tst_QSGListView::resizeDelegate()
2646 {
2647     QSGView *canvas = createView();
2648     canvas->show();
2649
2650     QStringList strings;
2651     for (int i = 0; i < 30; ++i)
2652         strings << QString::number(i);
2653     QStringListModel model(strings);
2654
2655     QDeclarativeContext *ctxt = canvas->rootContext();
2656     ctxt->setContextProperty("testModel", &model);
2657
2658     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/displaylist.qml"));
2659     qApp->processEvents();
2660
2661     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2662     QVERIFY(listview != 0);
2663
2664     QSGItem *contentItem = listview->contentItem();
2665     QVERIFY(contentItem != 0);
2666
2667     QCOMPARE(listview->count(), model.rowCount());
2668
2669     listview->setCurrentIndex(25);
2670     listview->setContentY(0);
2671
2672     for (int i = 0; i < 16; ++i) {
2673         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2674         QVERIFY(item != 0);
2675         QCOMPARE(item->y(), i*20.0);
2676     }
2677
2678     QCOMPARE(listview->currentItem()->y(), 500.0);
2679     QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
2680
2681     canvas->rootObject()->setProperty("delegateHeight", 30);
2682     QTest::qWait(300);
2683
2684     for (int i = 0; i < 11; ++i) {
2685         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2686         QVERIFY(item != 0);
2687         QTRY_COMPARE(item->y(), i*30.0);
2688     }
2689
2690     QTRY_COMPARE(listview->currentItem()->y(), 750.0);
2691     QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
2692
2693     listview->setCurrentIndex(1);
2694     listview->positionViewAtIndex(25, QSGListView::Beginning);
2695     listview->positionViewAtIndex(5, QSGListView::Beginning);
2696
2697     for (int i = 5; i < 16; ++i) {
2698         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2699         QVERIFY(item != 0);
2700         QCOMPARE(item->y(), i*30.0);
2701     }
2702
2703     QTRY_COMPARE(listview->currentItem()->y(), 30.0);
2704     QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
2705
2706     canvas->rootObject()->setProperty("delegateHeight", 20);
2707     QTest::qWait(300);
2708
2709     for (int i = 5; i < 11; ++i) {
2710         QSGItem *item = findItem<QSGItem>(contentItem, "wrapper", i);
2711         QVERIFY(item != 0);
2712         QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
2713     }
2714
2715     QTRY_COMPARE(listview->currentItem()->y(), 70.0);
2716     QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
2717
2718     delete canvas;
2719 }
2720
2721 void tst_QSGListView::resizeFirstDelegate()
2722 {
2723     // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
2724     // and other delegates have height > 0
2725
2726     QSGView *canvas = createView();
2727     canvas->show();
2728
2729     // bug only occurs when all items in the model are visible
2730     TestModel model;
2731     for (int i = 0; i < 10; i++)
2732         model.addItem("Item" + QString::number(i), "");
2733
2734     QDeclarativeContext *ctxt = canvas->rootContext();
2735     ctxt->setContextProperty("testModel", &model);
2736
2737     TestObject *testObject = new TestObject;
2738     ctxt->setContextProperty("testObject", testObject);
2739
2740     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2741     qApp->processEvents();
2742
2743     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2744     QVERIFY(listview != 0);
2745
2746     QSGItem *contentItem = listview->contentItem();
2747     QVERIFY(contentItem != 0);
2748
2749     QSGItem *item = 0;
2750     for (int i = 0; i < model.count(); ++i) {
2751         item = findItem<QSGItem>(contentItem, "wrapper", i);
2752         QVERIFY(item != 0);
2753         QCOMPARE(item->y(), i*20.0);
2754     }
2755
2756     item = findItem<QSGItem>(contentItem, "wrapper", 0);
2757     item->setHeight(0);
2758
2759     // check the content y has not jumped up and down
2760     QCOMPARE(listview->contentY(), 0.0);
2761     QSignalSpy spy(listview, SIGNAL(contentYChanged()));
2762     QTest::qWait(300);
2763     QCOMPARE(spy.count(), 0);
2764
2765     for (int i = 1; i < model.count(); ++i) {
2766         item = findItem<QSGItem>(contentItem, "wrapper", i);
2767         QVERIFY(item != 0);
2768         QTRY_COMPARE(item->y(), (i-1)*20.0);
2769     }
2770
2771     delete testObject;
2772     delete canvas;
2773 }
2774
2775 void tst_QSGListView::QTBUG_16037()
2776 {
2777     QSGView *canvas = createView();
2778     canvas->show();
2779
2780     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/qtbug16037.qml"));
2781     qApp->processEvents();
2782
2783     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "listview");
2784     QTRY_VERIFY(listview != 0);
2785
2786     QVERIFY(listview->contentHeight() <= 0.0);
2787
2788     QMetaObject::invokeMethod(canvas->rootObject(), "setModel");
2789
2790     QTRY_COMPARE(listview->contentHeight(), 80.0);
2791
2792     delete canvas;
2793 }
2794
2795 void tst_QSGListView::indexAt()
2796 {
2797     QSGView *canvas = createView();
2798
2799     TestModel model;
2800     for (int i = 0; i < 30; i++)
2801         model.addItem("Item" + QString::number(i), "");
2802
2803     QDeclarativeContext *ctxt = canvas->rootContext();
2804     ctxt->setContextProperty("testModel", &model);
2805
2806     TestObject *testObject = new TestObject;
2807     ctxt->setContextProperty("testObject", testObject);
2808
2809     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/listviewtest.qml"));
2810     qApp->processEvents();
2811
2812     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2813     QTRY_VERIFY(listview != 0);
2814
2815     QSGItem *contentItem = listview->contentItem();
2816     QTRY_VERIFY(contentItem != 0);
2817
2818     QCOMPARE(listview->indexAt(0,0), 0);
2819     QCOMPARE(listview->indexAt(0,19), 0);
2820     QCOMPARE(listview->indexAt(239,19), 0);
2821     QCOMPARE(listview->indexAt(0,20), 1);
2822     QCOMPARE(listview->indexAt(240,20), -1);
2823
2824     delete canvas;
2825     delete testObject;
2826 }
2827
2828 void tst_QSGListView::incrementalModel()
2829 {
2830     QSGView *canvas = createView();
2831
2832     IncrementalModel model;
2833     QDeclarativeContext *ctxt = canvas->rootContext();
2834     ctxt->setContextProperty("testModel", &model);
2835
2836     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/displaylist.qml"));
2837     qApp->processEvents();
2838
2839     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "list");
2840     QTRY_VERIFY(listview != 0);
2841
2842     QSGItem *contentItem = listview->contentItem();
2843     QTRY_VERIFY(contentItem != 0);
2844
2845     QTRY_COMPARE(listview->count(), 20);
2846
2847     listview->positionViewAtIndex(10, QSGListView::Beginning);
2848
2849     QTRY_COMPARE(listview->count(), 25);
2850
2851     delete canvas;
2852 }
2853
2854 void tst_QSGListView::onAdd()
2855 {
2856     QFETCH(int, initialItemCount);
2857     QFETCH(int, itemsToAdd);
2858
2859     const int delegateHeight = 10;
2860     TestModel2 model;
2861
2862     // these initial items should not trigger ListView.onAdd
2863     for (int i=0; i<initialItemCount; i++)
2864         model.addItem("dummy value", "dummy value");
2865
2866     QSGView *canvas = createView();
2867     canvas->setFixedSize(200, delegateHeight * (initialItemCount + itemsToAdd));
2868     QDeclarativeContext *ctxt = canvas->rootContext();
2869     ctxt->setContextProperty("testModel", &model);
2870     ctxt->setContextProperty("delegateHeight", delegateHeight);
2871     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/attachedSignals.qml"));
2872
2873     QObject *object = canvas->rootObject();
2874     object->setProperty("width", canvas->width());
2875     object->setProperty("height", canvas->height());
2876     qApp->processEvents();
2877
2878     QList<QPair<QString, QString> > items;
2879     for (int i=0; i<itemsToAdd; i++)
2880         items << qMakePair(QString("value %1").arg(i), QString::number(i));
2881     model.addItems(items);
2882
2883     qApp->processEvents();
2884
2885     QVariantList result = object->property("addedDelegates").toList();
2886     QCOMPARE(result.count(), items.count());
2887     for (int i=0; i<items.count(); i++)
2888         QCOMPARE(result[i].toString(), items[i].first);
2889
2890     delete canvas;
2891 }
2892
2893 void tst_QSGListView::onAdd_data()
2894 {
2895     QTest::addColumn<int>("initialItemCount");
2896     QTest::addColumn<int>("itemsToAdd");
2897
2898     QTest::newRow("0, add 1") << 0 << 1;
2899     QTest::newRow("0, add 2") << 0 << 2;
2900     QTest::newRow("0, add 10") << 0 << 10;
2901
2902     QTest::newRow("1, add 1") << 1 << 1;
2903     QTest::newRow("1, add 2") << 1 << 2;
2904     QTest::newRow("1, add 10") << 1 << 10;
2905
2906     QTest::newRow("5, add 1") << 5 << 1;
2907     QTest::newRow("5, add 2") << 5 << 2;
2908     QTest::newRow("5, add 10") << 5 << 10;
2909 }
2910
2911 void tst_QSGListView::onRemove()
2912 {
2913     QFETCH(int, initialItemCount);
2914     QFETCH(int, indexToRemove);
2915     QFETCH(int, removeCount);
2916
2917     const int delegateHeight = 10;
2918     TestModel2 model;
2919     for (int i=0; i<initialItemCount; i++)
2920         model.addItem(QString("value %1").arg(i), "dummy value");
2921
2922     QSGView *canvas = createView();
2923     QDeclarativeContext *ctxt = canvas->rootContext();
2924     ctxt->setContextProperty("testModel", &model);
2925     ctxt->setContextProperty("delegateHeight", delegateHeight);
2926     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/attachedSignals.qml"));
2927     QObject *object = canvas->rootObject();
2928
2929     qApp->processEvents();
2930
2931     model.removeItems(indexToRemove, removeCount);
2932     qApp->processEvents();
2933     QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
2934
2935     delete canvas;
2936 }
2937
2938 void tst_QSGListView::onRemove_data()
2939 {
2940     QTest::addColumn<int>("initialItemCount");
2941     QTest::addColumn<int>("indexToRemove");
2942     QTest::addColumn<int>("removeCount");
2943
2944     QTest::newRow("remove first") << 1 << 0 << 1;
2945     QTest::newRow("two items, remove first") << 2 << 0 << 1;
2946     QTest::newRow("two items, remove last") << 2 << 1 << 1;
2947     QTest::newRow("two items, remove all") << 2 << 0 << 2;
2948
2949     QTest::newRow("four items, remove first") << 4 << 0 << 1;
2950     QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
2951     QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
2952     QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
2953     QTest::newRow("four items, remove last") << 4 << 3 << 1;
2954     QTest::newRow("four items, remove all") << 4 << 0 << 4;
2955
2956     QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
2957     QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
2958     QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
2959 }
2960
2961 void tst_QSGListView::rightToLeft()
2962 {
2963     QSGView *canvas = createView();
2964     canvas->setFixedSize(640,320);
2965     canvas->setSource(QUrl::fromLocalFile(SRCDIR "/data/rightToLeft.qml"));
2966     qApp->processEvents();
2967
2968     QVERIFY(canvas->rootObject() != 0);
2969     QSGListView *listview = findItem<QSGListView>(canvas->rootObject(), "view");
2970     QTRY_VERIFY(listview != 0);
2971
2972     QSGItem *contentItem = listview->contentItem();
2973     QTRY_VERIFY(contentItem != 0);
2974
2975     QSGVisualItemModel *model = canvas->rootObject()->findChild<QSGVisualItemModel*>("itemModel");
2976     QTRY_VERIFY(model != 0);
2977
2978     QTRY_VERIFY(model->count() == 3);
2979     QTRY_COMPARE(listview->currentIndex(), 0);
2980
2981     // initial position at first item, right edge aligned
2982     QCOMPARE(listview->contentX(), -640.);
2983
2984     QSGItem *item = findItem<QSGItem>(contentItem, "item1");
2985     QTRY_VERIFY(item);
2986     QTRY_COMPARE(item->x(), -100.0);
2987     QCOMPARE(item->height(), listview->height());
2988
2989     QSGText *text = findItem<QSGText>(contentItem, "text1");
2990     QTRY_VERIFY(text);
2991     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
2992
2993     listview->setCurrentIndex(2);
2994
2995     item = findItem<QSGItem>(contentItem, "item3");
2996     QTRY_VERIFY(item);
2997     QTRY_COMPARE(item->x(), -540.0);
2998
2999     text = findItem<QSGText>(contentItem, "text3");
3000     QTRY_VERIFY(text);
3001     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
3002
3003     QCOMPARE(listview->contentX(), -640.);
3004
3005     // Ensure resizing maintains position relative to right edge
3006     qobject_cast<QSGItem*>(canvas->rootObject())->setWidth(600);
3007     QTRY_COMPARE(listview->contentX(), -600.);
3008
3009     delete canvas;
3010 }
3011
3012 void tst_QSGListView::test_mirroring()
3013 {
3014     QSGView *canvasA = createView();
3015     canvasA->setSource(QUrl::fromLocalFile(SRCDIR "/data/rightToLeft.qml"));
3016     QSGListView *listviewA = findItem<QSGListView>(canvasA->rootObject(), "view");
3017     QTRY_VERIFY(listviewA != 0);
3018
3019     QSGView *canvasB = createView();
3020     canvasB->setSource(QUrl::fromLocalFile(SRCDIR "/data/rightToLeft.qml"));
3021     QSGListView *listviewB = findItem<QSGListView>(canvasB->rootObject(), "view");
3022     QTRY_VERIFY(listviewA != 0);
3023     qApp->processEvents();
3024
3025     QList<QString> objectNames;
3026     objectNames << "item1" << "item2"; // << "item3"
3027
3028     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3029     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3030     QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
3031
3032     // LTR != RTL
3033     foreach(const QString objectName, objectNames)
3034         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3035
3036     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3037     listviewB->setProperty("layoutDirection", Qt::LeftToRight);
3038
3039     // LTR == LTR
3040     foreach(const QString objectName, objectNames)
3041         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3042
3043     QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
3044     QSGItemPrivate::get(listviewB)->setLayoutMirror(true);
3045     QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
3046
3047     // LTR != LTR+mirror
3048     foreach(const QString objectName, objectNames)
3049         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3050
3051     listviewA->setProperty("layoutDirection", Qt::RightToLeft);
3052
3053     // RTL == LTR+mirror
3054     foreach(const QString objectName, objectNames)
3055         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3056
3057     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
3058
3059     // RTL != RTL+mirror
3060     foreach(const QString objectName, objectNames)
3061         QVERIFY(findItem<QSGItem>(listviewA, objectName)->x() != findItem<QSGItem>(listviewB, objectName)->x());
3062
3063     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
3064
3065     // LTR == RTL+mirror
3066     foreach(const QString objectName, objectNames)
3067         QCOMPARE(findItem<QSGItem>(listviewA, objectName)->x(), findItem<QSGItem>(listviewB, objectName)->x());
3068
3069     delete canvasA;
3070     delete canvasB;
3071 }
3072
3073 void tst_QSGListView::qListModelInterface_items()
3074 {
3075     items<TestModel>();
3076 }
3077
3078 void tst_QSGListView::qAbstractItemModel_items()
3079 {
3080     items<TestModel2>();
3081 }
3082
3083 void tst_QSGListView::qListModelInterface_changed()
3084 {
3085     changed<TestModel>();
3086 }
3087
3088 void tst_QSGListView::qAbstractItemModel_changed()
3089 {
3090     changed<TestModel2>();
3091 }
3092
3093 void tst_QSGListView::qListModelInterface_inserted()
3094 {
3095     inserted<TestModel>();
3096 }
3097
3098 void tst_QSGListView::qAbstractItemModel_inserted()
3099 {
3100     inserted<TestModel2>();
3101 }
3102
3103 void tst_QSGListView::qListModelInterface_removed()
3104 {
3105     removed<TestModel>(false);
3106     removed<TestModel>(true);
3107 }
3108
3109 void tst_QSGListView::qAbstractItemModel_removed()
3110 {
3111     removed<TestModel2>(false);
3112     removed<TestModel2>(true);
3113 }
3114
3115 void tst_QSGListView::qListModelInterface_moved()
3116 {
3117     moved<TestModel>();
3118 }
3119
3120 void tst_QSGListView::qListModelInterface_moved_data()
3121 {
3122     moved_data();
3123 }
3124
3125 void tst_QSGListView::qAbstractItemModel_moved()
3126 {
3127     moved<TestModel2>();
3128 }
3129
3130 void tst_QSGListView::qAbstractItemModel_moved_data()
3131 {
3132     moved_data();
3133 }
3134
3135 void tst_QSGListView::qListModelInterface_clear()
3136 {
3137     clear<TestModel>();
3138 }
3139
3140 void tst_QSGListView::qAbstractItemModel_clear()
3141 {
3142     clear<TestModel2>();
3143 }
3144
3145 QSGView *tst_QSGListView::createView()
3146 {
3147     QSGView *canvas = new QSGView(0);
3148     canvas->setFixedSize(240,320);
3149
3150     return canvas;
3151 }
3152
3153 /*
3154    Find an item with the specified objectName.  If index is supplied then the
3155    item must also evaluate the {index} expression equal to index
3156 */
3157 template<typename T>
3158 T *tst_QSGListView::findItem(QSGItem *parent, const QString &objectName, int index)
3159 {
3160     const QMetaObject &mo = T::staticMetaObject;
3161     //qDebug() << parent->childItems().count() << "children";
3162     for (int i = 0; i < parent->childItems().count(); ++i) {
3163         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
3164         if(!item)
3165             continue;
3166         //qDebug() << "try" << item;
3167         if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName)) {
3168             if (index != -1) {
3169                 QDeclarativeExpression e(qmlContext(item), item, "index");
3170                 if (e.evaluate().toInt() == index)
3171                     return static_cast<T*>(item);
3172             } else {
3173                 return static_cast<T*>(item);
3174             }
3175         }
3176         item = findItem<T>(item, objectName, index);
3177         if (item)
3178             return static_cast<T*>(item);
3179     }
3180
3181     return 0;
3182 }
3183
3184 template<typename T>
3185 QList<T*> tst_QSGListView::findItems(QSGItem *parent, const QString &objectName)
3186 {
3187     QList<T*> items;
3188     const QMetaObject &mo = T::staticMetaObject;
3189     //qDebug() << parent->childItems().count() << "children";
3190     for (int i = 0; i < parent->childItems().count(); ++i) {
3191         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
3192         if(!item || !item->isVisible())
3193             continue;
3194         //qDebug() << "try" << item;
3195         if (mo.cast(item) && (objectName.isEmpty() || item->objectName() == objectName))
3196             items.append(static_cast<T*>(item));
3197         items += findItems<T>(item, objectName);
3198     }
3199
3200     return items;
3201 }
3202
3203 void tst_QSGListView::dumpTree(QSGItem *parent, int depth)
3204 {
3205     static QString padding("                       ");
3206     for (int i = 0; i < parent->childItems().count(); ++i) {
3207         QSGItem *item = qobject_cast<QSGItem*>(parent->childItems().at(i));
3208         if(!item)
3209             continue;
3210         qDebug() << padding.left(depth*2) << item;
3211         dumpTree(item, depth+1);
3212     }
3213 }
3214
3215
3216 QTEST_MAIN(tst_QSGListView)
3217
3218 #include "tst_qsglistview.moc"
3219