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