Revert "Remove properties related to highlight speed"
[profile/ivi/qtdeclarative.git] / tests / auto / quick / qquicklistview / tst_qquicklistview.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
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 <QtQml/qqmlengine.h>
46 #include <QtQml/qqmlcontext.h>
47 #include <QtQml/qqmlexpression.h>
48 #include <QtQml/qqmlincubator.h>
49 #include <QtQuick/private/qquicklistview_p.h>
50 #include <QtQuick/private/qquicktext_p.h>
51 #include <QtQuick/private/qquickvisualitemmodel_p.h>
52 #include <QtQml/private/qquicklistmodel_p.h>
53 #include "../../shared/util.h"
54 #include "../shared/viewtestutil.h"
55 #include "../shared/visualtestutil.h"
56 #include "incrementalmodel.h"
57 #include <math.h>
58
59 Q_DECLARE_METATYPE(Qt::LayoutDirection)
60 Q_DECLARE_METATYPE(QQuickItemView::VerticalLayoutDirection)
61 Q_DECLARE_METATYPE(QQuickListView::Orientation)
62 Q_DECLARE_METATYPE(Qt::Key)
63
64 using namespace QQuickViewTestUtil;
65 using namespace QQuickVisualTestUtil;
66
67 #define SHARE_VIEWS
68
69 class tst_QQuickListView : public QQmlDataTest
70 {
71     Q_OBJECT
72 public:
73     tst_QQuickListView();
74
75 private slots:
76     void init();
77     // Test both QListModelInterface and QAbstractItemModel model types
78     void qListModelInterface_items();
79     void qListModelInterface_package_items();
80     void qAbstractItemModel_items();
81
82     void qListModelInterface_changed();
83     void qListModelInterface_package_changed();
84     void qAbstractItemModel_changed();
85
86     void qListModelInterface_inserted();
87     void qListModelInterface_inserted_more();
88     void qListModelInterface_inserted_more_data();
89     void qListModelInterface_package_inserted();
90     void qAbstractItemModel_inserted();
91     void qAbstractItemModel_inserted_more();
92     void qAbstractItemModel_inserted_more_data();
93     void qAbstractItemModel_inserted_more_bottomToTop();
94     void qAbstractItemModel_inserted_more_bottomToTop_data();
95
96     void qListModelInterface_removed();
97     void qListModelInterface_removed_more();
98     void qListModelInterface_removed_more_data();
99     void qListModelInterface_package_removed();
100     void qAbstractItemModel_removed();
101     void qAbstractItemModel_removed_more();
102     void qAbstractItemModel_removed_more_data();
103     void qAbstractItemModel_removed_more_bottomToTop();
104     void qAbstractItemModel_removed_more_bottomToTop_data();
105
106     void qListModelInterface_moved();
107     void qListModelInterface_moved_data();
108     void qListModelInterface_package_moved();
109     void qListModelInterface_package_moved_data();
110     void qAbstractItemModel_moved();
111     void qAbstractItemModel_moved_data();
112     void qAbstractItemModel_moved_bottomToTop();
113     void qAbstractItemModel_moved_bottomToTop_data();
114
115     void multipleChanges_condensed() { multipleChanges(true); }
116     void multipleChanges_condensed_data() { multipleChanges_data(); }
117     void multipleChanges_uncondensed() { multipleChanges(false); }
118     void multipleChanges_uncondensed_data() { multipleChanges_data(); }
119
120     void qListModelInterface_clear();
121     void qListModelInterface_package_clear();
122     void qAbstractItemModel_clear();
123     void qAbstractItemModel_clear_bottomToTop();
124
125     void insertBeforeVisible();
126     void insertBeforeVisible_data();
127     void swapWithFirstItem();
128     void itemList();
129     void itemListFlicker();
130     void currentIndex_delayedItemCreation();
131     void currentIndex_delayedItemCreation_data();
132     void currentIndex();
133     void noCurrentIndex();
134     void keyNavigation();
135     void keyNavigation_data();
136     void enforceRange();
137     void enforceRange_withoutHighlight();
138     void spacing();
139     void qListModelInterface_sections();
140     void qListModelInterface_package_sections();
141     void qAbstractItemModel_sections();
142     void sectionsPositioning();
143     void sectionsDelegate();
144     void sectionsDragOutsideBounds_data();
145     void sectionsDragOutsideBounds();
146     void sectionPropertyChange();
147     void cacheBuffer();
148     void positionViewAtIndex();
149     void resetModel();
150     void propertyChanges();
151     void componentChanges();
152     void modelChanges();
153     void manualHighlight();
154     void initialZValues();
155     void header();
156     void header_data();
157     void header_delayItemCreation();
158     void footer();
159     void footer_data();
160     void extents();
161     void extents_data();
162     void resetModel_headerFooter();
163     void resizeView();
164     void resizeViewAndRepaint();
165     void sizeLessThan1();
166     void QTBUG_14821();
167     void resizeDelegate();
168     void resizeFirstDelegate();
169     void repositionResizedDelegate();
170     void repositionResizedDelegate_data();
171     void QTBUG_16037();
172     void indexAt_itemAt_data();
173     void indexAt_itemAt();
174     void incrementalModel();
175     void onAdd();
176     void onAdd_data();
177     void onRemove();
178     void onRemove_data();
179     void rightToLeft();
180     void test_mirroring();
181     void margins();
182     void marginsResize();
183     void marginsResize_data();
184     void creationContext();
185     void snapToItem_data();
186     void snapToItem();
187     void snapOneItem_data();
188     void snapOneItem();
189
190     void QTBUG_9791();
191     void QTBUG_11105();
192     void QTBUG_21742();
193
194     void asynchronous();
195     void unrequestedVisibility();
196
197     void populateTransitions();
198     void populateTransitions_data();
199     void addTransitions();
200     void addTransitions_data();
201     void moveTransitions();
202     void moveTransitions_data();
203     void removeTransitions();
204     void removeTransitions_data();
205     void displacedTransitions();
206     void displacedTransitions_data();
207     void multipleTransitions();
208     void multipleTransitions_data();
209     void multipleDisplaced();
210
211     void flickBeyondBounds();
212     void destroyItemOnCreation();
213
214     void parentBinding();
215
216 private:
217     template <class T> void items(const QUrl &source, bool forceLayout);
218     template <class T> void changed(const QUrl &source, bool forceLayout);
219     template <class T> void inserted(const QUrl &source);
220     template <class T> void inserted_more(QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
221     template <class T> void removed(const QUrl &source, bool animated);
222     template <class T> void removed_more(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
223     template <class T> void moved(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
224     template <class T> void clear(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
225     template <class T> void sections(const QUrl &source);
226
227     void multipleChanges(bool condensed);
228     void multipleChanges_data();
229
230     QList<int> toIntList(const QVariantList &list);
231     void matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes);
232     void matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes);
233     void matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems);
234
235     void inserted_more_data();
236     void removed_more_data();
237     void moved_data();
238
239 #ifdef SHARE_VIEWS
240     QQuickView *getView() {
241         if (m_view) {
242             if (QString(QTest::currentTestFunction()) != testForView) {
243                 delete m_view;
244                 m_view = 0;
245             } else {
246                 m_view->setSource(QUrl());
247                 return m_view;
248             }
249         }
250
251         testForView = QTest::currentTestFunction();
252         m_view = createView();
253         return m_view;
254     }
255     void releaseView(QQuickView *view) {
256         Q_ASSERT(view == m_view);
257         m_view->setSource(QUrl());
258     }
259 #else
260     QQuickView *getView() {
261         return createView();
262     }
263     void releaseView(QQuickView *view) {
264         delete view;
265     }
266 #endif
267
268     static void errorMsgHandler(QtMsgType, const char *)
269     {
270         ++m_errorCount;
271     }
272
273     QQuickView *m_view;
274     QString testForView;
275     static int m_errorCount;
276 };
277
278 int tst_QQuickListView::m_errorCount = 0;
279
280 class TestObject : public QObject
281 {
282     Q_OBJECT
283
284     Q_PROPERTY(bool error READ error WRITE setError NOTIFY changedError)
285     Q_PROPERTY(bool animate READ animate NOTIFY changedAnim)
286     Q_PROPERTY(bool invalidHighlight READ invalidHighlight NOTIFY changedHl)
287     Q_PROPERTY(int cacheBuffer READ cacheBuffer NOTIFY changedCacheBuffer)
288
289 public:
290     TestObject(QObject *parent = 0)
291         : QObject(parent), mError(true), mAnimate(false), mInvalidHighlight(false)
292         , mCacheBuffer(0) {}
293
294     bool error() const { return mError; }
295     void setError(bool err) { mError = err; emit changedError(); }
296
297     bool animate() const { return mAnimate; }
298     void setAnimate(bool anim) { mAnimate = anim; emit changedAnim(); }
299
300     bool invalidHighlight() const { return mInvalidHighlight; }
301     void setInvalidHighlight(bool invalid) { mInvalidHighlight = invalid; emit changedHl(); }
302
303     int cacheBuffer() const { return mCacheBuffer; }
304     void setCacheBuffer(int buffer) { mCacheBuffer = buffer; emit changedCacheBuffer(); }
305
306 signals:
307     void changedError();
308     void changedAnim();
309     void changedHl();
310     void changedCacheBuffer();
311
312 public:
313     bool mError;
314     bool mAnimate;
315     bool mInvalidHighlight;
316     int mCacheBuffer;
317 };
318
319 tst_QQuickListView::tst_QQuickListView() : m_view(0)
320 {
321 }
322
323 void tst_QQuickListView::init()
324 {
325 #ifdef SHARE_VIEWS
326     if (m_view && QString(QTest::currentTestFunction()) != testForView) {
327         testForView = QString();
328         delete m_view;
329         m_view = 0;
330     }
331 #endif
332 }
333
334 template <class T>
335 void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
336 {
337     QQuickView *canvas = createView();
338
339     T model;
340     model.addItem("Fred", "12345");
341     model.addItem("John", "2345");
342     model.addItem("Bob", "54321");
343
344     QQmlContext *ctxt = canvas->rootContext();
345     ctxt->setContextProperty("testModel", &model);
346
347     TestObject *testObject = new TestObject;
348     ctxt->setContextProperty("testObject", testObject);
349
350     canvas->setSource(source);
351     qApp->processEvents();
352
353     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
354     QTRY_VERIFY(listview != 0);
355
356     QQuickItem *contentItem = listview->contentItem();
357     QTRY_VERIFY(contentItem != 0);
358
359     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
360     QTRY_VERIFY(testObject->error() == false);
361
362     QTRY_VERIFY(listview->highlightItem() != 0);
363     QTRY_COMPARE(listview->count(), model.count());
364     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
365     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
366
367     // current item should be first item
368     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
369
370     for (int i = 0; i < model.count(); ++i) {
371         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
372         QTRY_VERIFY(name != 0);
373         QTRY_COMPARE(name->text(), model.name(i));
374         QQuickText *number = findItem<QQuickText>(contentItem, "textNumber", i);
375         QTRY_VERIFY(number != 0);
376         QTRY_COMPARE(number->text(), model.number(i));
377     }
378
379     // switch to other delegate
380     testObject->setAnimate(true);
381     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
382     QTRY_VERIFY(testObject->error() == false);
383     QTRY_VERIFY(listview->currentItem());
384
385     // set invalid highlight
386     testObject->setInvalidHighlight(true);
387     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
388     QTRY_VERIFY(testObject->error() == false);
389     QTRY_VERIFY(listview->currentItem());
390     QTRY_VERIFY(listview->highlightItem() == 0);
391
392     // back to normal highlight
393     testObject->setInvalidHighlight(false);
394     QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
395     QTRY_VERIFY(testObject->error() == false);
396     QTRY_VERIFY(listview->currentItem());
397     QTRY_VERIFY(listview->highlightItem() != 0);
398
399     // set an empty model and confirm that items are destroyed
400     T model2;
401     ctxt->setContextProperty("testModel", &model2);
402
403     // Force a layout, necessary if ListView is completed before VisualDataModel.
404     if (forceLayout)
405         QCOMPARE(listview->property("count").toInt(), 0);
406
407     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
408     QTRY_VERIFY(itemCount == 0);
409
410     QTRY_COMPARE(listview->highlightResizeSpeed(), 1000.0);
411     QTRY_COMPARE(listview->highlightMoveSpeed(), 1000.0);
412
413     delete canvas;
414     delete testObject;
415 }
416
417
418 template <class T>
419 void tst_QQuickListView::changed(const QUrl &source, bool forceLayout)
420 {
421     QQuickView *canvas = createView();
422
423     T model;
424     model.addItem("Fred", "12345");
425     model.addItem("John", "2345");
426     model.addItem("Bob", "54321");
427
428     QQmlContext *ctxt = canvas->rootContext();
429     ctxt->setContextProperty("testModel", &model);
430
431     TestObject *testObject = new TestObject;
432     ctxt->setContextProperty("testObject", testObject);
433
434     canvas->setSource(source);
435     qApp->processEvents();
436
437     QQuickFlickable *listview = findItem<QQuickFlickable>(canvas->rootObject(), "list");
438     QTRY_VERIFY(listview != 0);
439
440     QQuickItem *contentItem = listview->contentItem();
441     QTRY_VERIFY(contentItem != 0);
442
443     // Force a layout, necessary if ListView is completed before VisualDataModel.
444     if (forceLayout)
445         QCOMPARE(listview->property("count").toInt(), model.count());
446
447     model.modifyItem(1, "Will", "9876");
448     QQuickText *name = findItem<QQuickText>(contentItem, "textName", 1);
449     QTRY_VERIFY(name != 0);
450     QTRY_COMPARE(name->text(), model.name(1));
451     QQuickText *number = findItem<QQuickText>(contentItem, "textNumber", 1);
452     QTRY_VERIFY(number != 0);
453     QTRY_COMPARE(number->text(), model.number(1));
454
455     delete canvas;
456     delete testObject;
457 }
458
459 template <class T>
460 void tst_QQuickListView::inserted(const QUrl &source)
461 {
462     QQuickView *canvas = createView();
463     canvas->show();
464
465     T model;
466     model.addItem("Fred", "12345");
467     model.addItem("John", "2345");
468     model.addItem("Bob", "54321");
469
470     QQmlContext *ctxt = canvas->rootContext();
471     ctxt->setContextProperty("testModel", &model);
472
473     TestObject *testObject = new TestObject;
474     ctxt->setContextProperty("testObject", testObject);
475
476     canvas->setSource(source);
477     qApp->processEvents();
478
479     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
480     QTRY_VERIFY(listview != 0);
481
482     QQuickItem *contentItem = listview->contentItem();
483     QTRY_VERIFY(contentItem != 0);
484
485     model.insertItem(1, "Will", "9876");
486
487     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
488     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
489
490     QQuickText *name = findItem<QQuickText>(contentItem, "textName", 1);
491     QTRY_VERIFY(name != 0);
492     QTRY_COMPARE(name->text(), model.name(1));
493     QQuickText *number = findItem<QQuickText>(contentItem, "textNumber", 1);
494     QTRY_VERIFY(number != 0);
495     QTRY_COMPARE(number->text(), model.number(1));
496
497     // Confirm items positioned correctly
498     for (int i = 0; i < model.count(); ++i) {
499         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
500         QTRY_COMPARE(item->y(), i*20.0);
501     }
502
503     model.insertItem(0, "Foo", "1111"); // zero index, and current item
504
505     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
506     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
507
508     name = findItem<QQuickText>(contentItem, "textName", 0);
509     QTRY_VERIFY(name != 0);
510     QTRY_COMPARE(name->text(), model.name(0));
511     number = findItem<QQuickText>(contentItem, "textNumber", 0);
512     QTRY_VERIFY(number != 0);
513     QTRY_COMPARE(number->text(), model.number(0));
514
515     QTRY_COMPARE(listview->currentIndex(), 1);
516
517     // Confirm items positioned correctly
518     for (int i = 0; i < model.count(); ++i) {
519         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
520         QTRY_COMPARE(item->y(), i*20.0);
521     }
522
523     for (int i = model.count(); i < 30; ++i)
524         model.insertItem(i, "Hello", QString::number(i));
525
526     listview->setContentY(80);
527
528     // Insert item outside visible area
529     model.insertItem(1, "Hello", "1324");
530
531     QTRY_VERIFY(listview->contentY() == 80);
532
533     // Confirm items positioned correctly
534     for (int i = 5; i < 5+15; ++i) {
535         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
536         if (!item) qWarning() << "Item" << i << "not found";
537         QTRY_VERIFY(item);
538         QTRY_COMPARE(item->y(), i*20.0 - 20.0);
539     }
540
541 //    QTRY_COMPARE(listview->contentItemHeight(), model.count() * 20.0);
542
543     // QTBUG-19675
544     model.clear();
545     model.insertItem(0, "Hello", "1234");
546     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
547
548     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
549     QVERIFY(item);
550     QCOMPARE(item->y(), 0.);
551     QTRY_VERIFY(listview->contentY() == 0);
552
553     delete canvas;
554     delete testObject;
555 }
556
557 template <class T>
558 void tst_QQuickListView::inserted_more(QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
559 {
560     QFETCH(qreal, contentY);
561     QFETCH(int, insertIndex);
562     QFETCH(int, insertCount);
563     QFETCH(qreal, itemsOffsetAfterMove);
564
565     T model;
566     for (int i = 0; i < 30; i++)
567         model.addItem("Item" + QString::number(i), "");
568
569     QQuickView *canvas = getView();
570     QQmlContext *ctxt = canvas->rootContext();
571     ctxt->setContextProperty("testModel", &model);
572
573     TestObject *testObject = new TestObject;
574     ctxt->setContextProperty("testObject", testObject);
575
576     canvas->setSource(testFileUrl("listviewtest.qml"));
577     canvas->show();
578     qApp->processEvents();
579     QTest::qWaitForWindowShown(canvas);
580
581     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
582     QTRY_VERIFY(listview != 0);
583     QQuickItem *contentItem = listview->contentItem();
584     QTRY_VERIFY(contentItem != 0);
585
586     bool waitForPolish = (contentY != 0);
587     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
588         listview->setVerticalLayoutDirection(verticalLayoutDirection);
589         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
590         contentY = -listview->height() - contentY;
591     }
592     listview->setContentY(contentY);
593     if (waitForPolish)
594         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
595
596     QList<QPair<QString, QString> > newData;
597     for (int i=0; i<insertCount; i++)
598         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
599     model.insertItems(insertIndex, newData);
600     QTRY_COMPARE(listview->property("count").toInt(), model.count());
601
602     // check visibleItems.first() is in correct position
603     QQuickItem *item0 = findItem<QQuickItem>(contentItem, "wrapper", 0);
604     QVERIFY(item0);
605     if (verticalLayoutDirection == QQuickItemView::BottomToTop)
606         QCOMPARE(item0->y(), -item0->height() - itemsOffsetAfterMove);
607     else
608         QCOMPARE(item0->y(), itemsOffsetAfterMove);
609
610     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
611     int firstVisibleIndex = -1;
612     for (int i=0; i<items.count(); i++) {
613         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
614         if (item && !QQuickItemPrivate::get(item)->culled) {
615             firstVisibleIndex = i;
616             break;
617         }
618     }
619     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
620
621     // Confirm items positioned correctly and indexes correct
622     QQuickText *name;
623     QQuickText *number;
624     for (int i = firstVisibleIndex; i < model.count() && i < items.count(); ++i) {
625         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
626         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
627         qreal pos = i*20.0 + itemsOffsetAfterMove;
628         if (verticalLayoutDirection == QQuickItemView::BottomToTop)
629             pos = -item0->height() - pos;
630         QTRY_COMPARE(item->y(), pos);
631         name = findItem<QQuickText>(contentItem, "textName", i);
632         QVERIFY(name != 0);
633         QTRY_COMPARE(name->text(), model.name(i));
634         number = findItem<QQuickText>(contentItem, "textNumber", i);
635         QVERIFY(number != 0);
636         QTRY_COMPARE(number->text(), model.number(i));
637     }
638
639     releaseView(canvas);
640     delete testObject;
641 }
642
643 void tst_QQuickListView::inserted_more_data()
644 {
645     QTest::addColumn<qreal>("contentY");
646     QTest::addColumn<int>("insertIndex");
647     QTest::addColumn<int>("insertCount");
648     QTest::addColumn<qreal>("itemsOffsetAfterMove");
649
650     QTest::newRow("add 1, before visible items")
651             << 80.0     // show 4-19
652             << 3 << 1
653             << -20.0;   // insert above first visible i.e. 0 is at -20, first visible should not move
654
655     QTest::newRow("add multiple, before visible")
656             << 80.0     // show 4-19
657             << 3 << 3
658             << -20.0 * 3;   // again first visible should not move
659
660     QTest::newRow("add 1, at start of visible, content at start")
661             << 0.0
662             << 0 << 1
663             << 0.0;
664
665     QTest::newRow("add multiple, start of visible, content at start")
666             << 0.0
667             << 0 << 3
668             << 0.0;
669
670     QTest::newRow("add 1, at start of visible, content not at start")
671             << 80.0     // show 4-19
672             << 4 << 1
673             << 0.0;
674
675     QTest::newRow("add multiple, at start of visible, content not at start")
676             << 80.0     // show 4-19
677             << 4 << 3
678             << 0.0;
679
680
681     QTest::newRow("add 1, at end of visible, content at start")
682             << 0.0
683             << 15 << 1
684             << 0.0;
685
686     QTest::newRow("add 1, at end of visible, content at start")
687             << 0.0
688             << 15 << 3
689             << 0.0;
690
691     QTest::newRow("add 1, at end of visible, content not at start")
692             << 80.0     // show 4-19
693             << 19 << 1
694             << 0.0;
695
696     QTest::newRow("add multiple, at end of visible, content not at start")
697             << 80.0     // show 4-19
698             << 19 << 3
699             << 0.0;
700
701
702     QTest::newRow("add 1, after visible, content at start")
703             << 0.0
704             << 16 << 1
705             << 0.0;
706
707     QTest::newRow("add 1, after visible, content at start")
708             << 0.0
709             << 16 << 3
710             << 0.0;
711
712     QTest::newRow("add 1, after visible, content not at start")
713             << 80.0     // show 4-19
714             << 20 << 1
715             << 0.0;
716
717     QTest::newRow("add multiple, after visible, content not at start")
718             << 80.0     // show 4-19
719             << 20 << 3
720             << 0.0;
721 }
722
723 void tst_QQuickListView::insertBeforeVisible()
724 {
725     QFETCH(int, insertIndex);
726     QFETCH(int, insertCount);
727     QFETCH(int, cacheBuffer);
728
729     QQuickText *name;
730     QQuickView *canvas = getView();
731
732     QmlListModel model;
733     for (int i = 0; i < 30; i++)
734         model.addItem("Item" + QString::number(i), "");
735
736     QQmlContext *ctxt = canvas->rootContext();
737     ctxt->setContextProperty("testModel", &model);
738
739     TestObject *testObject = new TestObject;
740     ctxt->setContextProperty("testObject", testObject);
741
742     canvas->setSource(testFileUrl("listviewtest.qml"));
743     canvas->show();
744     qApp->processEvents();
745     QTest::qWaitForWindowShown(canvas);
746
747     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
748     QTRY_VERIFY(listview != 0);
749     QQuickItem *contentItem = listview->contentItem();
750     QTRY_VERIFY(contentItem != 0);
751
752     listview->setCacheBuffer(cacheBuffer);
753     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
754
755     // trigger a refill (not just setting contentY) so that the visibleItems grid is updated
756     int firstVisibleIndex = 20;     // move to an index where the top item is not visible
757     listview->setContentY(firstVisibleIndex * 20.0);
758     listview->setCurrentIndex(firstVisibleIndex);
759
760     qApp->processEvents();
761     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
762     QTRY_COMPARE(listview->currentIndex(), firstVisibleIndex);
763     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", firstVisibleIndex);
764     QVERIFY(item);
765     QCOMPARE(item->y(), listview->contentY());
766
767     QList<QPair<QString, QString> > newData;
768     for (int i=0; i<insertCount; i++)
769         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
770     model.insertItems(insertIndex, newData);
771     QTRY_COMPARE(listview->property("count").toInt(), model.count());
772
773     // now, moving to the top of the view should position the inserted items correctly
774     int itemsOffsetAfterMove = -(insertCount * 20);
775     listview->setCurrentIndex(0);
776     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
777     QTRY_COMPARE(listview->currentIndex(), 0);
778     QTRY_COMPARE(listview->contentY(), 0.0 + itemsOffsetAfterMove);
779
780     // Confirm items positioned correctly and indexes correct
781     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
782     for (int i = 0; i < model.count() && i < itemCount; ++i) {
783         item = findItem<QQuickItem>(contentItem, "wrapper", i);
784         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
785         QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
786         name = findItem<QQuickText>(contentItem, "textName", i);
787         QVERIFY(name != 0);
788         QTRY_COMPARE(name->text(), model.name(i));
789     }
790
791     releaseView(canvas);
792     delete testObject;
793 }
794
795 void tst_QQuickListView::insertBeforeVisible_data()
796 {
797     QTest::addColumn<int>("insertIndex");
798     QTest::addColumn<int>("insertCount");
799     QTest::addColumn<int>("cacheBuffer");
800
801     QTest::newRow("insert 1 at 0, 0 buffer") << 0 << 1 << 0;
802     QTest::newRow("insert 1 at 0, 100 buffer") << 0 << 1 << 100;
803     QTest::newRow("insert 1 at 0, 500 buffer") << 0 << 1 << 500;
804
805     QTest::newRow("insert 1 at 1, 0 buffer") << 1 << 1 << 0;
806     QTest::newRow("insert 1 at 1, 100 buffer") << 1 << 1 << 100;
807     QTest::newRow("insert 1 at 1, 500 buffer") << 1 << 1 << 500;
808
809     QTest::newRow("insert multiple at 0, 0 buffer") << 0 << 3 << 0;
810     QTest::newRow("insert multiple at 0, 100 buffer") << 0 << 3 << 100;
811     QTest::newRow("insert multiple at 0, 500 buffer") << 0 << 3 << 500;
812
813     QTest::newRow("insert multiple at 1, 0 buffer") << 1 << 3 << 0;
814     QTest::newRow("insert multiple at 1, 100 buffer") << 1 << 3 << 100;
815     QTest::newRow("insert multiple at 1, 500 buffer") << 1 << 3 << 500;
816 }
817
818 template <class T>
819 void tst_QQuickListView::removed(const QUrl &source, bool /* animated */)
820 {
821     QQuickView *canvas = createView();
822
823     T model;
824     for (int i = 0; i < 50; i++)
825         model.addItem("Item" + QString::number(i), "");
826
827     QQmlContext *ctxt = canvas->rootContext();
828     ctxt->setContextProperty("testModel", &model);
829
830     TestObject *testObject = new TestObject;
831     ctxt->setContextProperty("testObject", testObject);
832
833     canvas->setSource(source);
834     canvas->show();
835     qApp->processEvents();
836     QTest::qWaitForWindowShown(canvas);
837
838     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
839     QTRY_VERIFY(listview != 0);
840     QQuickItem *contentItem = listview->contentItem();
841     QTRY_VERIFY(contentItem != 0);
842     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
843
844     model.removeItem(1);
845     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
846
847     QQuickText *name = findItem<QQuickText>(contentItem, "textName", 1);
848     QTRY_VERIFY(name != 0);
849     QTRY_COMPARE(name->text(), model.name(1));
850     QQuickText *number = findItem<QQuickText>(contentItem, "textNumber", 1);
851     QTRY_VERIFY(number != 0);
852     QTRY_COMPARE(number->text(), model.number(1));
853
854     // Confirm items positioned correctly
855     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
856     for (int i = 0; i < model.count() && i < itemCount; ++i) {
857         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
858         if (!item) qWarning() << "Item" << i << "not found";
859         QTRY_VERIFY(item);
860         QTRY_VERIFY(item->y() == i*20);
861     }
862
863     // Remove first item (which is the current item);
864     model.removeItem(0);
865     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
866
867     name = findItem<QQuickText>(contentItem, "textName", 0);
868     QTRY_VERIFY(name != 0);
869     QTRY_COMPARE(name->text(), model.name(0));
870     number = findItem<QQuickText>(contentItem, "textNumber", 0);
871     QTRY_VERIFY(number != 0);
872     QTRY_COMPARE(number->text(), model.number(0));
873
874     // Confirm items positioned correctly
875     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
876     for (int i = 0; i < model.count() && i < itemCount; ++i) {
877         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
878         if (!item) qWarning() << "Item" << i << "not found";
879         QTRY_VERIFY(item);
880         QTRY_COMPARE(item->y(),i*20.0);
881     }
882
883     // Remove items not visible
884     model.removeItem(18);
885     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
886
887     // Confirm items positioned correctly
888     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
889     for (int i = 0; i < model.count() && i < itemCount; ++i) {
890         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
891         if (!item) qWarning() << "Item" << i << "not found";
892         QTRY_VERIFY(item);
893         QTRY_COMPARE(item->y(),i*20.0);
894     }
895
896     // Remove items before visible
897     listview->setContentY(80);
898     listview->setCurrentIndex(10);
899
900     model.removeItem(1); // post: top item will be at 20
901     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
902
903     // Confirm items positioned correctly
904     for (int i = 2; i < 18; ++i) {
905         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
906         if (!item) qWarning() << "Item" << i << "not found";
907         QTRY_VERIFY(item);
908         QTRY_COMPARE(item->y(),20+i*20.0);
909     }
910
911     // Remove current index
912     QTRY_VERIFY(listview->currentIndex() == 9);
913     QQuickItem *oldCurrent = listview->currentItem();
914     model.removeItem(9);
915
916     QTRY_COMPARE(listview->currentIndex(), 9);
917     QTRY_VERIFY(listview->currentItem() != oldCurrent);
918
919     listview->setContentY(20); // That's the top now
920     // let transitions settle.
921     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
922
923     // Confirm items positioned correctly
924     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
925     for (int i = 0; i < model.count() && i < itemCount; ++i) {
926         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
927         if (!item) qWarning() << "Item" << i << "not found";
928         QTRY_VERIFY(item);
929         QTRY_COMPARE(item->y(),20+i*20.0);
930     }
931
932     // remove current item beyond visible items.
933     listview->setCurrentIndex(20);
934     listview->setContentY(40);
935     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
936
937     model.removeItem(20);
938     QTRY_COMPARE(listview->currentIndex(), 20);
939     QTRY_VERIFY(listview->currentItem() != 0);
940
941     // remove item before current, but visible
942     listview->setCurrentIndex(8);
943     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
944     oldCurrent = listview->currentItem();
945     model.removeItem(6);
946
947     QTRY_COMPARE(listview->currentIndex(), 7);
948     QTRY_VERIFY(listview->currentItem() == oldCurrent);
949
950     listview->setContentY(80);
951     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
952
953     // remove all visible items
954     model.removeItems(1, 18);
955     QTRY_COMPARE(listview->count() , model.count());
956
957     // Confirm items positioned correctly
958     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
959     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
960         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i+1);
961         if (!item) qWarning() << "Item" << i+1 << "not found";
962         QTRY_VERIFY(item);
963         QTRY_COMPARE(item->y(),80+i*20.0);
964     }
965
966     model.removeItems(1, 17);
967     QTRY_COMPARE(listview->count() , model.count());
968
969     model.removeItems(2, 1);
970     QTRY_COMPARE(listview->count() , model.count());
971
972     model.addItem("New", "1");
973     QTRY_COMPARE(listview->count() , model.count());
974
975     QTRY_VERIFY(name = findItem<QQuickText>(contentItem, "textName", model.count()-1));
976     QCOMPARE(name->text(), QString("New"));
977
978     // Add some more items so that we don't run out
979     model.clear();
980     for (int i = 0; i < 50; i++)
981         model.addItem("Item" + QString::number(i), "");
982
983     // QTBUG-QTBUG-20575
984     listview->setCurrentIndex(0);
985     listview->setContentY(30);
986     model.removeItem(0);
987     QTRY_VERIFY(name = findItem<QQuickText>(contentItem, "textName", 0));
988
989     // QTBUG-19198 move to end and remove all visible items one at a time.
990     listview->positionViewAtEnd();
991     for (int i = 0; i < 18; ++i)
992         model.removeItems(model.count() - 1, 1);
993     QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() > 16);
994
995     delete canvas;
996     delete testObject;
997 }
998
999 template <class T>
1000 void tst_QQuickListView::removed_more(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
1001 {
1002     QFETCH(qreal, contentY);
1003     QFETCH(int, removeIndex);
1004     QFETCH(int, removeCount);
1005     QFETCH(qreal, itemsOffsetAfterMove);
1006
1007     QQuickView *canvas = getView();
1008
1009     T model;
1010     for (int i = 0; i < 30; i++)
1011         model.addItem("Item" + QString::number(i), "");
1012
1013     QQmlContext *ctxt = canvas->rootContext();
1014     ctxt->setContextProperty("testModel", &model);
1015
1016     TestObject *testObject = new TestObject;
1017     ctxt->setContextProperty("testObject", testObject);
1018
1019     canvas->setSource(source);
1020     canvas->show();
1021     qApp->processEvents();
1022     QTest::qWaitForWindowShown(canvas);
1023
1024     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1025     QTRY_VERIFY(listview != 0);
1026     QQuickItem *contentItem = listview->contentItem();
1027     QTRY_VERIFY(contentItem != 0);
1028
1029     bool waitForPolish = (contentY != 0);
1030     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
1031         listview->setVerticalLayoutDirection(verticalLayoutDirection);
1032         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1033         contentY = -listview->height() - contentY;
1034     }
1035     listview->setContentY(contentY);
1036     if (waitForPolish)
1037         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1038
1039     model.removeItems(removeIndex, removeCount);
1040     QTRY_COMPARE(listview->property("count").toInt(), model.count());
1041
1042     // check visibleItems.first() is in correct position
1043     QQuickItem *item0 = findItem<QQuickItem>(contentItem, "wrapper", 0);
1044     QVERIFY(item0);
1045     QVERIFY(item0);
1046     if (verticalLayoutDirection == QQuickItemView::BottomToTop)
1047         QCOMPARE(item0->y(), -item0->height() - itemsOffsetAfterMove);
1048     else
1049         QCOMPARE(item0->y(), itemsOffsetAfterMove);
1050
1051     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
1052     int firstVisibleIndex = -1;
1053     for (int i=0; i<items.count(); i++) {
1054         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1055         if (item && delegateVisible(item)) {
1056             firstVisibleIndex = i;
1057             break;
1058         }
1059     }
1060     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
1061
1062     // Confirm items positioned correctly and indexes correct
1063     QQuickText *name;
1064     QQuickText *number;
1065     for (int i = firstVisibleIndex; i < model.count() && i < items.count(); ++i) {
1066         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1067         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1068         qreal pos = i*20.0 + itemsOffsetAfterMove;
1069         if (verticalLayoutDirection == QQuickItemView::BottomToTop)
1070             pos = -item0->height() - pos;
1071         QTRY_COMPARE(item->y(), pos);
1072         name = findItem<QQuickText>(contentItem, "textName", i);
1073         QVERIFY(name != 0);
1074         QTRY_COMPARE(name->text(), model.name(i));
1075         number = findItem<QQuickText>(contentItem, "textNumber", i);
1076         QVERIFY(number != 0);
1077         QTRY_COMPARE(number->text(), model.number(i));
1078     }
1079
1080     releaseView(canvas);
1081     delete testObject;
1082 }
1083
1084 void tst_QQuickListView::removed_more_data()
1085 {
1086     QTest::addColumn<qreal>("contentY");
1087     QTest::addColumn<int>("removeIndex");
1088     QTest::addColumn<int>("removeCount");
1089     QTest::addColumn<qreal>("itemsOffsetAfterMove");
1090
1091     QTest::newRow("remove 1, before visible items")
1092             << 80.0     // show 4-19
1093             << 3 << 1
1094             << 20.0;   // visible items slide down by 1 item so that first visible does not move
1095
1096     QTest::newRow("remove multiple, all before visible items")
1097             << 80.0
1098             << 1 << 3
1099             << 20.0 * 3;
1100
1101     QTest::newRow("remove multiple, all before visible items, remove item 0")
1102             << 80.0
1103             << 0 << 4
1104             << 20.0 * 4;
1105
1106     // remove 1,2,3 before the visible pos, 0 moves down to just before the visible pos,
1107     // items 4,5 are removed from view, item 6 slides up to original pos of item 4 (80px)
1108     QTest::newRow("remove multiple, mix of items from before and within visible items")
1109             << 80.0
1110             << 1 << 5
1111             << 20.0 * 3;    // adjust for the 3 items removed before the visible
1112
1113     QTest::newRow("remove multiple, mix of items from before and within visible items, remove item 0")
1114             << 80.0
1115             << 0 << 6
1116             << 20.0 * 4;    // adjust for the 3 items removed before the visible
1117
1118
1119     QTest::newRow("remove 1, from start of visible, content at start")
1120             << 0.0
1121             << 0 << 1
1122             << 0.0;
1123
1124     QTest::newRow("remove multiple, from start of visible, content at start")
1125             << 0.0
1126             << 0 << 3
1127             << 0.0;
1128
1129     QTest::newRow("remove 1, from start of visible, content not at start")
1130             << 80.0     // show 4-19
1131             << 4 << 1
1132             << 0.0;
1133
1134     QTest::newRow("remove multiple, from start of visible, content not at start")
1135             << 80.0     // show 4-19
1136             << 4 << 3
1137             << 0.0;
1138
1139
1140     QTest::newRow("remove 1, from middle of visible, content at start")
1141             << 0.0
1142             << 10 << 1
1143             << 0.0;
1144
1145     QTest::newRow("remove multiple, from middle of visible, content at start")
1146             << 0.0
1147             << 10 << 5
1148             << 0.0;
1149
1150     QTest::newRow("remove 1, from middle of visible, content not at start")
1151             << 80.0     // show 4-19
1152             << 10 << 1
1153             << 0.0;
1154
1155     QTest::newRow("remove multiple, from middle of visible, content not at start")
1156             << 80.0     // show 4-19
1157             << 10 << 5
1158             << 0.0;
1159
1160
1161     QTest::newRow("remove 1, after visible, content at start")
1162             << 0.0
1163             << 16 << 1
1164             << 0.0;
1165
1166     QTest::newRow("remove multiple, after visible, content at start")
1167             << 0.0
1168             << 16 << 5
1169             << 0.0;
1170
1171     QTest::newRow("remove 1, after visible, content not at middle")
1172             << 80.0     // show 4-19
1173             << 16+4 << 1
1174             << 0.0;
1175
1176     QTest::newRow("remove multiple, after visible, content not at start")
1177             << 80.0     // show 4-19
1178             << 16+4 << 5
1179             << 0.0;
1180
1181     QTest::newRow("remove multiple, mix of items from within and after visible items")
1182             << 80.0
1183             << 18 << 5
1184             << 0.0;
1185 }
1186
1187 template <class T>
1188 void tst_QQuickListView::clear(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
1189 {
1190     QQuickView *canvas = createView();
1191
1192     T model;
1193     for (int i = 0; i < 30; i++)
1194         model.addItem("Item" + QString::number(i), "");
1195
1196     QQmlContext *ctxt = canvas->rootContext();
1197     ctxt->setContextProperty("testModel", &model);
1198
1199     TestObject *testObject = new TestObject;
1200     ctxt->setContextProperty("testObject", testObject);
1201
1202     canvas->setSource(source);
1203     canvas->show();
1204     qApp->processEvents();
1205
1206     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1207     QTRY_VERIFY(listview != 0);
1208     QQuickItem *contentItem = listview->contentItem();
1209     QTRY_VERIFY(contentItem != 0);
1210
1211     listview->setVerticalLayoutDirection(verticalLayoutDirection);
1212     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1213
1214     model.clear();
1215
1216     QTRY_COMPARE(findItems<QQuickListView>(contentItem, "wrapper").count(), 0);
1217     QTRY_VERIFY(listview->count() == 0);
1218     QTRY_VERIFY(listview->currentItem() == 0);
1219     if (verticalLayoutDirection == QQuickItemView::TopToBottom)
1220         QTRY_COMPARE(listview->contentY(), 0.0);
1221     else
1222         QTRY_COMPARE(listview->contentY(), -listview->height());
1223     QVERIFY(listview->currentIndex() == -1);
1224
1225     QCOMPARE(listview->contentHeight(), 0.0);
1226
1227     // confirm sanity when adding an item to cleared list
1228     model.addItem("New", "1");
1229     QTRY_VERIFY(listview->count() == 1);
1230     QVERIFY(listview->currentItem() != 0);
1231     QVERIFY(listview->currentIndex() == 0);
1232
1233     delete canvas;
1234     delete testObject;
1235 }
1236
1237 template <class T>
1238 void tst_QQuickListView::moved(const QUrl &source, QQuickItemView::VerticalLayoutDirection verticalLayoutDirection)
1239 {
1240     QFETCH(qreal, contentY);
1241     QFETCH(int, from);
1242     QFETCH(int, to);
1243     QFETCH(int, count);
1244     QFETCH(qreal, itemsOffsetAfterMove);
1245
1246     QQuickText *name;
1247     QQuickText *number;
1248     QQuickView *canvas = getView();
1249
1250     T model;
1251     for (int i = 0; i < 30; i++)
1252         model.addItem("Item" + QString::number(i), "");
1253
1254     QQmlContext *ctxt = canvas->rootContext();
1255     ctxt->setContextProperty("testModel", &model);
1256
1257     TestObject *testObject = new TestObject;
1258     ctxt->setContextProperty("testObject", testObject);
1259
1260     canvas->setSource(source);
1261     canvas->show();
1262     qApp->processEvents();
1263     QTest::qWaitForWindowShown(canvas);
1264
1265     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1266     QTRY_VERIFY(listview != 0);
1267     QQuickItem *contentItem = listview->contentItem();
1268     QTRY_VERIFY(contentItem != 0);
1269
1270     // always need to wait for view to be painted before the first move()
1271     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1272
1273     bool waitForPolish = (contentY != 0);
1274     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
1275         listview->setVerticalLayoutDirection(verticalLayoutDirection);
1276         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1277         contentY = -listview->height() - contentY;
1278     }
1279     listview->setContentY(contentY);
1280     if (waitForPolish)
1281         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1282
1283     model.moveItems(from, to, count);
1284     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1285
1286     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
1287     int firstVisibleIndex = -1;
1288     for (int i=0; i<items.count(); i++) {
1289         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1290         if (item && delegateVisible(item)) {
1291             firstVisibleIndex = i;
1292             break;
1293         }
1294     }
1295     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
1296
1297     // Confirm items positioned correctly and indexes correct
1298     for (int i = firstVisibleIndex; i < model.count() && i < items.count(); ++i) {
1299         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1300         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1301         qreal pos = i*20.0 + itemsOffsetAfterMove;
1302         if (verticalLayoutDirection == QQuickItemView::BottomToTop)
1303             pos = -item->height() - pos;
1304         QTRY_COMPARE(item->y(), pos);
1305         name = findItem<QQuickText>(contentItem, "textName", i);
1306         QVERIFY(name != 0);
1307         QTRY_COMPARE(name->text(), model.name(i));
1308         number = findItem<QQuickText>(contentItem, "textNumber", i);
1309         QVERIFY(number != 0);
1310         QTRY_COMPARE(number->text(), model.number(i));
1311
1312         // current index should have been updated
1313         if (item == listview->currentItem())
1314             QTRY_COMPARE(listview->currentIndex(), i);
1315     }
1316
1317     releaseView(canvas);
1318     delete testObject;
1319 }
1320
1321 void tst_QQuickListView::moved_data()
1322 {
1323     QTest::addColumn<qreal>("contentY");
1324     QTest::addColumn<int>("from");
1325     QTest::addColumn<int>("to");
1326     QTest::addColumn<int>("count");
1327     QTest::addColumn<qreal>("itemsOffsetAfterMove");
1328
1329     // model starts with 30 items, each 20px high, in area 320px high
1330     // 16 items should be visible at a time
1331     // itemsOffsetAfterMove should be > 0 whenever items above the visible pos have moved
1332
1333     QTest::newRow("move 1 forwards, within visible items")
1334             << 0.0
1335             << 1 << 4 << 1
1336             << 0.0;
1337
1338     QTest::newRow("move 1 forwards, from non-visible -> visible")
1339             << 80.0     // show 4-19
1340             << 1 << 18 << 1
1341             << 20.0;    // removed 1 item above the first visible, so item 0 should drop down by 1 to minimize movement
1342
1343     QTest::newRow("move 1 forwards, from non-visible -> visible (move first item)")
1344             << 80.0     // show 4-19
1345             << 0 << 4 << 1
1346             << 20.0;    // first item has moved to below item4, everything drops down by size of 1 item
1347
1348     QTest::newRow("move 1 forwards, from visible -> non-visible")
1349             << 0.0
1350             << 1 << 16 << 1
1351             << 0.0;
1352
1353     QTest::newRow("move 1 forwards, from visible -> non-visible (move first item)")
1354             << 0.0
1355             << 0 << 16 << 1
1356             << 0.0;
1357
1358
1359     QTest::newRow("move 1 backwards, within visible items")
1360             << 0.0
1361             << 4 << 1 << 1
1362             << 0.0;
1363
1364     QTest::newRow("move 1 backwards, within visible items (to first index)")
1365             << 0.0
1366             << 4 << 0 << 1
1367             << 0.0;
1368
1369     QTest::newRow("move 1 backwards, from non-visible -> visible")
1370             << 0.0
1371             << 20 << 4 << 1
1372             << 0.0;
1373
1374     QTest::newRow("move 1 backwards, from non-visible -> visible (move last item)")
1375             << 0.0
1376             << 29 << 15 << 1
1377             << 0.0;
1378
1379     QTest::newRow("move 1 backwards, from visible -> non-visible")
1380             << 80.0     // show 4-19
1381             << 16 << 1 << 1
1382             << -20.0;   // to minimize movement, item 0 moves to -20, and other items do not move
1383
1384     QTest::newRow("move 1 backwards, from visible -> non-visible (move first item)")
1385             << 80.0     // show 4-19
1386             << 16 << 0 << 1
1387             << -20.0;   // to minimize movement, item 16 (now at 0) moves to -20, and other items do not move
1388
1389
1390     QTest::newRow("move multiple forwards, within visible items")
1391             << 0.0
1392             << 0 << 5 << 3
1393             << 0.0;
1394
1395     QTest::newRow("move multiple forwards, before visible items")
1396             << 140.0     // show 7-22
1397             << 4 << 5 << 3      // 4,5,6 move to below 7
1398             << 20.0 * 3;      // 4,5,6 moved down
1399
1400     QTest::newRow("move multiple forwards, from non-visible -> visible")
1401             << 80.0     // show 4-19
1402             << 1 << 5 << 3
1403             << 20.0 * 3;    // moving 3 from above the content y should adjust y positions accordingly
1404
1405     QTest::newRow("move multiple forwards, from non-visible -> visible (move first item)")
1406             << 80.0     // show 4-19
1407             << 0 << 5 << 3
1408             << 20.0 * 3;        // moving 3 from above the content y should adjust y positions accordingly
1409
1410     QTest::newRow("move multiple forwards, mix of non-visible/visible")
1411             << 40.0
1412             << 1 << 16 << 2
1413             << 20.0;    // item 1,2 are removed, item 3 is now first visible
1414
1415     QTest::newRow("move multiple forwards, to bottom of view")
1416             << 0.0
1417             << 5 << 13 << 3
1418             << 0.0;
1419
1420     QTest::newRow("move multiple forwards, to bottom of view, first->last")
1421             << 0.0
1422             << 0 << 13 << 3
1423             << 0.0;
1424
1425     QTest::newRow("move multiple forwards, to bottom of view, content y not 0")
1426             << 80.0
1427             << 5+4 << 13+4 << 3
1428             << 0.0;
1429
1430     QTest::newRow("move multiple forwards, from visible -> non-visible")
1431             << 0.0
1432             << 1 << 16 << 3
1433             << 0.0;
1434
1435     QTest::newRow("move multiple forwards, from visible -> non-visible (move first item)")
1436             << 0.0
1437             << 0 << 16 << 3
1438             << 0.0;
1439
1440
1441     QTest::newRow("move multiple backwards, within visible items")
1442             << 0.0
1443             << 4 << 1 << 3
1444             << 0.0;
1445
1446     QTest::newRow("move multiple backwards, within visible items (move first item)")
1447             << 0.0
1448             << 10 << 0 << 3
1449             << 0.0;
1450
1451     QTest::newRow("move multiple backwards, from non-visible -> visible")
1452             << 0.0
1453             << 20 << 4 << 3
1454             << 0.0;
1455
1456     QTest::newRow("move multiple backwards, from non-visible -> visible (move last item)")
1457             << 0.0
1458             << 27 << 10 << 3
1459             << 0.0;
1460
1461     QTest::newRow("move multiple backwards, from visible -> non-visible")
1462             << 80.0     // show 4-19
1463             << 16 << 1 << 3
1464             << -20.0 * 3;   // to minimize movement, 0 moves by -60, and other items do not move
1465
1466     QTest::newRow("move multiple backwards, from visible -> non-visible (move first item)")
1467             << 80.0     // show 4-19
1468             << 16 << 0 << 3
1469             << -20.0 * 3;   // to minimize movement, 16,17,18 move to above item 0, and other items do not move
1470 }
1471
1472 void tst_QQuickListView::multipleChanges(bool condensed)
1473 {
1474     QFETCH(int, startCount);
1475     QFETCH(QList<ListChange>, changes);
1476     QFETCH(int, newCount);
1477     QFETCH(int, newCurrentIndex);
1478
1479     QQuickView *canvas = getView();
1480
1481     QmlListModel model;
1482     for (int i = 0; i < startCount; i++)
1483         model.addItem("Item" + QString::number(i), "");
1484
1485     QQmlContext *ctxt = canvas->rootContext();
1486     ctxt->setContextProperty("testModel", &model);
1487
1488     TestObject *testObject = new TestObject;
1489     ctxt->setContextProperty("testObject", testObject);
1490
1491     canvas->setSource(testFileUrl("listviewtest.qml"));
1492     canvas->show();
1493     qApp->processEvents();
1494     QTest::qWaitForWindowShown(canvas);
1495
1496     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1497     QTRY_VERIFY(listview != 0);
1498     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1499
1500     for (int i=0; i<changes.count(); i++) {
1501         switch (changes[i].type) {
1502             case ListChange::Inserted:
1503             {
1504                 QList<QPair<QString, QString> > items;
1505                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
1506                     items << qMakePair(QString("new item %1").arg(j), QString::number(j));
1507                 model.insertItems(changes[i].index, items);
1508                 break;
1509             }
1510             case ListChange::Removed:
1511                 model.removeItems(changes[i].index, changes[i].count);
1512                 break;
1513             case ListChange::Moved:
1514                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
1515                 break;
1516             case ListChange::SetCurrent:
1517                 listview->setCurrentIndex(changes[i].index);
1518                 break;
1519             case ListChange::SetContentY:
1520                 listview->setContentY(changes[i].pos);
1521                 break;
1522             default:
1523                 continue;
1524         }
1525         if (!condensed) {
1526             QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1527         }
1528     }
1529     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1530
1531     QCOMPARE(listview->count(), newCount);
1532     QCOMPARE(listview->count(), model.count());
1533     QCOMPARE(listview->currentIndex(), newCurrentIndex);
1534
1535     QQuickText *name;
1536     QQuickText *number;
1537     QQuickItem *contentItem = listview->contentItem();
1538     QTRY_VERIFY(contentItem != 0);
1539     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
1540     for (int i=0; i < model.count() && i < itemCount; ++i) {
1541         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1542         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
1543         name = findItem<QQuickText>(contentItem, "textName", i);
1544         QVERIFY(name != 0);
1545         QTRY_COMPARE(name->text(), model.name(i));
1546         number = findItem<QQuickText>(contentItem, "textNumber", i);
1547         QVERIFY(number != 0);
1548         QTRY_COMPARE(number->text(), model.number(i));
1549     }
1550
1551     delete testObject;
1552     releaseView(canvas);
1553 }
1554
1555 void tst_QQuickListView::multipleChanges_data()
1556 {
1557     QTest::addColumn<int>("startCount");
1558     QTest::addColumn<QList<ListChange> >("changes");
1559     QTest::addColumn<int>("newCount");
1560     QTest::addColumn<int>("newCurrentIndex");
1561
1562     QList<ListChange> changes;
1563
1564     for (int i=1; i<30; i++)
1565         changes << ListChange::remove(0);
1566     QTest::newRow("remove all but 1, first->last") << 30 << changes << 1 << 0;
1567
1568     changes << ListChange::remove(0);
1569     QTest::newRow("remove all") << 30 << changes << 0 << -1;
1570
1571     changes.clear();
1572     changes << ListChange::setCurrent(29);
1573     for (int i=29; i>0; i--)
1574         changes << ListChange::remove(i);
1575     QTest::newRow("remove last (current) -> first") << 30 << changes << 1 << 0;
1576
1577     QTest::newRow("remove then insert at 0") << 10 << (QList<ListChange>()
1578             << ListChange::remove(0, 1)
1579             << ListChange::insert(0, 1)
1580             ) << 10 << 1;
1581
1582     QTest::newRow("remove then insert at non-zero index") << 10 << (QList<ListChange>()
1583             << ListChange::setCurrent(2)
1584             << ListChange::remove(2, 1)
1585             << ListChange::insert(2, 1)
1586             ) << 10 << 3;
1587
1588     QTest::newRow("remove current then insert below it") << 10 << (QList<ListChange>()
1589             << ListChange::setCurrent(1)
1590             << ListChange::remove(1, 3)
1591             << ListChange::insert(2, 2)
1592             ) << 9 << 1;
1593
1594     QTest::newRow("remove current index then move it down") << 10 << (QList<ListChange>()
1595             << ListChange::setCurrent(2)
1596             << ListChange::remove(1, 3)
1597             << ListChange::move(1, 5, 1)
1598             ) << 7 << 5;
1599
1600     QTest::newRow("remove current index then move it up") << 10 << (QList<ListChange>()
1601             << ListChange::setCurrent(5)
1602             << ListChange::remove(4, 3)
1603             << ListChange::move(4, 1, 1)
1604             ) << 7 << 1;
1605
1606
1607     QTest::newRow("insert multiple times") << 0 << (QList<ListChange>()
1608             << ListChange::insert(0, 2)
1609             << ListChange::insert(0, 4)
1610             << ListChange::insert(0, 6)
1611             ) << 12 << 10;
1612
1613     QTest::newRow("insert multiple times with current index changes") << 0 << (QList<ListChange>()
1614             << ListChange::insert(0, 2)
1615             << ListChange::insert(0, 4)
1616             << ListChange::insert(0, 6)
1617             << ListChange::setCurrent(3)
1618             << ListChange::insert(3, 2)
1619             ) << 14 << 5;
1620
1621     QTest::newRow("insert and remove all") << 0 << (QList<ListChange>()
1622             << ListChange::insert(0, 30)
1623             << ListChange::remove(0, 30)
1624             ) << 0 << -1;
1625
1626     QTest::newRow("insert and remove current") << 30 << (QList<ListChange>()
1627             << ListChange::insert(1)
1628             << ListChange::setCurrent(1)
1629             << ListChange::remove(1)
1630             ) << 30 << 1;
1631
1632     QTest::newRow("insert before 0, then remove cross section of new and old items") << 10 << (QList<ListChange>()
1633             << ListChange::insert(0, 10)
1634             << ListChange::remove(5, 10)
1635             ) << 10 << 5;
1636
1637     QTest::newRow("insert multiple, then move new items to end") << 10 << (QList<ListChange>()
1638             << ListChange::insert(0, 3)
1639             << ListChange::move(0, 10, 3)
1640             ) << 13 << 0;
1641
1642     QTest::newRow("insert multiple, then move new and some old items to end") << 10 << (QList<ListChange>()
1643             << ListChange::insert(0, 3)
1644             << ListChange::move(0, 8, 5)
1645             ) << 13 << 11;
1646
1647     QTest::newRow("insert multiple at end, then move new and some old items to start") << 10 << (QList<ListChange>()
1648             << ListChange::setCurrent(9)
1649             << ListChange::insert(10, 3)
1650             << ListChange::move(8, 0, 5)
1651             ) << 13 << 1;
1652
1653
1654     QTest::newRow("move back and forth to same index") << 10 << (QList<ListChange>()
1655             << ListChange::setCurrent(1)
1656             << ListChange::move(1, 2, 2)
1657             << ListChange::move(2, 1, 2)
1658             ) << 10 << 1;
1659
1660     QTest::newRow("move forwards then back") << 10 << (QList<ListChange>()
1661             << ListChange::setCurrent(2)
1662             << ListChange::move(1, 2, 3)
1663             << ListChange::move(3, 0, 5)
1664             ) << 10 << 0;
1665
1666     QTest::newRow("move current, then remove it") << 10 << (QList<ListChange>()
1667             << ListChange::setCurrent(5)
1668             << ListChange::move(5, 0, 1)
1669             << ListChange::remove(0)
1670             ) << 9 << 0;
1671
1672     QTest::newRow("move current, then insert before it") << 10 << (QList<ListChange>()
1673             << ListChange::setCurrent(5)
1674             << ListChange::move(5, 0, 1)
1675             << ListChange::insert(0)
1676             ) << 11 << 1;
1677
1678     QTest::newRow("move multiple, then remove them") << 10 << (QList<ListChange>()
1679             << ListChange::setCurrent(1)
1680             << ListChange::move(5, 1, 3)
1681             << ListChange::remove(1, 3)
1682             ) << 7 << 1;
1683
1684     QTest::newRow("move multiple, then insert before them") << 10 << (QList<ListChange>()
1685             << ListChange::setCurrent(5)
1686             << ListChange::move(5, 1, 3)
1687             << ListChange::insert(1, 5)
1688             ) << 15 << 6;
1689
1690     QTest::newRow("move multiple, then insert after them") << 10 << (QList<ListChange>()
1691             << ListChange::setCurrent(3)
1692             << ListChange::move(0, 1, 2)
1693             << ListChange::insert(3, 5)
1694             ) << 15 << 8;
1695
1696     QTest::newRow("clear current") << 0 << (QList<ListChange>()
1697             << ListChange::insert(0, 5)
1698             << ListChange::setCurrent(-1)
1699             << ListChange::remove(0, 5)
1700             << ListChange::insert(0, 5)
1701             ) << 5 << -1;
1702
1703     QTest::newRow("remove, scroll") << 30 << (QList<ListChange>()
1704             << ListChange::remove(20, 5)
1705             << ListChange::setContentY(20)
1706             ) << 25 << 0;
1707
1708     QTest::newRow("insert, scroll") << 10 << (QList<ListChange>()
1709             << ListChange::insert(9, 5)
1710             << ListChange::setContentY(20)
1711             ) << 15 << 0;
1712
1713     QTest::newRow("move, scroll") << 20 << (QList<ListChange>()
1714             << ListChange::move(15, 8, 3)
1715             << ListChange::setContentY(0)
1716             ) << 20 << 0;
1717
1718     QTest::newRow("clear, insert, scroll") << 30 << (QList<ListChange>()
1719             << ListChange::setContentY(20)
1720             << ListChange::remove(0, 30)
1721             << ListChange::insert(0, 2)
1722             << ListChange::setContentY(0)
1723             ) << 2 << 0;
1724 }
1725
1726 void tst_QQuickListView::swapWithFirstItem()
1727 {
1728     QQuickView *canvas = createView();
1729
1730     QmlListModel model;
1731     for (int i = 0; i < 30; i++)
1732         model.addItem("Item" + QString::number(i), "");
1733
1734     QQmlContext *ctxt = canvas->rootContext();
1735     ctxt->setContextProperty("testModel", &model);
1736
1737     TestObject *testObject = new TestObject;
1738     ctxt->setContextProperty("testObject", testObject);
1739
1740     canvas->setSource(testFileUrl("listviewtest.qml"));
1741     canvas->show();
1742     qApp->processEvents();
1743
1744     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1745     QTRY_VERIFY(listview != 0);
1746     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1747
1748     // ensure content position is stable
1749     listview->setContentY(0);
1750     model.moveItem(1, 0);
1751     QTRY_VERIFY(listview->contentY() == 0);
1752
1753     delete testObject;
1754     delete canvas;
1755 }
1756
1757 void tst_QQuickListView::enforceRange()
1758 {
1759     QQuickView *canvas = createView();
1760
1761     QmlListModel model;
1762     for (int i = 0; i < 30; i++)
1763         model.addItem("Item" + QString::number(i), "");
1764
1765     QQmlContext *ctxt = canvas->rootContext();
1766     ctxt->setContextProperty("testModel", &model);
1767
1768     canvas->setSource(testFileUrl("listview-enforcerange.qml"));
1769     canvas->show();
1770     qApp->processEvents();
1771
1772     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1773     QTRY_VERIFY(listview != 0);
1774
1775     QTRY_COMPARE(listview->preferredHighlightBegin(), 100.0);
1776     QTRY_COMPARE(listview->preferredHighlightEnd(), 100.0);
1777     QTRY_COMPARE(listview->highlightRangeMode(), QQuickListView::StrictlyEnforceRange);
1778     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1779
1780     QQuickItem *contentItem = listview->contentItem();
1781     QTRY_VERIFY(contentItem != 0);
1782
1783     // view should be positioned at the top of the range.
1784     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
1785     QTRY_VERIFY(item);
1786     QTRY_COMPARE(listview->contentY(), -100.0);
1787
1788     QQuickText *name = findItem<QQuickText>(contentItem, "textName", 0);
1789     QTRY_VERIFY(name != 0);
1790     QTRY_COMPARE(name->text(), model.name(0));
1791     QQuickText *number = findItem<QQuickText>(contentItem, "textNumber", 0);
1792     QTRY_VERIFY(number != 0);
1793     QTRY_COMPARE(number->text(), model.number(0));
1794
1795     // Check currentIndex is updated when contentItem moves
1796     listview->setContentY(20);
1797
1798     QTRY_COMPARE(listview->currentIndex(), 6);
1799
1800     // change model
1801     QmlListModel model2;
1802     for (int i = 0; i < 5; i++)
1803         model2.addItem("Item" + QString::number(i), "");
1804
1805     ctxt->setContextProperty("testModel", &model2);
1806     QCOMPARE(listview->count(), 5);
1807
1808     delete canvas;
1809 }
1810
1811 void tst_QQuickListView::enforceRange_withoutHighlight()
1812 {
1813     // QTBUG-20287
1814     // If no highlight is set but StrictlyEnforceRange is used, the content should still move
1815     // to the correct position (i.e. to the next/previous item, not next/previous section)
1816     // when moving up/down via incrementCurrentIndex() and decrementCurrentIndex()
1817
1818     QQuickView *canvas = createView();
1819
1820     QmlListModel model;
1821     model.addItem("Item 0", "a");
1822     model.addItem("Item 1", "b");
1823     model.addItem("Item 2", "b");
1824     model.addItem("Item 3", "c");
1825
1826     QQmlContext *ctxt = canvas->rootContext();
1827     ctxt->setContextProperty("testModel", &model);
1828
1829     canvas->setSource(testFileUrl("listview-enforcerange-nohighlight.qml"));
1830     canvas->show();
1831     qApp->processEvents();
1832
1833     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1834     QTRY_VERIFY(listview != 0);
1835     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1836
1837     qreal expectedPos = -100.0;
1838
1839     expectedPos += 10.0;    // scroll past 1st section's delegate (10px height)
1840     QTRY_COMPARE(listview->contentY(), expectedPos);
1841
1842     expectedPos += 20 + 10;     // scroll past 1st section and section delegate of 2nd section
1843     QTest::keyClick(canvas, Qt::Key_Down);
1844
1845     QTRY_COMPARE(listview->contentY(), expectedPos);
1846
1847     expectedPos += 20;     // scroll past 1st item of 2nd section
1848     QTest::keyClick(canvas, Qt::Key_Down);
1849     QTRY_COMPARE(listview->contentY(), expectedPos);
1850
1851     expectedPos += 20 + 10;     // scroll past 2nd item of 2nd section and section delegate of 3rd section
1852     QTest::keyClick(canvas, Qt::Key_Down);
1853     QTRY_COMPARE(listview->contentY(), expectedPos);
1854
1855     delete canvas;
1856 }
1857
1858 void tst_QQuickListView::spacing()
1859 {
1860     QQuickView *canvas = createView();
1861
1862     QmlListModel model;
1863     for (int i = 0; i < 30; i++)
1864         model.addItem("Item" + QString::number(i), "");
1865
1866     QQmlContext *ctxt = canvas->rootContext();
1867     ctxt->setContextProperty("testModel", &model);
1868
1869     TestObject *testObject = new TestObject;
1870     ctxt->setContextProperty("testObject", testObject);
1871
1872     canvas->setSource(testFileUrl("listviewtest.qml"));
1873     canvas->show();
1874     qApp->processEvents();
1875
1876     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1877     QTRY_VERIFY(listview != 0);
1878
1879     QQuickItem *contentItem = listview->contentItem();
1880     QTRY_VERIFY(contentItem != 0);
1881     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1882
1883     // Confirm items positioned correctly
1884     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
1885     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1886         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1887         if (!item) qWarning() << "Item" << i << "not found";
1888         QTRY_VERIFY(item);
1889         QTRY_VERIFY(item->y() == i*20);
1890     }
1891
1892     listview->setSpacing(10);
1893     QTRY_VERIFY(listview->spacing() == 10);
1894
1895     // Confirm items positioned correctly
1896     QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() == 11);
1897     for (int i = 0; i < 11; ++i) {
1898         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1899         if (!item) qWarning() << "Item" << i << "not found";
1900         QTRY_VERIFY(item);
1901         QTRY_VERIFY(item->y() == i*30);
1902     }
1903
1904     listview->setSpacing(0);
1905
1906     // Confirm items positioned correctly
1907     QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() >= 16);
1908     for (int i = 0; i < 16; ++i) {
1909         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1910         if (!item) qWarning() << "Item" << i << "not found";
1911         QTRY_VERIFY(item);
1912         QTRY_COMPARE(item->y(), i*20.0);
1913     }
1914
1915     delete canvas;
1916     delete testObject;
1917 }
1918
1919 template <typename T>
1920 void tst_QQuickListView::sections(const QUrl &source)
1921 {
1922     QQuickView *canvas = createView();
1923
1924     T model;
1925     for (int i = 0; i < 30; i++)
1926         model.addItem("Item" + QString::number(i), QString::number(i/5));
1927
1928     QQmlContext *ctxt = canvas->rootContext();
1929     ctxt->setContextProperty("testModel", &model);
1930
1931     canvas->setSource(source);
1932     canvas->show();
1933     qApp->processEvents();
1934
1935     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
1936     QTRY_VERIFY(listview != 0);
1937
1938     QQuickItem *contentItem = listview->contentItem();
1939     QTRY_VERIFY(contentItem != 0);
1940
1941     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
1942
1943     // Confirm items positioned correctly
1944     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
1945     for (int i = 0; i < model.count() && i < itemCount; ++i) {
1946         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
1947         QVERIFY(item);
1948         QTRY_COMPARE(item->y(), qreal(i*20 + ((i+4)/5) * 20));
1949         QQuickText *next = findItem<QQuickText>(item, "nextSection");
1950         QCOMPARE(next->text().toInt(), (i+1)/5);
1951     }
1952
1953     QSignalSpy currentSectionChangedSpy(listview, SIGNAL(currentSectionChanged()));
1954
1955     // Remove section boundary
1956     model.removeItem(5);
1957     QTRY_COMPARE(listview->count(), model.count());
1958
1959     // New section header created
1960     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 5);
1961     QTRY_VERIFY(item);
1962     QTRY_COMPARE(item->height(), 40.0);
1963
1964     model.insertItem(3, "New Item", "0");
1965     QTRY_COMPARE(listview->count(), model.count());
1966
1967     // Section header moved
1968     item = findItem<QQuickItem>(contentItem, "wrapper", 5);
1969     QTRY_VERIFY(item);
1970     QTRY_COMPARE(item->height(), 20.0);
1971
1972     item = findItem<QQuickItem>(contentItem, "wrapper", 6);
1973     QTRY_VERIFY(item);
1974     QTRY_COMPARE(item->height(), 40.0);
1975
1976     // insert item which will become a section header
1977     model.insertItem(6, "Replace header", "1");
1978     QTRY_COMPARE(listview->count(), model.count());
1979
1980     item = findItem<QQuickItem>(contentItem, "wrapper", 6);
1981     QTRY_VERIFY(item);
1982     QTRY_COMPARE(item->height(), 40.0);
1983
1984     item = findItem<QQuickItem>(contentItem, "wrapper", 7);
1985     QTRY_VERIFY(item);
1986     QTRY_COMPARE(item->height(), 20.0);
1987
1988     QTRY_COMPARE(listview->currentSection(), QString("0"));
1989
1990     listview->setContentY(140);
1991     QTRY_COMPARE(listview->currentSection(), QString("1"));
1992
1993     QTRY_COMPARE(currentSectionChangedSpy.count(), 1);
1994
1995     listview->setContentY(20);
1996     QTRY_COMPARE(listview->currentSection(), QString("0"));
1997
1998     QTRY_COMPARE(currentSectionChangedSpy.count(), 2);
1999
2000     item = findItem<QQuickItem>(contentItem, "wrapper", 1);
2001     QTRY_VERIFY(item);
2002     QTRY_COMPARE(item->height(), 20.0);
2003
2004     // check that headers change when item changes
2005     listview->setContentY(0);
2006     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2007     model.modifyItem(0, "changed", "2");
2008     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2009
2010     item = findItem<QQuickItem>(contentItem, "wrapper", 1);
2011     QTRY_VERIFY(item);
2012     QTRY_COMPARE(item->height(), 40.0);
2013
2014     delete canvas;
2015 }
2016
2017 void tst_QQuickListView::sectionsDelegate()
2018 {
2019     QSKIP("QTBUG-24395");
2020
2021     QQuickView *canvas = createView();
2022
2023     QmlListModel model;
2024     for (int i = 0; i < 30; i++)
2025         model.addItem("Item" + QString::number(i), QString::number(i/5));
2026
2027     QQmlContext *ctxt = canvas->rootContext();
2028     ctxt->setContextProperty("testModel", &model);
2029
2030     canvas->setSource(testFileUrl("listview-sections_delegate.qml"));
2031     canvas->show();
2032     qApp->processEvents();
2033
2034     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2035     QTRY_VERIFY(listview != 0);
2036
2037     QQuickItem *contentItem = listview->contentItem();
2038     QTRY_VERIFY(contentItem != 0);
2039
2040     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2041
2042     // Confirm items positioned correctly
2043     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2044     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2045         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2046         QTRY_VERIFY(item);
2047         QTRY_COMPARE(item->y(), qreal(i*20 + ((i+5)/5) * 20));
2048         QQuickText *next = findItem<QQuickText>(item, "nextSection");
2049         QCOMPARE(next->text().toInt(), (i+1)/5);
2050     }
2051
2052     for (int i = 0; i < 3; ++i) {
2053         QQuickItem *item = findItem<QQuickItem>(contentItem, "sect_" + QString::number(i));
2054         QVERIFY(item);
2055         QTRY_COMPARE(item->y(), qreal(i*20*6));
2056     }
2057
2058     // ensure section header is maintained in view
2059     listview->setCurrentIndex(20);
2060     QTRY_VERIFY(listview->contentY() >= 200.0);
2061     listview->setCurrentIndex(0);
2062     QTRY_COMPARE(listview->contentY(), 0.0);
2063
2064     // change section
2065     model.modifyItem(0, "One", "aaa");
2066     model.modifyItem(1, "Two", "aaa");
2067     model.modifyItem(2, "Three", "aaa");
2068     model.modifyItem(3, "Four", "aaa");
2069     model.modifyItem(4, "Five", "aaa");
2070     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2071
2072     for (int i = 0; i < 3; ++i) {
2073         QQuickItem *item = findItem<QQuickItem>(contentItem,
2074                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2075         QVERIFY(item);
2076         QTRY_COMPARE(item->y(), qreal(i*20*6));
2077     }
2078
2079     // remove section boundary
2080     model.removeItem(5);
2081     QTRY_COMPARE(listview->count(), model.count());
2082     for (int i = 0; i < 3; ++i) {
2083         QQuickItem *item = findItem<QQuickItem>(contentItem,
2084                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2085         QVERIFY(item);
2086     }
2087
2088     // QTBUG-17606
2089     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "sect_1");
2090     QCOMPARE(items.count(), 1);
2091
2092     // QTBUG-17759
2093     model.modifyItem(0, "One", "aaa");
2094     model.modifyItem(1, "One", "aaa");
2095     model.modifyItem(2, "One", "aaa");
2096     model.modifyItem(3, "Four", "aaa");
2097     model.modifyItem(4, "Four", "aaa");
2098     model.modifyItem(5, "Four", "aaa");
2099     model.modifyItem(6, "Five", "aaa");
2100     model.modifyItem(7, "Five", "aaa");
2101     model.modifyItem(8, "Five", "aaa");
2102     model.modifyItem(9, "Two", "aaa");
2103     model.modifyItem(10, "Two", "aaa");
2104     model.modifyItem(11, "Two", "aaa");
2105     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2106     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "sect_aaa").count(), 1);
2107     canvas->rootObject()->setProperty("sectionProperty", "name");
2108     // ensure view has settled.
2109     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "sect_Four").count(), 1);
2110     for (int i = 0; i < 4; ++i) {
2111         QQuickItem *item = findItem<QQuickItem>(contentItem,
2112                 "sect_" + model.name(i*3));
2113         QVERIFY(item);
2114         QTRY_COMPARE(item->y(), qreal(i*20*4));
2115     }
2116
2117     delete canvas;
2118 }
2119
2120 void tst_QQuickListView::sectionsDragOutsideBounds_data()
2121 {
2122     QTest::addColumn<int>("distance");
2123     QTest::addColumn<int>("cacheBuffer");
2124
2125     QTest::newRow("500, no cache buffer") << 500 << 0;
2126     QTest::newRow("1000, no cache buffer") << 1000 << 0;
2127     QTest::newRow("500, cache buffer") << 500 << 320;
2128     QTest::newRow("1000, cache buffer") << 1000 << 320;
2129 }
2130
2131 void tst_QQuickListView::sectionsDragOutsideBounds()
2132 {
2133     QFETCH(int, distance);
2134     QFETCH(int, cacheBuffer);
2135
2136     QQuickView *canvas = getView();
2137
2138     QmlListModel model;
2139     for (int i = 0; i < 10; i++)
2140         model.addItem("Item" + QString::number(i), QString::number(i/5));
2141
2142     QQmlContext *ctxt = canvas->rootContext();
2143     ctxt->setContextProperty("testModel", &model);
2144
2145     canvas->setSource(testFileUrl("listview-sections_delegate.qml"));
2146     canvas->show();
2147     qApp->processEvents();
2148
2149     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2150     QTRY_VERIFY(listview != 0);
2151     listview->setCacheBuffer(cacheBuffer);
2152
2153     QQuickItem *contentItem = listview->contentItem();
2154     QTRY_VERIFY(contentItem != 0);
2155
2156     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2157
2158     // QTBUG-17769
2159     // Drag view up beyond bounds
2160     QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20,20));
2161     QTest::mouseMove(canvas, QPoint(20,0));
2162     QTest::mouseMove(canvas, QPoint(20,-50));
2163     QTest::mouseMove(canvas, QPoint(20,-distance));
2164     QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,-distance));
2165     // view should settle back at 0
2166     QTRY_COMPARE(listview->contentY(), 0.0);
2167
2168     QTest::mousePress(canvas, Qt::LeftButton, 0, QPoint(20,0));
2169     QTest::mouseMove(canvas, QPoint(20,20));
2170     QTest::mouseMove(canvas, QPoint(20,70));
2171     QTest::mouseMove(canvas, QPoint(20,distance));
2172
2173     QTest::mouseRelease(canvas, Qt::LeftButton, 0, QPoint(20,distance));
2174     // view should settle back at 0
2175     QTRY_COMPARE(listview->contentY(), 0.0);
2176
2177     releaseView(canvas);
2178 }
2179
2180 void tst_QQuickListView::sectionsPositioning()
2181 {
2182     QQuickView *canvas = createView();
2183
2184     QmlListModel model;
2185     for (int i = 0; i < 30; i++)
2186         model.addItem("Item" + QString::number(i), QString::number(i/5));
2187
2188     QQmlContext *ctxt = canvas->rootContext();
2189     ctxt->setContextProperty("testModel", &model);
2190
2191     canvas->setSource(testFileUrl("listview-sections_delegate.qml"));
2192     canvas->show();
2193     qApp->processEvents();
2194     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart | QQuickViewSection::NextLabelAtEnd)));
2195
2196     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2197     QTRY_VERIFY(listview != 0);
2198     QQuickItem *contentItem = listview->contentItem();
2199     QTRY_VERIFY(contentItem != 0);
2200     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2201
2202     for (int i = 0; i < 3; ++i) {
2203         QQuickItem *item = findItem<QQuickItem>(contentItem, "sect_" + QString::number(i));
2204         QVERIFY(item);
2205         QTRY_COMPARE(item->y(), qreal(i*20*6));
2206     }
2207
2208     QQuickItem *topItem = findVisibleChild(contentItem, "sect_0"); // section header
2209     QVERIFY(topItem);
2210     QCOMPARE(topItem->y(), 0.);
2211
2212     QQuickItem *bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
2213     QVERIFY(bottomItem);
2214     QCOMPARE(bottomItem->y(), 300.);
2215
2216     // move down a little and check that section header is at top
2217     listview->setContentY(10);
2218     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2219     QCOMPARE(topItem->y(), 0.);
2220
2221     // push the top header up
2222     listview->setContentY(110);
2223     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2224     topItem = findVisibleChild(contentItem, "sect_0"); // section header
2225     QVERIFY(topItem);
2226     QCOMPARE(topItem->y(), 100.);
2227
2228     QQuickItem *item = findVisibleChild(contentItem, "sect_1");
2229     QVERIFY(item);
2230     QCOMPARE(item->y(), 120.);
2231
2232     bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
2233     QVERIFY(bottomItem);
2234     QCOMPARE(bottomItem->y(), 410.);
2235
2236     // Move past section 0
2237     listview->setContentY(120);
2238     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2239     topItem = findVisibleChild(contentItem, "sect_0"); // section header
2240     QVERIFY(!topItem);
2241
2242     // Push section footer down
2243     listview->setContentY(70);
2244     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2245     bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
2246     QVERIFY(bottomItem);
2247     QCOMPARE(bottomItem->y(), 380.);
2248
2249     // Change current section, and verify case insensitive comparison
2250     listview->setContentY(10);
2251     model.modifyItem(0, "One", "aaa");
2252     model.modifyItem(1, "Two", "AAA");
2253     model.modifyItem(2, "Three", "aAa");
2254     model.modifyItem(3, "Four", "aaA");
2255     model.modifyItem(4, "Five", "Aaa");
2256     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2257
2258     QTRY_COMPARE(listview->currentSection(), QString("aaa"));
2259
2260     for (int i = 0; i < 3; ++i) {
2261         QQuickItem *item = findItem<QQuickItem>(contentItem,
2262                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2263         QVERIFY(item);
2264         QTRY_COMPARE(item->y(), qreal(i*20*6));
2265     }
2266
2267     QTRY_VERIFY(topItem = findVisibleChild(contentItem, "sect_aaa")); // section header
2268     QCOMPARE(topItem->y(), 10.);
2269
2270     // remove section boundary
2271     listview->setContentY(120);
2272     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2273     model.removeItem(5);
2274     QTRY_COMPARE(listview->count(), model.count());
2275     for (int i = 1; i < 3; ++i) {
2276         QQuickItem *item = findVisibleChild(contentItem,
2277                 "sect_" + QString::number(i));
2278         QVERIFY(item);
2279         QTRY_COMPARE(item->y(), qreal(i*20*6));
2280     }
2281
2282     QVERIFY(topItem = findVisibleChild(contentItem, "sect_1"));
2283     QTRY_COMPARE(topItem->y(), 120.);
2284
2285     // Change the next section
2286     listview->setContentY(0);
2287     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2288     bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
2289     QVERIFY(bottomItem);
2290     QTRY_COMPARE(bottomItem->y(), 300.);
2291
2292     model.modifyItem(14, "New", "new");
2293     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2294
2295     QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
2296     QTRY_COMPARE(bottomItem->y(), 300.);
2297
2298     // Turn sticky footer off
2299     listview->setContentY(20);
2300     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2301     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart)));
2302     QTRY_VERIFY(item = findVisibleChild(contentItem, "sect_new")); // inline label restored
2303     QCOMPARE(item->y(), 340.);
2304
2305     // Turn sticky header off
2306     listview->setContentY(30);
2307     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2308     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels)));
2309     QTRY_VERIFY(item = findVisibleChild(contentItem, "sect_aaa")); // inline label restored
2310     QCOMPARE(item->y(), 0.);
2311
2312     // if an empty model is set the header/footer should be cleaned up
2313     canvas->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart | QQuickViewSection::NextLabelAtEnd)));
2314     QTRY_VERIFY(findVisibleChild(contentItem, "sect_aaa")); // section header
2315     QTRY_VERIFY(findVisibleChild(contentItem, "sect_new")); // section footer
2316     QmlListModel model1;
2317     ctxt->setContextProperty("testModel", &model1);
2318     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_aaa")); // section header
2319     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_new")); // section footer
2320
2321     // clear model - header/footer should be cleaned up
2322     ctxt->setContextProperty("testModel", &model);
2323     QTRY_VERIFY(findVisibleChild(contentItem, "sect_aaa")); // section header
2324     QTRY_VERIFY(findVisibleChild(contentItem, "sect_new")); // section footer
2325     model.clear();
2326     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_aaa")); // section header
2327     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_new")); // section footer
2328
2329     delete canvas;
2330 }
2331
2332 void tst_QQuickListView::sectionPropertyChange()
2333 {
2334     QQuickView *canvas = createView();
2335
2336     canvas->setSource(testFileUrl("sectionpropertychange.qml"));
2337     canvas->show();
2338     qApp->processEvents();
2339
2340     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2341     QTRY_VERIFY(listview != 0);
2342
2343     QQuickItem *contentItem = listview->contentItem();
2344     QTRY_VERIFY(contentItem != 0);
2345
2346     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2347
2348     // Confirm items positioned correctly
2349     for (int i = 0; i < 2; ++i) {
2350         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2351         QTRY_VERIFY(item);
2352         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2353     }
2354
2355     QMetaObject::invokeMethod(canvas->rootObject(), "switchGroups");
2356     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2357
2358     // Confirm items positioned correctly
2359     for (int i = 0; i < 2; ++i) {
2360         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2361         QTRY_VERIFY(item);
2362         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2363     }
2364
2365     QMetaObject::invokeMethod(canvas->rootObject(), "switchGroups");
2366     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2367
2368     // Confirm items positioned correctly
2369     for (int i = 0; i < 2; ++i) {
2370         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2371         QTRY_VERIFY(item);
2372         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2373     }
2374
2375     delete canvas;
2376 }
2377
2378 void tst_QQuickListView::currentIndex_delayedItemCreation()
2379 {
2380     QFETCH(bool, setCurrentToZero);
2381
2382     QQuickView *canvas = getView();
2383
2384     // test currentIndexChanged() is emitted even if currentIndex = 0 on start up
2385     // (since the currentItem will have changed and that shares the same index)
2386     canvas->rootContext()->setContextProperty("setCurrentToZero", setCurrentToZero);
2387
2388     canvas->setSource(testFileUrl("fillModelOnComponentCompleted.qml"));
2389     qApp->processEvents();
2390
2391     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2392     QTRY_VERIFY(listview != 0);
2393     QQuickItem *contentItem = listview->contentItem();
2394     QTRY_VERIFY(contentItem != 0);
2395
2396     QSignalSpy spy(listview, SIGNAL(currentItemChanged()));
2397     QCOMPARE(listview->currentIndex(), 0);
2398     QTRY_COMPARE(spy.count(), 1);
2399
2400     releaseView(canvas);
2401 }
2402
2403 void tst_QQuickListView::currentIndex_delayedItemCreation_data()
2404 {
2405     QTest::addColumn<bool>("setCurrentToZero");
2406
2407     QTest::newRow("set to 0") << true;
2408     QTest::newRow("don't set to 0") << false;
2409 }
2410
2411 void tst_QQuickListView::currentIndex()
2412 {
2413     QmlListModel model;
2414     for (int i = 0; i < 30; i++)
2415         model.addItem("Item" + QString::number(i), QString::number(i));
2416
2417     QQuickView *canvas = new QQuickView(0);
2418     canvas->setGeometry(0,0,240,320);
2419
2420     QQmlContext *ctxt = canvas->rootContext();
2421     ctxt->setContextProperty("testModel", &model);
2422     ctxt->setContextProperty("testWrap", QVariant(false));
2423
2424     QString filename(testFile("listview-initCurrent.qml"));
2425     canvas->setSource(QUrl::fromLocalFile(filename));
2426     canvas->show();
2427     qApp->processEvents();
2428
2429     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2430     QTRY_VERIFY(listview != 0);
2431     QQuickItem *contentItem = listview->contentItem();
2432     QTRY_VERIFY(contentItem != 0);
2433     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2434
2435     // current item should be 20th item at startup
2436     // and current item should be in view
2437     QCOMPARE(listview->currentIndex(), 20);
2438     QCOMPARE(listview->contentY(), 100.0);
2439     QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 20));
2440     QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
2441
2442     listview->setCurrentIndex(0);
2443     QCOMPARE(listview->currentIndex(), 0);
2444     // confirm that the velocity is updated
2445     QTRY_VERIFY(listview->verticalVelocity() != 0.0);
2446
2447     // footer should become visible if it is out of view, and then current index is set to count-1
2448     canvas->rootObject()->setProperty("showFooter", true);
2449     QTRY_VERIFY(listview->footerItem());
2450     listview->setCurrentIndex(model.count()-2);
2451     QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
2452     listview->setCurrentIndex(model.count()-1);
2453     QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
2454     canvas->rootObject()->setProperty("showFooter", false);
2455
2456     // header should become visible if it is out of view, and then current index is set to 0
2457     canvas->rootObject()->setProperty("showHeader", true);
2458     QTRY_VERIFY(listview->headerItem());
2459     listview->setCurrentIndex(1);
2460     QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
2461     listview->setCurrentIndex(0);
2462     QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
2463     canvas->rootObject()->setProperty("showHeader", false);
2464
2465     // turn off auto highlight
2466     listview->setHighlightFollowsCurrentItem(false);
2467     QVERIFY(listview->highlightFollowsCurrentItem() == false);
2468
2469     QVERIFY(listview->highlightItem());
2470     qreal hlPos = listview->highlightItem()->y();
2471
2472     listview->setCurrentIndex(4);
2473     QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
2474
2475     // insert item before currentIndex
2476     listview->setCurrentIndex(28);
2477     model.insertItem(0, "Foo", "1111");
2478     QTRY_COMPARE(canvas->rootObject()->property("current").toInt(), 29);
2479
2480     // check removing highlight by setting currentIndex to -1;
2481     listview->setCurrentIndex(-1);
2482
2483     QCOMPARE(listview->currentIndex(), -1);
2484     QVERIFY(!listview->highlightItem());
2485     QVERIFY(!listview->currentItem());
2486
2487     // moving currentItem out of view should make it invisible
2488     listview->setCurrentIndex(0);
2489     QTRY_VERIFY(delegateVisible(listview->currentItem()));
2490     listview->setContentY(200);
2491     QTRY_VERIFY(!delegateVisible(listview->currentItem()));
2492
2493     delete canvas;
2494 }
2495
2496 void tst_QQuickListView::noCurrentIndex()
2497 {
2498     QmlListModel 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     QQmlContext *ctxt = canvas->rootContext();
2506     ctxt->setContextProperty("testModel", &model);
2507
2508     QString filename(testFile("listview-noCurrent.qml"));
2509     canvas->setSource(QUrl::fromLocalFile(filename));
2510     canvas->show();
2511     qApp->processEvents();
2512
2513     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2514     QTRY_VERIFY(listview != 0);
2515     QQuickItem *contentItem = listview->contentItem();
2516     QTRY_VERIFY(contentItem != 0);
2517     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
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::keyNavigation()
2535 {
2536     QFETCH(QQuickListView::Orientation, orientation);
2537     QFETCH(Qt::LayoutDirection, layoutDirection);
2538     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
2539     QFETCH(Qt::Key, forwardsKey);
2540     QFETCH(Qt::Key, backwardsKey);
2541     QFETCH(QPointF, contentPosAtFirstItem);
2542     QFETCH(QPointF, contentPosAtLastItem);
2543
2544     QmlListModel model;
2545     for (int i = 0; i < 30; i++)
2546         model.addItem("Item" + QString::number(i), "");
2547
2548     QQuickView *canvas = getView();
2549     TestObject *testObject = new TestObject;
2550     canvas->rootContext()->setContextProperty("testModel", &model);
2551     canvas->rootContext()->setContextProperty("testObject", testObject);
2552     canvas->setSource(testFileUrl("listviewtest.qml"));
2553     canvas->show();
2554     qApp->processEvents();
2555
2556     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2557     QTRY_VERIFY(listview != 0);
2558
2559     listview->setOrientation(orientation);
2560     listview->setLayoutDirection(layoutDirection);
2561     listview->setVerticalLayoutDirection(verticalLayoutDirection);
2562     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2563
2564     canvas->requestActivateWindow();
2565     QTRY_VERIFY(qGuiApp->focusWindow() == canvas);
2566     QCOMPARE(listview->currentIndex(), 0);
2567
2568     QTest::keyClick(canvas, forwardsKey);
2569     QCOMPARE(listview->currentIndex(), 1);
2570
2571     QTest::keyClick(canvas, backwardsKey);
2572     QCOMPARE(listview->currentIndex(), 0);
2573
2574     // hold down a key to go forwards
2575     for (int i=0; i<model.count()-1; i++) {
2576         QTest::simulateEvent(canvas, true, forwardsKey, Qt::NoModifier, "", true);
2577         QTRY_COMPARE(listview->currentIndex(), i+1);
2578     }
2579     QTest::keyRelease(canvas, forwardsKey);
2580     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
2581     QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
2582     QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
2583
2584     // hold down a key to go backwards
2585     for (int i=model.count()-1; i > 0; i--) {
2586         QTest::simulateEvent(canvas, true, backwardsKey, Qt::NoModifier, "", true);
2587         QTRY_COMPARE(listview->currentIndex(), i-1);
2588     }
2589     QTest::keyRelease(canvas, backwardsKey);
2590     QTRY_COMPARE(listview->currentIndex(), 0);
2591     QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
2592     QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
2593
2594     // no wrap
2595     QVERIFY(!listview->isWrapEnabled());
2596     listview->incrementCurrentIndex();
2597     QCOMPARE(listview->currentIndex(), 1);
2598     listview->decrementCurrentIndex();
2599     QCOMPARE(listview->currentIndex(), 0);
2600
2601     listview->decrementCurrentIndex();
2602     QCOMPARE(listview->currentIndex(), 0);
2603
2604     // with wrap
2605     listview->setWrapEnabled(true);
2606     QVERIFY(listview->isWrapEnabled());
2607
2608     listview->decrementCurrentIndex();
2609     QCOMPARE(listview->currentIndex(), model.count()-1);
2610     QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
2611     QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
2612
2613     listview->incrementCurrentIndex();
2614     QCOMPARE(listview->currentIndex(), 0);
2615     QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
2616     QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
2617
2618     releaseView(canvas);
2619     delete testObject;
2620 }
2621
2622 void tst_QQuickListView::keyNavigation_data()
2623 {
2624     QTest::addColumn<QQuickListView::Orientation>("orientation");
2625     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2626     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
2627     QTest::addColumn<Qt::Key>("forwardsKey");
2628     QTest::addColumn<Qt::Key>("backwardsKey");
2629     QTest::addColumn<QPointF>("contentPosAtFirstItem");
2630     QTest::addColumn<QPointF>("contentPosAtLastItem");
2631
2632     QTest::newRow("Vertical, TopToBottom")
2633             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
2634             << Qt::Key_Down << Qt::Key_Up
2635             << QPointF(0, 0)
2636             << QPointF(0, 30*20 - 320);
2637
2638     QTest::newRow("Vertical, BottomToTop")
2639             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
2640             << Qt::Key_Up << Qt::Key_Down
2641             << QPointF(0, -320)
2642             << QPointF(0, -(30 * 20));
2643
2644     QTest::newRow("Horizontal, LeftToRight")
2645             << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
2646             << Qt::Key_Right << Qt::Key_Left
2647             << QPointF(0, 0)
2648             << QPointF(30*240 - 240, 0);
2649
2650     QTest::newRow("Horizontal, RightToLeft")
2651             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
2652             << Qt::Key_Left << Qt::Key_Right
2653             << QPointF(-240, 0)
2654             << QPointF(-(30 * 240), 0);
2655 }
2656
2657 void tst_QQuickListView::itemList()
2658 {
2659     QQuickView *canvas = createView();
2660     canvas->setSource(testFileUrl("itemlist.qml"));
2661     canvas->show();
2662     qApp->processEvents();
2663
2664     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "view");
2665     QTRY_VERIFY(listview != 0);
2666
2667     QQuickItem *contentItem = listview->contentItem();
2668     QTRY_VERIFY(contentItem != 0);
2669
2670     QQuickVisualItemModel *model = canvas->rootObject()->findChild<QQuickVisualItemModel*>("itemModel");
2671     QTRY_VERIFY(model != 0);
2672
2673     QTRY_VERIFY(model->count() == 3);
2674     QTRY_COMPARE(listview->currentIndex(), 0);
2675
2676     QQuickItem *item = findItem<QQuickItem>(contentItem, "item1");
2677     QTRY_VERIFY(item);
2678     QTRY_COMPARE(item->x(), 0.0);
2679     QCOMPARE(item->height(), listview->height());
2680
2681     QQuickText *text = findItem<QQuickText>(contentItem, "text1");
2682     QTRY_VERIFY(text);
2683     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
2684
2685     listview->setCurrentIndex(2);
2686
2687     item = findItem<QQuickItem>(contentItem, "item3");
2688     QTRY_VERIFY(item);
2689     QTRY_COMPARE(item->x(), 480.0);
2690
2691     text = findItem<QQuickText>(contentItem, "text3");
2692     QTRY_VERIFY(text);
2693     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
2694
2695     delete canvas;
2696 }
2697
2698 void tst_QQuickListView::itemListFlicker()
2699 {
2700     QQuickView *canvas = createView();
2701     canvas->setSource(testFileUrl("itemlist-flicker.qml"));
2702     canvas->show();
2703     qApp->processEvents();
2704
2705     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "view");
2706     QTRY_VERIFY(listview != 0);
2707
2708     QQuickItem *contentItem = listview->contentItem();
2709     QTRY_VERIFY(contentItem != 0);
2710
2711     QQuickVisualItemModel *model = canvas->rootObject()->findChild<QQuickVisualItemModel*>("itemModel");
2712     QTRY_VERIFY(model != 0);
2713
2714     QTRY_VERIFY(model->count() == 3);
2715     QTRY_COMPARE(listview->currentIndex(), 0);
2716
2717     QQuickItem *item;
2718
2719     QVERIFY(item = findItem<QQuickItem>(contentItem, "item1"));
2720     QVERIFY(delegateVisible(item));
2721     QVERIFY(item = findItem<QQuickItem>(contentItem, "item2"));
2722     QVERIFY(delegateVisible(item));
2723     QVERIFY(item = findItem<QQuickItem>(contentItem, "item3"));
2724     QVERIFY(delegateVisible(item));
2725
2726     listview->setCurrentIndex(1);
2727
2728     QVERIFY(item = findItem<QQuickItem>(contentItem, "item1"));
2729     QVERIFY(delegateVisible(item));
2730     QVERIFY(item = findItem<QQuickItem>(contentItem, "item2"));
2731     QVERIFY(delegateVisible(item));
2732     QVERIFY(item = findItem<QQuickItem>(contentItem, "item3"));
2733     QVERIFY(delegateVisible(item));
2734
2735     listview->setCurrentIndex(2);
2736
2737     QVERIFY(item = findItem<QQuickItem>(contentItem, "item1"));
2738     QVERIFY(delegateVisible(item));
2739     QVERIFY(item = findItem<QQuickItem>(contentItem, "item2"));
2740     QVERIFY(delegateVisible(item));
2741     QVERIFY(item = findItem<QQuickItem>(contentItem, "item3"));
2742     QVERIFY(delegateVisible(item));
2743
2744     delete canvas;
2745 }
2746
2747 void tst_QQuickListView::cacheBuffer()
2748 {
2749     QQuickView *canvas = createView();
2750
2751     QmlListModel model;
2752     for (int i = 0; i < 90; i++)
2753         model.addItem("Item" + QString::number(i), "");
2754
2755     QQmlContext *ctxt = canvas->rootContext();
2756     ctxt->setContextProperty("testModel", &model);
2757
2758     TestObject *testObject = new TestObject;
2759     ctxt->setContextProperty("testObject", testObject);
2760
2761     canvas->setSource(testFileUrl("listviewtest.qml"));
2762     canvas->show();
2763     qApp->processEvents();
2764
2765     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2766     QTRY_VERIFY(listview != 0);
2767
2768     QQuickItem *contentItem = listview->contentItem();
2769     QTRY_VERIFY(contentItem != 0);
2770     QTRY_VERIFY(listview->delegate() != 0);
2771     QTRY_VERIFY(listview->model() != 0);
2772     QTRY_VERIFY(listview->highlight() != 0);
2773
2774     // Confirm items positioned correctly
2775     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2776     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2777         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2778         if (!item) qWarning() << "Item" << i << "not found";
2779         QTRY_VERIFY(item);
2780         QTRY_VERIFY(item->y() == i*20);
2781     }
2782
2783     QQmlIncubationController controller;
2784     canvas->engine()->setIncubationController(&controller);
2785
2786     testObject->setCacheBuffer(200);
2787     QTRY_VERIFY(listview->cacheBuffer() == 200);
2788
2789     // items will be created one at a time
2790     for (int i = itemCount; i < qMin(itemCount+10,model.count()); ++i) {
2791         QVERIFY(findItem<QQuickItem>(listview, "wrapper", i) == 0);
2792         QQuickItem *item = 0;
2793         while (!item) {
2794             bool b = false;
2795             controller.incubateWhile(&b);
2796             item = findItem<QQuickItem>(listview, "wrapper", i);
2797         }
2798     }
2799
2800     {
2801         bool b = true;
2802         controller.incubateWhile(&b);
2803     }
2804
2805     int newItemCount = 0;
2806     newItemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2807
2808     // Confirm items positioned correctly
2809     for (int i = 0; i < model.count() && i < newItemCount; ++i) {
2810         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2811         if (!item) qWarning() << "Item" << i << "not found";
2812         QTRY_VERIFY(item);
2813         QTRY_VERIFY(item->y() == i*20);
2814     }
2815
2816     // move view and confirm items in view are visible immediately and outside are created async
2817     listview->setContentY(300);
2818
2819     for (int i = 15; i < 32; ++i) {
2820         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2821         if (!item) qWarning() << "Item" << i << "not found";
2822         QVERIFY(item);
2823         QVERIFY(item->y() == i*20);
2824     }
2825
2826     QVERIFY(findItem<QQuickItem>(listview, "wrapper", 32) == 0);
2827
2828     // ensure buffered items are created
2829     for (int i = 32; i < qMin(41,model.count()); ++i) {
2830         QQuickItem *item = 0;
2831         while (!item) {
2832             qGuiApp->processEvents(); // allow refill to happen
2833             bool b = false;
2834             controller.incubateWhile(&b);
2835             item = findItem<QQuickItem>(listview, "wrapper", i);
2836         }
2837     }
2838
2839     {
2840         bool b = true;
2841         controller.incubateWhile(&b);
2842     }
2843
2844     delete canvas;
2845     delete testObject;
2846 }
2847
2848 void tst_QQuickListView::positionViewAtIndex()
2849 {
2850     QQuickView *canvas = createView();
2851
2852     QmlListModel model;
2853     for (int i = 0; i < 40; i++)
2854         model.addItem("Item" + QString::number(i), "");
2855
2856     QQmlContext *ctxt = canvas->rootContext();
2857     ctxt->setContextProperty("testModel", &model);
2858
2859     TestObject *testObject = new TestObject;
2860     ctxt->setContextProperty("testObject", testObject);
2861     canvas->show();
2862     canvas->setSource(testFileUrl("listviewtest.qml"));
2863     qApp->processEvents();
2864
2865     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
2866     QTRY_VERIFY(listview != 0);
2867     QQuickItem *contentItem = listview->contentItem();
2868     QTRY_VERIFY(contentItem != 0);
2869     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2870
2871     // Confirm items positioned correctly
2872     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2873     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2874         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2875         if (!item) qWarning() << "Item" << i << "not found";
2876         QTRY_VERIFY(item);
2877         QTRY_COMPARE(item->y(), i*20.);
2878     }
2879
2880     // Position on a currently visible item
2881     listview->positionViewAtIndex(3, QQuickListView::Beginning);
2882     QTRY_COMPARE(listview->contentY(), 60.);
2883
2884     // Confirm items positioned correctly
2885     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2886     for (int i = 3; i < model.count() && i < itemCount-3-1; ++i) {
2887         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2888         if (!item) qWarning() << "Item" << i << "not found";
2889         QTRY_VERIFY(item);
2890         QTRY_COMPARE(item->y(), i*20.);
2891     }
2892
2893     // Position on an item beyond the visible items
2894     listview->positionViewAtIndex(22, QQuickListView::Beginning);
2895     QTRY_COMPARE(listview->contentY(), 440.);
2896
2897     // Confirm items positioned correctly
2898     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2899     for (int i = 22; i < model.count() && i < itemCount-22-1; ++i) {
2900         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2901         if (!item) qWarning() << "Item" << i << "not found";
2902         QTRY_VERIFY(item);
2903         QTRY_COMPARE(item->y(), i*20.);
2904     }
2905
2906     // Position on an item that would leave empty space if positioned at the top
2907     listview->positionViewAtIndex(28, QQuickListView::Beginning);
2908     QTRY_COMPARE(listview->contentY(), 480.);
2909
2910     // Confirm items positioned correctly
2911     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2912     for (int i = 24; i < model.count() && i < itemCount-24-1; ++i) {
2913         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2914         if (!item) qWarning() << "Item" << i << "not found";
2915         QTRY_VERIFY(item);
2916         QTRY_COMPARE(item->y(), i*20.);
2917     }
2918
2919     // Position at the beginning again
2920     listview->positionViewAtIndex(0, QQuickListView::Beginning);
2921     QTRY_COMPARE(listview->contentY(), 0.);
2922
2923     // Confirm items positioned correctly
2924     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2925     for (int i = 0; i < model.count() && i < itemCount-1; ++i) {
2926         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2927         if (!item) qWarning() << "Item" << i << "not found";
2928         QTRY_VERIFY(item);
2929         QTRY_COMPARE(item->y(), i*20.);
2930     }
2931
2932     // Position at End using last index
2933     listview->positionViewAtIndex(model.count()-1, QQuickListView::End);
2934     QTRY_COMPARE(listview->contentY(), 480.);
2935
2936     // Confirm items positioned correctly
2937     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2938     for (int i = 24; i < model.count(); ++i) {
2939         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2940         if (!item) qWarning() << "Item" << i << "not found";
2941         QTRY_VERIFY(item);
2942         QTRY_COMPARE(item->y(), i*20.);
2943     }
2944
2945     // Position at End
2946     listview->positionViewAtIndex(20, QQuickListView::End);
2947     QTRY_COMPARE(listview->contentY(), 100.);
2948
2949     // Position in Center
2950     listview->positionViewAtIndex(15, QQuickListView::Center);
2951     QTRY_COMPARE(listview->contentY(), 150.);
2952
2953     // Ensure at least partially visible
2954     listview->positionViewAtIndex(15, QQuickListView::Visible);
2955     QTRY_COMPARE(listview->contentY(), 150.);
2956
2957     listview->setContentY(302);
2958     listview->positionViewAtIndex(15, QQuickListView::Visible);
2959     QTRY_COMPARE(listview->contentY(), 302.);
2960
2961     listview->setContentY(320);
2962     listview->positionViewAtIndex(15, QQuickListView::Visible);
2963     QTRY_COMPARE(listview->contentY(), 300.);
2964
2965     listview->setContentY(85);
2966     listview->positionViewAtIndex(20, QQuickListView::Visible);
2967     QTRY_COMPARE(listview->contentY(), 85.);
2968
2969     listview->setContentY(75);
2970     listview->positionViewAtIndex(20, QQuickListView::Visible);
2971     QTRY_COMPARE(listview->contentY(), 100.);
2972
2973     // Ensure completely visible
2974     listview->setContentY(120);
2975     listview->positionViewAtIndex(20, QQuickListView::Contain);
2976     QTRY_COMPARE(listview->contentY(), 120.);
2977
2978     listview->setContentY(302);
2979     listview->positionViewAtIndex(15, QQuickListView::Contain);
2980     QTRY_COMPARE(listview->contentY(), 300.);
2981
2982     listview->setContentY(85);
2983     listview->positionViewAtIndex(20, QQuickListView::Contain);
2984     QTRY_COMPARE(listview->contentY(), 100.);
2985
2986     // positionAtBeginnging
2987     listview->positionViewAtBeginning();
2988     QTRY_COMPARE(listview->contentY(), 0.);
2989
2990     listview->setContentY(80);
2991     canvas->rootObject()->setProperty("showHeader", true);
2992     listview->positionViewAtBeginning();
2993     QTRY_COMPARE(listview->contentY(), -30.);
2994
2995     // positionAtEnd
2996     listview->positionViewAtEnd();
2997     QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
2998
2999     listview->setContentY(80);
3000     canvas->rootObject()->setProperty("showFooter", true);
3001     listview->positionViewAtEnd();
3002     QTRY_COMPARE(listview->contentY(), 510.);
3003
3004     // set current item to outside visible view, position at beginning
3005     // and ensure highlight moves to current item
3006     listview->setCurrentIndex(1);
3007     listview->positionViewAtBeginning();
3008     QTRY_COMPARE(listview->contentY(), -30.);
3009     QVERIFY(listview->highlightItem());
3010     QCOMPARE(listview->highlightItem()->y(), 20.);
3011
3012     delete canvas;
3013     delete testObject;
3014 }
3015
3016 void tst_QQuickListView::resetModel()
3017 {
3018     QQuickView *canvas = createView();
3019
3020     QStringList strings;
3021     strings << "one" << "two" << "three";
3022     QStringListModel model(strings);
3023
3024     QQmlContext *ctxt = canvas->rootContext();
3025     ctxt->setContextProperty("testModel", &model);
3026
3027     canvas->setSource(testFileUrl("displaylist.qml"));
3028     canvas->show();
3029     qApp->processEvents();
3030
3031     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3032     QTRY_VERIFY(listview != 0);
3033     QQuickItem *contentItem = listview->contentItem();
3034     QTRY_VERIFY(contentItem != 0);
3035     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3036
3037     QTRY_COMPARE(listview->count(), model.rowCount());
3038
3039     for (int i = 0; i < model.rowCount(); ++i) {
3040         QQuickText *display = findItem<QQuickText>(contentItem, "displayText", i);
3041         QTRY_VERIFY(display != 0);
3042         QTRY_COMPARE(display->text(), strings.at(i));
3043     }
3044
3045     strings.clear();
3046     strings << "four" << "five" << "six" << "seven";
3047     model.setStringList(strings);
3048
3049     QTRY_COMPARE(listview->count(), model.rowCount());
3050
3051     for (int i = 0; i < model.rowCount(); ++i) {
3052         QQuickText *display = findItem<QQuickText>(contentItem, "displayText", i);
3053         QTRY_VERIFY(display != 0);
3054         QTRY_COMPARE(display->text(), strings.at(i));
3055     }
3056
3057     delete canvas;
3058 }
3059
3060 void tst_QQuickListView::propertyChanges()
3061 {
3062     QQuickView *canvas = createView();
3063     QTRY_VERIFY(canvas);
3064     canvas->setSource(testFileUrl("propertychangestest.qml"));
3065
3066     QQuickListView *listView = canvas->rootObject()->findChild<QQuickListView*>("listView");
3067     QTRY_VERIFY(listView);
3068
3069     QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
3070     QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
3071     QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
3072     QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
3073     QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
3074     QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
3075     QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
3076
3077     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
3078     QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
3079     QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
3080     QTRY_COMPARE(listView->highlightRangeMode(), QQuickListView::ApplyRange);
3081     QTRY_COMPARE(listView->isWrapEnabled(), true);
3082     QTRY_COMPARE(listView->cacheBuffer(), 10);
3083     QTRY_COMPARE(listView->snapMode(), QQuickListView::SnapToItem);
3084
3085     listView->setHighlightFollowsCurrentItem(false);
3086     listView->setPreferredHighlightBegin(1.0);
3087     listView->setPreferredHighlightEnd(1.0);
3088     listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
3089     listView->setWrapEnabled(false);
3090     listView->setCacheBuffer(3);
3091     listView->setSnapMode(QQuickListView::SnapOneItem);
3092
3093     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
3094     QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
3095     QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
3096     QTRY_COMPARE(listView->highlightRangeMode(), QQuickListView::StrictlyEnforceRange);
3097     QTRY_COMPARE(listView->isWrapEnabled(), false);
3098     QTRY_COMPARE(listView->cacheBuffer(), 3);
3099     QTRY_COMPARE(listView->snapMode(), QQuickListView::SnapOneItem);
3100
3101     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
3102     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
3103     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
3104     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
3105     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
3106     QTRY_COMPARE(cacheBufferSpy.count(),1);
3107     QTRY_COMPARE(snapModeSpy.count(),1);
3108
3109     listView->setHighlightFollowsCurrentItem(false);
3110     listView->setPreferredHighlightBegin(1.0);
3111     listView->setPreferredHighlightEnd(1.0);
3112     listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
3113     listView->setWrapEnabled(false);
3114     listView->setCacheBuffer(3);
3115     listView->setSnapMode(QQuickListView::SnapOneItem);
3116
3117     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
3118     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
3119     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
3120     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
3121     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
3122     QTRY_COMPARE(cacheBufferSpy.count(),1);
3123     QTRY_COMPARE(snapModeSpy.count(),1);
3124
3125     delete canvas;
3126 }
3127
3128 void tst_QQuickListView::componentChanges()
3129 {
3130     QQuickView *canvas = createView();
3131     QTRY_VERIFY(canvas);
3132     canvas->setSource(testFileUrl("propertychangestest.qml"));
3133
3134     QQuickListView *listView = canvas->rootObject()->findChild<QQuickListView*>("listView");
3135     QTRY_VERIFY(listView);
3136
3137     QQmlComponent component(canvas->engine());
3138     component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
3139
3140     QQmlComponent delegateComponent(canvas->engine());
3141     delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
3142
3143     QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
3144     QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
3145     QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
3146     QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
3147
3148     listView->setHighlight(&component);
3149     listView->setHeader(&component);
3150     listView->setFooter(&component);
3151     listView->setDelegate(&delegateComponent);
3152
3153     QTRY_COMPARE(listView->highlight(), &component);
3154     QTRY_COMPARE(listView->header(), &component);
3155     QTRY_COMPARE(listView->footer(), &component);
3156     QTRY_COMPARE(listView->delegate(), &delegateComponent);
3157
3158     QTRY_COMPARE(highlightSpy.count(),1);
3159     QTRY_COMPARE(delegateSpy.count(),1);
3160     QTRY_COMPARE(headerSpy.count(),1);
3161     QTRY_COMPARE(footerSpy.count(),1);
3162
3163     listView->setHighlight(&component);
3164     listView->setHeader(&component);
3165     listView->setFooter(&component);
3166     listView->setDelegate(&delegateComponent);
3167
3168     QTRY_COMPARE(highlightSpy.count(),1);
3169     QTRY_COMPARE(delegateSpy.count(),1);
3170     QTRY_COMPARE(headerSpy.count(),1);
3171     QTRY_COMPARE(footerSpy.count(),1);
3172
3173     delete canvas;
3174 }
3175
3176 void tst_QQuickListView::modelChanges()
3177 {
3178     QQuickView *canvas = createView();
3179     QTRY_VERIFY(canvas);
3180     canvas->setSource(testFileUrl("propertychangestest.qml"));
3181
3182     QQuickListView *listView = canvas->rootObject()->findChild<QQuickListView*>("listView");
3183     QTRY_VERIFY(listView);
3184
3185     QQuickListModel *alternateModel = canvas->rootObject()->findChild<QQuickListModel*>("alternateModel");
3186     QTRY_VERIFY(alternateModel);
3187     QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
3188     QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
3189
3190     listView->setModel(modelVariant);
3191     QTRY_COMPARE(listView->model(), modelVariant);
3192     QTRY_COMPARE(modelSpy.count(),1);
3193
3194     listView->setModel(modelVariant);
3195     QTRY_COMPARE(modelSpy.count(),1);
3196
3197     listView->setModel(QVariant());
3198     QTRY_COMPARE(modelSpy.count(),2);
3199
3200     delete canvas;
3201 }
3202
3203 void tst_QQuickListView::QTBUG_9791()
3204 {
3205     QQuickView *canvas = createView();
3206
3207     canvas->setSource(testFileUrl("strictlyenforcerange.qml"));
3208     qApp->processEvents();
3209
3210     QQuickListView *listview = qobject_cast<QQuickListView*>(canvas->rootObject());
3211     QTRY_VERIFY(listview != 0);
3212
3213     QQuickItem *contentItem = listview->contentItem();
3214     QTRY_VERIFY(contentItem != 0);
3215     QTRY_VERIFY(listview->delegate() != 0);
3216     QTRY_VERIFY(listview->model() != 0);
3217
3218     QMetaObject::invokeMethod(listview, "fillModel");
3219     qApp->processEvents();
3220
3221     // Confirm items positioned correctly
3222     int itemCount = findItems<QQuickItem>(contentItem, "wrapper", false).count();
3223     QCOMPARE(itemCount, 3);
3224
3225     for (int i = 0; i < itemCount; ++i) {
3226         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3227         if (!item) qWarning() << "Item" << i << "not found";
3228         QTRY_VERIFY(item);
3229         QTRY_COMPARE(item->x(), i*300.0);
3230     }
3231
3232     // check that view is positioned correctly
3233     QTRY_COMPARE(listview->contentX(), 590.0);
3234
3235     delete canvas;
3236 }
3237
3238 void tst_QQuickListView::manualHighlight()
3239 {
3240     QQuickView *canvas = new QQuickView(0);
3241     canvas->setGeometry(0,0,240,320);
3242
3243     QString filename(testFile("manual-highlight.qml"));
3244     canvas->setSource(QUrl::fromLocalFile(filename));
3245
3246     qApp->processEvents();
3247
3248     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3249     QTRY_VERIFY(listview != 0);
3250
3251     QQuickItem *contentItem = listview->contentItem();
3252     QTRY_VERIFY(contentItem != 0);
3253
3254     QTRY_COMPARE(listview->currentIndex(), 0);
3255     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
3256     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3257
3258     listview->setCurrentIndex(2);
3259
3260     QTRY_COMPARE(listview->currentIndex(), 2);
3261     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 2));
3262     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3263
3264     // QTBUG-15972
3265     listview->positionViewAtIndex(3, QQuickListView::Contain);
3266
3267     QTRY_COMPARE(listview->currentIndex(), 2);
3268     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 2));
3269     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3270
3271     delete canvas;
3272 }
3273
3274 void tst_QQuickListView::QTBUG_11105()
3275 {
3276     QQuickView *canvas = createView();
3277
3278     QmlListModel model;
3279     for (int i = 0; i < 30; i++)
3280         model.addItem("Item" + QString::number(i), "");
3281
3282     QQmlContext *ctxt = canvas->rootContext();
3283     ctxt->setContextProperty("testModel", &model);
3284
3285     TestObject *testObject = new TestObject;
3286     ctxt->setContextProperty("testObject", testObject);
3287
3288     canvas->setSource(testFileUrl("listviewtest.qml"));
3289     canvas->show();
3290     qApp->processEvents();
3291
3292     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3293     QTRY_VERIFY(listview != 0);
3294     QQuickItem *contentItem = listview->contentItem();
3295     QTRY_VERIFY(contentItem != 0);
3296     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3297
3298     // Confirm items positioned correctly
3299     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3300     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3301         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3302         if (!item) qWarning() << "Item" << i << "not found";
3303         QTRY_VERIFY(item);
3304         QTRY_VERIFY(item->y() == i*20);
3305     }
3306
3307     listview->positionViewAtIndex(20, QQuickListView::Beginning);
3308     QCOMPARE(listview->contentY(), 280.);
3309
3310     QmlListModel model2;
3311     for (int i = 0; i < 5; i++)
3312         model2.addItem("Item" + QString::number(i), "");
3313
3314     ctxt->setContextProperty("testModel", &model2);
3315
3316     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3317     QCOMPARE(itemCount, 5);
3318
3319     delete canvas;
3320     delete testObject;
3321 }
3322
3323 void tst_QQuickListView::initialZValues()
3324 {
3325     QQuickView *canvas = createView();
3326     canvas->setSource(testFileUrl("initialZValues.qml"));
3327     qApp->processEvents();
3328
3329     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3330     QTRY_VERIFY(listview != 0);
3331     QQuickItem *contentItem = listview->contentItem();
3332     QTRY_VERIFY(contentItem != 0);
3333
3334     QVERIFY(listview->headerItem());
3335     QTRY_COMPARE(listview->headerItem()->z(), listview->property("initialZ").toReal());
3336
3337     QVERIFY(listview->footerItem());
3338     QTRY_COMPARE(listview->footerItem()->z(), listview->property("initialZ").toReal());
3339
3340     delete canvas;
3341 }
3342
3343 void tst_QQuickListView::header()
3344 {
3345     QFETCH(QQuickListView::Orientation, orientation);
3346     QFETCH(Qt::LayoutDirection, layoutDirection);
3347     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3348     QFETCH(QPointF, initialHeaderPos);
3349     QFETCH(QPointF, changedHeaderPos);
3350     QFETCH(QPointF, initialContentPos);
3351     QFETCH(QPointF, changedContentPos);
3352     QFETCH(QPointF, firstDelegatePos);
3353     QFETCH(QPointF, resizeContentPos);
3354
3355     QmlListModel model;
3356     for (int i = 0; i < 30; i++)
3357         model.addItem("Item" + QString::number(i), "");
3358
3359     QQuickView *canvas = getView();
3360     canvas->rootContext()->setContextProperty("testModel", &model);
3361     canvas->rootContext()->setContextProperty("initialViewWidth", 240);
3362     canvas->rootContext()->setContextProperty("initialViewHeight", 320);
3363     canvas->setSource(testFileUrl("header.qml"));
3364     canvas->show();
3365     qApp->processEvents();
3366
3367     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3368     QTRY_VERIFY(listview != 0);
3369     listview->setOrientation(orientation);
3370     listview->setLayoutDirection(layoutDirection);
3371     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3372     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3373
3374     QQuickItem *contentItem = listview->contentItem();
3375     QTRY_VERIFY(contentItem != 0);
3376
3377     QQuickText *header = 0;
3378     QTRY_VERIFY(header = findItem<QQuickText>(contentItem, "header"));
3379     QVERIFY(header == listview->headerItem());
3380
3381     QCOMPARE(header->width(), 100.);
3382     QCOMPARE(header->height(), 30.);
3383     QCOMPARE(header->pos(), initialHeaderPos);
3384     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3385
3386     if (orientation == QQuickListView::Vertical)
3387         QCOMPARE(listview->contentHeight(), model.count() * 30. + header->height());
3388     else
3389         QCOMPARE(listview->contentWidth(), model.count() * 240. + header->width());
3390
3391     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3392     QVERIFY(item);
3393     QCOMPARE(item->pos(), firstDelegatePos);
3394
3395     model.clear();
3396     QTRY_COMPARE(listview->count(), model.count());
3397     QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
3398     if (orientation == QQuickListView::Vertical)
3399         QCOMPARE(listview->contentHeight(), header->height());
3400     else
3401         QCOMPARE(listview->contentWidth(), header->width());
3402
3403     for (int i = 0; i < 30; i++)
3404         model.addItem("Item" + QString::number(i), "");
3405
3406     QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
3407     QMetaObject::invokeMethod(canvas->rootObject(), "changeHeader");
3408
3409     QCOMPARE(headerItemSpy.count(), 1);
3410
3411     header = findItem<QQuickText>(contentItem, "header");
3412     QVERIFY(!header);
3413     header = findItem<QQuickText>(contentItem, "header2");
3414     QVERIFY(header);
3415
3416     QVERIFY(header == listview->headerItem());
3417
3418     QCOMPARE(header->pos(), changedHeaderPos);
3419     QCOMPARE(header->width(), 50.);
3420     QCOMPARE(header->height(), 20.);
3421     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3422
3423     item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3424     QVERIFY(item);
3425     QCOMPARE(item->pos(), firstDelegatePos);
3426
3427     listview->positionViewAtBeginning();
3428     header->setHeight(10);
3429     header->setWidth(40);
3430     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3431
3432     releaseView(canvas);
3433
3434
3435     // QTBUG-21207 header should become visible if view resizes from initial empty size
3436
3437     canvas = getView();
3438     canvas->rootContext()->setContextProperty("testModel", &model);
3439     canvas->rootContext()->setContextProperty("initialViewWidth", 0.0);
3440     canvas->rootContext()->setContextProperty("initialViewHeight", 0.0);
3441     canvas->setSource(testFileUrl("header.qml"));
3442     canvas->show();
3443     qApp->processEvents();
3444
3445     listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3446     QTRY_VERIFY(listview != 0);
3447     listview->setOrientation(orientation);
3448     listview->setLayoutDirection(layoutDirection);
3449     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3450     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3451
3452     listview->setWidth(240);
3453     listview->setHeight(320);
3454     QTRY_COMPARE(listview->headerItem()->pos(), initialHeaderPos);
3455     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3456
3457     releaseView(canvas);
3458 }
3459
3460 void tst_QQuickListView::header_data()
3461 {
3462     QTest::addColumn<QQuickListView::Orientation>("orientation");
3463     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3464     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
3465     QTest::addColumn<QPointF>("initialHeaderPos");
3466     QTest::addColumn<QPointF>("changedHeaderPos");
3467     QTest::addColumn<QPointF>("initialContentPos");
3468     QTest::addColumn<QPointF>("changedContentPos");
3469     QTest::addColumn<QPointF>("firstDelegatePos");
3470     QTest::addColumn<QPointF>("resizeContentPos");
3471
3472     // header1 = 100 x 30
3473     // header2 = 50 x 20
3474     // delegates = 240 x 30
3475     // view width = 240
3476
3477     // header above items, top left
3478     QTest::newRow("vertical, left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3479         << QPointF(0, -30)
3480         << QPointF(0, -20)
3481         << QPointF(0, -30)
3482         << QPointF(0, -20)
3483         << QPointF(0, 0)
3484         << QPointF(0, -10);
3485
3486     // header above items, top right
3487     QTest::newRow("vertical, layout right to left") << QQuickListView::Vertical << Qt::RightToLeft << QQuickItemView::TopToBottom
3488         << QPointF(0, -30)
3489         << QPointF(0, -20)
3490         << QPointF(0, -30)
3491         << QPointF(0, -20)
3492         << QPointF(0, 0)
3493         << QPointF(0, -10);
3494
3495     // header to left of items
3496     QTest::newRow("horizontal, layout left to right") << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3497         << QPointF(-100, 0)
3498         << QPointF(-50, 0)
3499         << QPointF(-100, 0)
3500         << QPointF(-50, 0)
3501         << QPointF(0, 0)
3502         << QPointF(-40, 0);
3503
3504     // header to right of items
3505     QTest::newRow("horizontal, layout right to left") << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3506         << QPointF(0, 0)
3507         << QPointF(0, 0)
3508         << QPointF(-240 + 100, 0)
3509         << QPointF(-240 + 50, 0)
3510         << QPointF(-240, 0)
3511         << QPointF(-240 + 40, 0);
3512
3513     // header below items
3514     QTest::newRow("vertical, bottom to top") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3515         << QPointF(0, 0)
3516         << QPointF(0, 0)
3517         << QPointF(0, -320 + 30)
3518         << QPointF(0, -320 + 20)
3519         << QPointF(0, -30)
3520         << QPointF(0, -320 + 10);
3521 }
3522
3523 void tst_QQuickListView::header_delayItemCreation()
3524 {
3525     QQuickView *canvas = createView();
3526
3527     QmlListModel model;
3528
3529     canvas->rootContext()->setContextProperty("setCurrentToZero", QVariant(false));
3530     canvas->setSource(testFileUrl("fillModelOnComponentCompleted.qml"));
3531     qApp->processEvents();
3532
3533     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3534     QTRY_VERIFY(listview != 0);
3535
3536     QQuickItem *contentItem = listview->contentItem();
3537     QTRY_VERIFY(contentItem != 0);
3538
3539     QQuickText *header = findItem<QQuickText>(contentItem, "header");
3540     QVERIFY(header);
3541     QCOMPARE(header->y(), -header->height());
3542
3543     QCOMPARE(listview->contentY(), -header->height());
3544
3545     model.clear();
3546     QTRY_COMPARE(header->y(), -header->height());
3547
3548     delete canvas;
3549 }
3550
3551 void tst_QQuickListView::footer()
3552 {
3553     QFETCH(QQuickListView::Orientation, orientation);
3554     QFETCH(Qt::LayoutDirection, layoutDirection);
3555     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3556     QFETCH(QPointF, initialFooterPos);
3557     QFETCH(QPointF, firstDelegatePos);
3558     QFETCH(QPointF, initialContentPos);
3559     QFETCH(QPointF, changedFooterPos);
3560     QFETCH(QPointF, changedContentPos);
3561     QFETCH(QPointF, resizeContentPos);
3562
3563     QQuickView *canvas = getView();
3564
3565     QmlListModel model;
3566     for (int i = 0; i < 3; i++)
3567         model.addItem("Item" + QString::number(i), "");
3568
3569     QQmlContext *ctxt = canvas->rootContext();
3570     ctxt->setContextProperty("testModel", &model);
3571
3572     canvas->setSource(testFileUrl("footer.qml"));
3573     canvas->show();
3574     qApp->processEvents();
3575
3576     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3577     QTRY_VERIFY(listview != 0);
3578     listview->setOrientation(orientation);
3579     listview->setLayoutDirection(layoutDirection);
3580     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3581     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3582
3583     QQuickItem *contentItem = listview->contentItem();
3584     QTRY_VERIFY(contentItem != 0);
3585
3586     QQuickText *footer = findItem<QQuickText>(contentItem, "footer");
3587     QVERIFY(footer);
3588
3589     QVERIFY(footer == listview->footerItem());
3590
3591     QCOMPARE(footer->pos(), initialFooterPos);
3592     QCOMPARE(footer->width(), 100.);
3593     QCOMPARE(footer->height(), 30.);
3594     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3595
3596     if (orientation == QQuickListView::Vertical)
3597         QCOMPARE(listview->contentHeight(), model.count() * 20. + footer->height());
3598     else
3599         QCOMPARE(listview->contentWidth(), model.count() * 40. + footer->width());
3600
3601     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3602     QVERIFY(item);
3603     QCOMPARE(item->pos(), firstDelegatePos);
3604
3605     // remove one item
3606     model.removeItem(1);
3607
3608     if (orientation == QQuickListView::Vertical) {
3609         QTRY_COMPARE(footer->y(), verticalLayoutDirection == QQuickItemView::TopToBottom ?
3610                 initialFooterPos.y() - 20 : initialFooterPos.y() + 20);  // delegate width = 40
3611     } else {
3612         QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
3613                 initialFooterPos.x() - 40 : initialFooterPos.x() + 40);  // delegate width = 40
3614     }
3615
3616     // remove all items
3617     model.clear();
3618     if (orientation == QQuickListView::Vertical)
3619         QTRY_COMPARE(listview->contentHeight(), footer->height());
3620     else
3621         QTRY_COMPARE(listview->contentWidth(), footer->width());
3622
3623     QPointF posWhenNoItems(0, 0);
3624     if (orientation == QQuickListView::Horizontal && layoutDirection == Qt::RightToLeft)
3625         posWhenNoItems.setX(-100);
3626     else if (orientation == QQuickListView::Vertical && verticalLayoutDirection == QQuickItemView::BottomToTop)
3627         posWhenNoItems.setY(-30);
3628     QTRY_COMPARE(footer->pos(), posWhenNoItems);
3629
3630     // if header is present, it's at a negative pos, so the footer should not move
3631     canvas->rootObject()->setProperty("showHeader", true);
3632     QTRY_COMPARE(footer->pos(), posWhenNoItems);
3633     canvas->rootObject()->setProperty("showHeader", false);
3634
3635     // add 30 items
3636     for (int i = 0; i < 30; i++)
3637         model.addItem("Item" + QString::number(i), "");
3638
3639     QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
3640     QMetaObject::invokeMethod(canvas->rootObject(), "changeFooter");
3641
3642     QCOMPARE(footerItemSpy.count(), 1);
3643
3644     footer = findItem<QQuickText>(contentItem, "footer");
3645     QVERIFY(!footer);
3646     footer = findItem<QQuickText>(contentItem, "footer2");
3647     QVERIFY(footer);
3648
3649     QVERIFY(footer == listview->footerItem());
3650
3651     QCOMPARE(footer->pos(), changedFooterPos);
3652     QCOMPARE(footer->width(), 50.);
3653     QCOMPARE(footer->height(), 20.);
3654     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3655
3656     item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3657     QVERIFY(item);
3658     QCOMPARE(item->pos(), firstDelegatePos);
3659
3660     listview->positionViewAtEnd();
3661     footer->setHeight(10);
3662     footer->setWidth(40);
3663     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3664
3665     releaseView(canvas);
3666 }
3667
3668 void tst_QQuickListView::footer_data()
3669 {
3670     QTest::addColumn<QQuickListView::Orientation>("orientation");
3671     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3672     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
3673     QTest::addColumn<QPointF>("initialFooterPos");
3674     QTest::addColumn<QPointF>("changedFooterPos");
3675     QTest::addColumn<QPointF>("initialContentPos");
3676     QTest::addColumn<QPointF>("changedContentPos");
3677     QTest::addColumn<QPointF>("firstDelegatePos");
3678     QTest::addColumn<QPointF>("resizeContentPos");
3679
3680     // footer1 = 100 x 30
3681     // footer2 = 50 x 20
3682     // delegates = 40 x 20
3683     // view width = 240
3684     // view height = 320
3685
3686     // footer below items, bottom left
3687     QTest::newRow("vertical, layout left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3688         << QPointF(0, 3 * 20)
3689         << QPointF(0, 30 * 20)  // added 30 items
3690         << QPointF(0, 0)
3691         << QPointF(0, 0)
3692         << QPointF(0, 0)
3693         << QPointF(0, 30 * 20 - 320 + 10);
3694
3695     // footer below items, bottom right
3696     QTest::newRow("vertical, layout right to left") << QQuickListView::Vertical << Qt::RightToLeft << QQuickItemView::TopToBottom
3697         << QPointF(0, 3 * 20)
3698         << QPointF(0, 30 * 20)
3699         << QPointF(0, 0)
3700         << QPointF(0, 0)
3701         << QPointF(0, 0)
3702         << QPointF(0, 30 * 20 - 320 + 10);
3703
3704     // footer to right of items
3705     QTest::newRow("horizontal, layout left to right") << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3706         << QPointF(40 * 3, 0)
3707         << QPointF(40 * 30, 0)
3708         << QPointF(0, 0)
3709         << QPointF(0, 0)
3710         << QPointF(0, 0)
3711         << QPointF(40 * 30 - 240 + 40, 0);
3712
3713     // footer to left of items
3714     QTest::newRow("horizontal, layout right to left") << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3715         << QPointF(-(40 * 3) - 100, 0)
3716         << QPointF(-(40 * 30) - 50, 0)     // 50 = new footer width
3717         << QPointF(-240, 0)
3718         << QPointF(-240, 0)
3719         << QPointF(-40, 0)
3720         << QPointF(-(40 * 30) - 40, 0);
3721
3722     // footer above items
3723     QTest::newRow("vertical, layout left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3724         << QPointF(0, -(3 * 20) - 30)
3725         << QPointF(0, -(30 * 20) - 20)
3726         << QPointF(0, -320)
3727         << QPointF(0, -320)
3728         << QPointF(0, -20)
3729         << QPointF(0, -(30 * 20) - 10);
3730 }
3731
3732 class LVAccessor : public QQuickListView
3733 {
3734 public:
3735     qreal minY() const { return minYExtent(); }
3736     qreal maxY() const { return maxYExtent(); }
3737     qreal minX() const { return minXExtent(); }
3738     qreal maxX() const { return maxXExtent(); }
3739 };
3740
3741
3742 void tst_QQuickListView::extents()
3743 {
3744     QFETCH(QQuickListView::Orientation, orientation);
3745     QFETCH(Qt::LayoutDirection, layoutDirection);
3746     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3747     QFETCH(QPointF, headerPos);
3748     QFETCH(QPointF, footerPos);
3749     QFETCH(QPointF, minPos);
3750     QFETCH(QPointF, maxPos);
3751     QFETCH(QPointF, origin_empty);
3752     QFETCH(QPointF, origin_nonEmpty);
3753
3754     QQuickView *canvas = getView();
3755
3756     QmlListModel model;
3757     QQmlContext *ctxt = canvas->rootContext();
3758     ctxt->setContextProperty("testModel", &model);
3759     canvas->setSource(testFileUrl("headerfooter.qml"));
3760     canvas->show();
3761     qApp->processEvents();
3762
3763     QQuickListView *listview = qobject_cast<QQuickListView*>(canvas->rootObject());
3764     QTRY_VERIFY(listview != 0);
3765     listview->setOrientation(orientation);
3766     listview->setLayoutDirection(layoutDirection);
3767     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3768     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3769
3770     QQuickItem *contentItem = listview->contentItem();
3771     QTRY_VERIFY(contentItem != 0);
3772
3773     QQuickItem *header = findItem<QQuickItem>(contentItem, "header");
3774     QVERIFY(header);
3775     QCOMPARE(header->pos(), headerPos);
3776
3777     QQuickItem *footer = findItem<QQuickItem>(contentItem, "footer");
3778     QVERIFY(footer);
3779     QCOMPARE(footer->pos(), footerPos);
3780
3781     QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), minPos.x());
3782     QCOMPARE(static_cast<LVAccessor*>(listview)->minY(), minPos.y());
3783     QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), maxPos.x());
3784     QCOMPARE(static_cast<LVAccessor*>(listview)->maxY(), maxPos.y());
3785
3786     QCOMPARE(listview->originX(), origin_empty.x());
3787     QCOMPARE(listview->originY(), origin_empty.y());
3788     for (int i=0; i<30; i++)
3789         model.addItem("Item" + QString::number(i), "");
3790     QTRY_COMPARE(listview->count(), model.count());
3791     QCOMPARE(listview->originX(), origin_nonEmpty.x());
3792     QCOMPARE(listview->originY(), origin_nonEmpty.y());
3793
3794     releaseView(canvas);
3795 }
3796
3797 void tst_QQuickListView::extents_data()
3798 {
3799     QTest::addColumn<QQuickListView::Orientation>("orientation");
3800     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3801     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
3802     QTest::addColumn<QPointF>("headerPos");
3803     QTest::addColumn<QPointF>("footerPos");
3804     QTest::addColumn<QPointF>("minPos");
3805     QTest::addColumn<QPointF>("maxPos");
3806     QTest::addColumn<QPointF>("origin_empty");
3807     QTest::addColumn<QPointF>("origin_nonEmpty");
3808
3809     // header is 240x20 (or 20x320 in Horizontal orientation)
3810     // footer is 240x30 (or 30x320 in Horizontal orientation)
3811
3812     QTest::newRow("Vertical, TopToBottom")
3813             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3814             << QPointF(0, -20) << QPointF(0, 0)
3815             << QPointF(0, 20) << QPointF(240, 20)
3816             << QPointF(0, -20) << QPointF(0, -20);
3817
3818     QTest::newRow("Vertical, BottomToTop")
3819             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3820             << QPointF(0, 0) << QPointF(0, -30)
3821             << QPointF(0, 320 - 20) << QPointF(240, 320 - 20)  // content flow is reversed
3822             << QPointF(0, -30) << QPointF(0, (-30.0 * 30) - 30);
3823
3824     QTest::newRow("Horizontal, LeftToRight")
3825             << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3826             << QPointF(-20, 0) << QPointF(0, 0)
3827             << QPointF(20, 0) << QPointF(20, 320)
3828             << QPointF(-20, 0) << QPointF(-20, 0);
3829
3830     QTest::newRow("Horizontal, RightToLeft")
3831             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3832             << QPointF(0, 0) << QPointF(-30, 0)
3833             << QPointF(240 - 20, 0) << QPointF(240 - 20, 320)  // content flow is reversed
3834             << QPointF(-30, 0) << QPointF((-240.0 * 30) - 30, 0);
3835 }
3836
3837 void tst_QQuickListView::resetModel_headerFooter()
3838 {
3839     // Resetting a model shouldn't crash in views with header/footer
3840
3841     QQuickView *canvas = createView();
3842
3843     QaimModel model;
3844     for (int i = 0; i < 4; i++)
3845         model.addItem("Item" + QString::number(i), "");
3846     QQmlContext *ctxt = canvas->rootContext();
3847     ctxt->setContextProperty("testModel", &model);
3848
3849     canvas->setSource(testFileUrl("headerfooter.qml"));
3850     qApp->processEvents();
3851
3852     QQuickListView *listview = qobject_cast<QQuickListView*>(canvas->rootObject());
3853     QTRY_VERIFY(listview != 0);
3854
3855     QQuickItem *contentItem = listview->contentItem();
3856     QTRY_VERIFY(contentItem != 0);
3857
3858     QQuickItem *header = findItem<QQuickItem>(contentItem, "header");
3859     QVERIFY(header);
3860     QCOMPARE(header->y(), -header->height());
3861
3862     QQuickItem *footer = findItem<QQuickItem>(contentItem, "footer");
3863     QVERIFY(footer);
3864     QCOMPARE(footer->y(), 30.*4);
3865
3866     model.reset();
3867
3868     header = findItem<QQuickItem>(contentItem, "header");
3869     QVERIFY(header);
3870     QCOMPARE(header->y(), -header->height());
3871
3872     footer = findItem<QQuickItem>(contentItem, "footer");
3873     QVERIFY(footer);
3874     QCOMPARE(footer->y(), 30.*4);
3875
3876     delete canvas;
3877 }
3878
3879 void tst_QQuickListView::resizeView()
3880 {
3881     QQuickView *canvas = createView();
3882
3883     QmlListModel model;
3884     for (int i = 0; i < 40; i++)
3885         model.addItem("Item" + QString::number(i), "");
3886
3887     QQmlContext *ctxt = canvas->rootContext();
3888     ctxt->setContextProperty("testModel", &model);
3889
3890     TestObject *testObject = new TestObject;
3891     ctxt->setContextProperty("testObject", testObject);
3892
3893     canvas->setSource(testFileUrl("listviewtest.qml"));
3894     canvas->show();
3895     qApp->processEvents();
3896
3897     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3898     QTRY_VERIFY(listview != 0);
3899     QQuickItem *contentItem = listview->contentItem();
3900     QTRY_VERIFY(contentItem != 0);
3901     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3902
3903     // Confirm items positioned correctly
3904     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3905     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3906         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3907         if (!item) qWarning() << "Item" << i << "not found";
3908         QTRY_VERIFY(item);
3909         QTRY_COMPARE(item->y(), i*20.);
3910     }
3911
3912     QVariant heightRatio;
3913     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3914     QCOMPARE(heightRatio.toReal(), 0.4);
3915
3916     listview->setHeight(200);
3917     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3918
3919     QMetaObject::invokeMethod(canvas->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3920     QCOMPARE(heightRatio.toReal(), 0.25);
3921
3922     // Ensure we handle -ve sizes
3923     listview->setHeight(-100);
3924     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 1);
3925
3926     listview->setCacheBuffer(200);
3927     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 11);
3928
3929     // ensure items in cache become visible
3930     listview->setHeight(200);
3931     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 21);
3932
3933     itemCount = findItems<QQuickItem>(contentItem, "wrapper", false).count();
3934     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3935         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3936         if (!item) qWarning() << "Item" << i << "not found";
3937         QTRY_VERIFY(item);
3938         QTRY_COMPARE(item->y(), i*20.);
3939         QCOMPARE(delegateVisible(item), i < 11); // inside view visible, outside not visible
3940     }
3941
3942     // ensure items outside view become invisible
3943     listview->setHeight(100);
3944     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 16);
3945
3946     itemCount = findItems<QQuickItem>(contentItem, "wrapper", false).count();
3947     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3948         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3949         if (!item) qWarning() << "Item" << i << "not found";
3950         QTRY_VERIFY(item);
3951         QTRY_COMPARE(item->y(), i*20.);
3952         QCOMPARE(delegateVisible(item), i < 6); // inside view visible, outside not visible
3953     }
3954
3955     delete canvas;
3956     delete testObject;
3957 }
3958
3959 void tst_QQuickListView::resizeViewAndRepaint()
3960 {
3961     QQuickView *canvas = createView();
3962
3963     QmlListModel model;
3964     for (int i = 0; i < 40; i++)
3965         model.addItem("Item" + QString::number(i), "");
3966
3967     QQmlContext *ctxt = canvas->rootContext();
3968     ctxt->setContextProperty("testModel", &model);
3969     ctxt->setContextProperty("initialHeight", 100);
3970
3971     canvas->setSource(testFileUrl("resizeview.qml"));
3972     canvas->show();
3973     qApp->processEvents();
3974
3975     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
3976     QTRY_VERIFY(listview != 0);
3977     QQuickItem *contentItem = listview->contentItem();
3978     QTRY_VERIFY(contentItem != 0);
3979     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3980
3981     // item at index 10 should not be currently visible
3982     QVERIFY(!findItem<QQuickItem>(contentItem, "wrapper", 10));
3983
3984     listview->setHeight(320);
3985
3986     QTRY_VERIFY(findItem<QQuickItem>(contentItem, "wrapper", 10));
3987
3988     listview->setHeight(100);
3989     QTRY_VERIFY(!findItem<QQuickItem>(contentItem, "wrapper", 10));
3990
3991     delete canvas;
3992 }
3993
3994 void tst_QQuickListView::sizeLessThan1()
3995 {
3996     QQuickView *canvas = createView();
3997
3998     QmlListModel model;
3999     for (int i = 0; i < 30; i++)
4000         model.addItem("Item" + QString::number(i), "");
4001
4002     QQmlContext *ctxt = canvas->rootContext();
4003     ctxt->setContextProperty("testModel", &model);
4004
4005     TestObject *testObject = new TestObject;
4006     ctxt->setContextProperty("testObject", testObject);
4007
4008     canvas->setSource(testFileUrl("sizelessthan1.qml"));
4009     canvas->show();
4010     qApp->processEvents();
4011
4012     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4013     QTRY_VERIFY(listview != 0);
4014     QQuickItem *contentItem = listview->contentItem();
4015     QTRY_VERIFY(contentItem != 0);
4016     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4017
4018     // Confirm items positioned correctly
4019     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
4020     for (int i = 0; i < model.count() && i < itemCount; ++i) {
4021         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4022         if (!item) qWarning() << "Item" << i << "not found";
4023         QTRY_VERIFY(item);
4024         QTRY_COMPARE(item->y(), i*0.5);
4025     }
4026
4027     delete canvas;
4028     delete testObject;
4029 }
4030
4031 void tst_QQuickListView::QTBUG_14821()
4032 {
4033     QQuickView *canvas = createView();
4034
4035     canvas->setSource(testFileUrl("qtbug14821.qml"));
4036     qApp->processEvents();
4037
4038     QQuickListView *listview = qobject_cast<QQuickListView*>(canvas->rootObject());
4039     QVERIFY(listview != 0);
4040
4041     QQuickItem *contentItem = listview->contentItem();
4042     QVERIFY(contentItem != 0);
4043
4044     listview->decrementCurrentIndex();
4045     QCOMPARE(listview->currentIndex(), 99);
4046
4047     listview->incrementCurrentIndex();
4048     QCOMPARE(listview->currentIndex(), 0);
4049
4050     delete canvas;
4051 }
4052
4053 void tst_QQuickListView::resizeDelegate()
4054 {
4055     QQuickView *canvas = createView();
4056
4057     QStringList strings;
4058     for (int i = 0; i < 30; ++i)
4059         strings << QString::number(i);
4060     QStringListModel model(strings);
4061
4062     QQmlContext *ctxt = canvas->rootContext();
4063     ctxt->setContextProperty("testModel", &model);
4064
4065     canvas->setSource(testFileUrl("displaylist.qml"));
4066     canvas->show();
4067     qApp->processEvents();
4068
4069     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4070     QVERIFY(listview != 0);
4071     QQuickItem *contentItem = listview->contentItem();
4072     QVERIFY(contentItem != 0);
4073     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4074
4075     QCOMPARE(listview->count(), model.rowCount());
4076
4077     listview->setCurrentIndex(25);
4078     listview->setContentY(0);
4079     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4080
4081     for (int i = 0; i < 16; ++i) {
4082         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4083         QVERIFY(item != 0);
4084         QCOMPARE(item->y(), i*20.0);
4085     }
4086
4087     QCOMPARE(listview->currentItem()->y(), 500.0);
4088     QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
4089
4090     canvas->rootObject()->setProperty("delegateHeight", 30);
4091     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4092
4093     for (int i = 0; i < 11; ++i) {
4094         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4095         QVERIFY(item != 0);
4096         QTRY_COMPARE(item->y(), i*30.0);
4097     }
4098
4099     QTRY_COMPARE(listview->currentItem()->y(), 750.0);
4100     QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
4101
4102     listview->setCurrentIndex(1);
4103     listview->positionViewAtIndex(25, QQuickListView::Beginning);
4104     listview->positionViewAtIndex(5, QQuickListView::Beginning);
4105     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4106
4107     for (int i = 5; i < 16; ++i) {
4108         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4109         QVERIFY(item != 0);
4110         QCOMPARE(item->y(), i*30.0);
4111     }
4112
4113     QTRY_COMPARE(listview->currentItem()->y(), 30.0);
4114     QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
4115
4116     canvas->rootObject()->setProperty("delegateHeight", 20);
4117     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4118
4119     for (int i = 5; i < 11; ++i) {
4120         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4121         QVERIFY(item != 0);
4122         QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
4123     }
4124
4125     QTRY_COMPARE(listview->currentItem()->y(), 70.0);
4126     QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
4127
4128     delete canvas;
4129 }
4130
4131 void tst_QQuickListView::resizeFirstDelegate()
4132 {
4133     // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
4134     // and other delegates have height > 0
4135
4136     QQuickView *canvas = createView();
4137
4138     // bug only occurs when all items in the model are visible
4139     QmlListModel model;
4140     for (int i = 0; i < 10; i++)
4141         model.addItem("Item" + QString::number(i), "");
4142
4143     QQmlContext *ctxt = canvas->rootContext();
4144     ctxt->setContextProperty("testModel", &model);
4145
4146     TestObject *testObject = new TestObject;
4147     ctxt->setContextProperty("testObject", testObject);
4148
4149     canvas->setSource(testFileUrl("listviewtest.qml"));
4150     canvas->show();
4151     qApp->processEvents();
4152
4153     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4154     QVERIFY(listview != 0);
4155     QQuickItem *contentItem = listview->contentItem();
4156     QVERIFY(contentItem != 0);
4157     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4158
4159     QQuickItem *item = 0;
4160     for (int i = 0; i < model.count(); ++i) {
4161         item = findItem<QQuickItem>(contentItem, "wrapper", i);
4162         QVERIFY(item != 0);
4163         QCOMPARE(item->y(), i*20.0);
4164     }
4165
4166     item = findItem<QQuickItem>(contentItem, "wrapper", 0);
4167     item->setHeight(0);
4168
4169     // check the content y has not jumped up and down
4170     QCOMPARE(listview->contentY(), 0.0);
4171     QSignalSpy spy(listview, SIGNAL(contentYChanged()));
4172     QTest::qWait(100);
4173     QCOMPARE(spy.count(), 0);
4174
4175     for (int i = 1; i < model.count(); ++i) {
4176         item = findItem<QQuickItem>(contentItem, "wrapper", i);
4177         QVERIFY(item != 0);
4178         QTRY_COMPARE(item->y(), (i-1)*20.0);
4179     }
4180
4181
4182     // QTBUG-22014: refill doesn't clear items scrolling off the top of the
4183     // list if they follow a zero-sized delegate
4184
4185     for (int i = 0; i < 10; i++)
4186         model.addItem("Item" + QString::number(i), "");
4187     QTRY_COMPARE(listview->count(), model.count());
4188
4189     item = findItem<QQuickItem>(contentItem, "wrapper", 1);
4190     QVERIFY(item);
4191     item->setHeight(0);
4192
4193     listview->setCurrentIndex(19);
4194     qApp->processEvents();
4195     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4196
4197     // items 0-2 should have been deleted
4198     for (int i=0; i<3; i++) {
4199         QTRY_VERIFY(!findItem<QQuickItem>(contentItem, "wrapper", i));
4200     }
4201
4202     delete testObject;
4203     delete canvas;
4204 }
4205
4206 void tst_QQuickListView::repositionResizedDelegate()
4207 {
4208     QFETCH(QQuickListView::Orientation, orientation);
4209     QFETCH(Qt::LayoutDirection, layoutDirection);
4210     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4211     QFETCH(QPointF, contentPos_itemFirstHalfVisible);
4212     QFETCH(QPointF, contentPos_itemSecondHalfVisible);
4213     QFETCH(QRectF, origPositionerRect);
4214     QFETCH(QRectF, resizedPositionerRect);
4215
4216     QQuickView *canvas = getView();
4217     QQmlContext *ctxt = canvas->rootContext();
4218     ctxt->setContextProperty("testHorizontal", orientation == QQuickListView::Horizontal);
4219     ctxt->setContextProperty("testRightToLeft", layoutDirection == Qt::RightToLeft);
4220     ctxt->setContextProperty("testBottomToTop", verticalLayoutDirection == QQuickListView::BottomToTop);
4221     canvas->setSource(testFileUrl("repositionResizedDelegate.qml"));
4222     canvas->show();
4223     qApp->processEvents();
4224
4225     QQuickListView *listview = qobject_cast<QQuickListView*>(canvas->rootObject());
4226     QTRY_VERIFY(listview != 0);
4227     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4228
4229     QQuickItem *positioner = findItem<QQuickItem>(canvas->rootObject(), "positioner");
4230     QVERIFY(positioner);
4231     QTRY_COMPARE(positioner->boundingRect().size(), origPositionerRect.size());
4232     QTRY_COMPARE(positioner->pos(), origPositionerRect.topLeft());
4233     QSignalSpy spy(listview, orientation == QQuickListView::Vertical ? SIGNAL(contentYChanged()) : SIGNAL(contentXChanged()));
4234     int prevSpyCount = 0;
4235
4236     // When an item is resized while it is partially visible, it should resize in the
4237     // direction of the content flow. If a RightToLeft or BottomToTop layout is used,
4238     // the item should also be re-positioned so its end position stays the same.
4239
4240     listview->setContentX(contentPos_itemFirstHalfVisible.x());
4241     listview->setContentY(contentPos_itemFirstHalfVisible.y());
4242     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4243     prevSpyCount = spy.count();
4244     QVERIFY(QMetaObject::invokeMethod(canvas->rootObject(), "incrementRepeater"));
4245     QTRY_COMPARE(positioner->boundingRect().size(), resizedPositionerRect.size());
4246     QTRY_COMPARE(positioner->pos(), resizedPositionerRect.topLeft());
4247     QCOMPARE(listview->contentX(), contentPos_itemFirstHalfVisible.x());
4248     QCOMPARE(listview->contentY(), contentPos_itemFirstHalfVisible.y());
4249     QCOMPARE(spy.count(), prevSpyCount);
4250
4251     QVERIFY(QMetaObject::invokeMethod(canvas->rootObject(), "decrementRepeater"));
4252     QTRY_COMPARE(positioner->boundingRect().size(), origPositionerRect.size());
4253     QTRY_COMPARE(positioner->pos(), origPositionerRect.topLeft());
4254     QCOMPARE(listview->contentX(), contentPos_itemFirstHalfVisible.x());
4255     QCOMPARE(listview->contentY(), contentPos_itemFirstHalfVisible.y());
4256
4257     listview->setContentX(contentPos_itemSecondHalfVisible.x());
4258     listview->setContentY(contentPos_itemSecondHalfVisible.y());
4259     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4260     prevSpyCount = spy.count();
4261
4262     QVERIFY(QMetaObject::invokeMethod(canvas->rootObject(), "incrementRepeater"));
4263     positioner = findItem<QQuickItem>(canvas->rootObject(), "positioner");
4264     QTRY_COMPARE(positioner->boundingRect().size(), resizedPositionerRect.size());
4265     QTRY_COMPARE(positioner->pos(), resizedPositionerRect.topLeft());
4266     QCOMPARE(listview->contentX(), contentPos_itemSecondHalfVisible.x());
4267     QCOMPARE(listview->contentY(), contentPos_itemSecondHalfVisible.y());
4268     qApp->processEvents();
4269     QCOMPARE(spy.count(), prevSpyCount);
4270
4271     releaseView(canvas);
4272 }
4273
4274 void tst_QQuickListView::repositionResizedDelegate_data()
4275 {
4276     QTest::addColumn<QQuickListView::Orientation>("orientation");
4277     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
4278     QTest::addColumn<QQuickListView::VerticalLayoutDirection>("verticalLayoutDirection");
4279     QTest::addColumn<QPointF>("contentPos_itemFirstHalfVisible");
4280     QTest::addColumn<QPointF>("contentPos_itemSecondHalfVisible");
4281     QTest::addColumn<QRectF>("origPositionerRect");
4282     QTest::addColumn<QRectF>("resizedPositionerRect");
4283
4284     QTest::newRow("vertical")
4285             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4286             << QPointF(0, 60) << QPointF(0, 200 + 60)
4287             << QRectF(0, 200, 120, 120)
4288             << QRectF(0, 200, 120, 120 * 2);
4289
4290     QTest::newRow("vertical, BottomToTop")
4291             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4292             << QPointF(0, -200 - 60) << QPointF(0, -200 - 260)
4293             << QRectF(0, -200 - 120, 120, 120)
4294             << QRectF(0, -200 - 120*2, 120, 120 * 2);
4295
4296     QTest::newRow("horizontal")
4297             << QQuickListView::Horizontal<< Qt::LeftToRight << QQuickItemView::TopToBottom
4298             << QPointF(60, 0) << QPointF(260, 0)
4299             << QRectF(200, 0, 120, 120)
4300             << QRectF(200, 0, 120 * 2, 120);
4301
4302     QTest::newRow("horizontal, rtl")
4303             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4304             << QPointF(-200 - 60, 0) << QPointF(-200 - 260, 0)
4305             << QRectF(-200 - 120, 0, 120, 120)
4306             << QRectF(-200 - 120 * 2, 0, 120 * 2, 120);
4307 }
4308
4309 void tst_QQuickListView::QTBUG_16037()
4310 {
4311     QQuickView *canvas = createView();
4312     canvas->show();
4313
4314     canvas->setSource(testFileUrl("qtbug16037.qml"));
4315     qApp->processEvents();
4316
4317     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "listview");
4318     QTRY_VERIFY(listview != 0);
4319
4320     QVERIFY(listview->contentHeight() <= 0.0);
4321
4322     QMetaObject::invokeMethod(canvas->rootObject(), "setModel");
4323
4324     QTRY_COMPARE(listview->contentHeight(), 80.0);
4325
4326     delete canvas;
4327 }
4328
4329 void tst_QQuickListView::indexAt_itemAt_data()
4330 {
4331     QTest::addColumn<qreal>("x");
4332     QTest::addColumn<qreal>("y");
4333     QTest::addColumn<int>("index");
4334
4335     QTest::newRow("Item 0 - 0, 0") << 0. << 0. << 0;
4336     QTest::newRow("Item 0 - 0, 19") << 0. << 19. << 0;
4337     QTest::newRow("Item 0 - 239, 19") << 239. << 19. << 0;
4338     QTest::newRow("Item 1 - 0, 20") << 0. << 20. << 1;
4339     QTest::newRow("No Item - 240, 20") << 240. << 20. << -1;
4340 }
4341
4342 void tst_QQuickListView::indexAt_itemAt()
4343 {
4344     QFETCH(qreal, x);
4345     QFETCH(qreal, y);
4346     QFETCH(int, index);
4347
4348     QQuickView *canvas = getView();
4349
4350     QmlListModel model;
4351     for (int i = 0; i < 30; i++)
4352         model.addItem("Item" + QString::number(i), "");
4353
4354     QQmlContext *ctxt = canvas->rootContext();
4355     ctxt->setContextProperty("testModel", &model);
4356
4357     TestObject *testObject = new TestObject;
4358     ctxt->setContextProperty("testObject", testObject);
4359
4360     canvas->setSource(testFileUrl("listviewtest.qml"));
4361     canvas->show();
4362     qApp->processEvents();
4363
4364     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4365     QTRY_VERIFY(listview != 0);
4366
4367     QQuickItem *contentItem = listview->contentItem();
4368     QTRY_VERIFY(contentItem != 0);
4369     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4370
4371     QQuickItem *item = 0;
4372     if (index >= 0) {
4373         item = findItem<QQuickItem>(contentItem, "wrapper", index);
4374         QVERIFY(item);
4375     }
4376     QCOMPARE(listview->indexAt(x,y), index);
4377     QVERIFY(listview->itemAt(x,y) == item);
4378
4379     releaseView(canvas);
4380     delete testObject;
4381 }
4382
4383 void tst_QQuickListView::incrementalModel()
4384 {
4385     QQuickView *canvas = createView();
4386
4387     IncrementalModel model;
4388     QQmlContext *ctxt = canvas->rootContext();
4389     ctxt->setContextProperty("testModel", &model);
4390
4391     canvas->setSource(testFileUrl("displaylist.qml"));
4392     qApp->processEvents();
4393
4394     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4395     QTRY_VERIFY(listview != 0);
4396
4397     QQuickItem *contentItem = listview->contentItem();
4398     QTRY_VERIFY(contentItem != 0);
4399
4400     QTRY_COMPARE(listview->count(), 20);
4401
4402     listview->positionViewAtIndex(10, QQuickListView::Beginning);
4403
4404     QTRY_COMPARE(listview->count(), 25);
4405
4406     delete canvas;
4407 }
4408
4409 void tst_QQuickListView::onAdd()
4410 {
4411     QFETCH(int, initialItemCount);
4412     QFETCH(int, itemsToAdd);
4413
4414     const int delegateHeight = 10;
4415     QaimModel model;
4416
4417     // these initial items should not trigger ListView.onAdd
4418     for (int i=0; i<initialItemCount; i++)
4419         model.addItem("dummy value", "dummy value");
4420
4421     QQuickView *canvas = createView();
4422     canvas->setGeometry(0,0,200, delegateHeight * (initialItemCount + itemsToAdd));
4423     QQmlContext *ctxt = canvas->rootContext();
4424     ctxt->setContextProperty("testModel", &model);
4425     ctxt->setContextProperty("delegateHeight", delegateHeight);
4426     canvas->setSource(testFileUrl("attachedSignals.qml"));
4427
4428     QObject *object = canvas->rootObject();
4429     object->setProperty("width", canvas->width());
4430     object->setProperty("height", canvas->height());
4431     qApp->processEvents();
4432
4433     QList<QPair<QString, QString> > items;
4434     for (int i=0; i<itemsToAdd; i++)
4435         items << qMakePair(QString("value %1").arg(i), QString::number(i));
4436     model.addItems(items);
4437     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
4438
4439     QVariantList result = object->property("addedDelegates").toList();
4440     QCOMPARE(result.count(), items.count());
4441     for (int i=0; i<items.count(); i++)
4442         QCOMPARE(result[i].toString(), items[i].first);
4443
4444     delete canvas;
4445 }
4446
4447 void tst_QQuickListView::onAdd_data()
4448 {
4449     QTest::addColumn<int>("initialItemCount");
4450     QTest::addColumn<int>("itemsToAdd");
4451
4452     QTest::newRow("0, add 1") << 0 << 1;
4453     QTest::newRow("0, add 2") << 0 << 2;
4454     QTest::newRow("0, add 10") << 0 << 10;
4455
4456     QTest::newRow("1, add 1") << 1 << 1;
4457     QTest::newRow("1, add 2") << 1 << 2;
4458     QTest::newRow("1, add 10") << 1 << 10;
4459
4460     QTest::newRow("5, add 1") << 5 << 1;
4461     QTest::newRow("5, add 2") << 5 << 2;
4462     QTest::newRow("5, add 10") << 5 << 10;
4463 }
4464
4465 void tst_QQuickListView::onRemove()
4466 {
4467     QFETCH(int, initialItemCount);
4468     QFETCH(int, indexToRemove);
4469     QFETCH(int, removeCount);
4470
4471     const int delegateHeight = 10;
4472     QaimModel model;
4473     for (int i=0; i<initialItemCount; i++)
4474         model.addItem(QString("value %1").arg(i), "dummy value");
4475
4476     QQuickView *canvas = getView();
4477     QQmlContext *ctxt = canvas->rootContext();
4478     ctxt->setContextProperty("testModel", &model);
4479     ctxt->setContextProperty("delegateHeight", delegateHeight);
4480     canvas->setSource(testFileUrl("attachedSignals.qml"));
4481
4482     QObject *object = canvas->rootObject();
4483
4484     model.removeItems(indexToRemove, removeCount);
4485     QTRY_COMPARE(canvas->rootObject()->property("count").toInt(), model.count());
4486
4487     QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
4488
4489     releaseView(canvas);
4490 }
4491
4492 void tst_QQuickListView::onRemove_data()
4493 {
4494     QTest::addColumn<int>("initialItemCount");
4495     QTest::addColumn<int>("indexToRemove");
4496     QTest::addColumn<int>("removeCount");
4497
4498     QTest::newRow("remove first") << 1 << 0 << 1;
4499     QTest::newRow("two items, remove first") << 2 << 0 << 1;
4500     QTest::newRow("two items, remove last") << 2 << 1 << 1;
4501     QTest::newRow("two items, remove all") << 2 << 0 << 2;
4502
4503     QTest::newRow("four items, remove first") << 4 << 0 << 1;
4504     QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
4505     QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
4506     QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
4507     QTest::newRow("four items, remove last") << 4 << 3 << 1;
4508     QTest::newRow("four items, remove all") << 4 << 0 << 4;
4509
4510     QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
4511     QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
4512     QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
4513 }
4514
4515 void tst_QQuickListView::rightToLeft()
4516 {
4517     QQuickView *canvas = createView();
4518     canvas->setGeometry(0,0,640,320);
4519     canvas->setSource(testFileUrl("rightToLeft.qml"));
4520     canvas->show();
4521     qApp->processEvents();
4522
4523     QVERIFY(canvas->rootObject() != 0);
4524     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "view");
4525     QTRY_VERIFY(listview != 0);
4526
4527     QQuickItem *contentItem = listview->contentItem();
4528     QTRY_VERIFY(contentItem != 0);
4529
4530     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4531
4532     QQuickVisualItemModel *model = canvas->rootObject()->findChild<QQuickVisualItemModel*>("itemModel");
4533     QTRY_VERIFY(model != 0);
4534
4535     QTRY_VERIFY(model->count() == 3);
4536     QTRY_COMPARE(listview->currentIndex(), 0);
4537
4538     // initial position at first item, right edge aligned
4539     QCOMPARE(listview->contentX(), -640.);
4540
4541     QQuickItem *item = findItem<QQuickItem>(contentItem, "item1");
4542     QTRY_VERIFY(item);
4543     QTRY_COMPARE(item->x(), -100.0);
4544     QCOMPARE(item->height(), listview->height());
4545
4546     QQuickText *text = findItem<QQuickText>(contentItem, "text1");
4547     QTRY_VERIFY(text);
4548     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
4549
4550     listview->setCurrentIndex(2);
4551
4552     item = findItem<QQuickItem>(contentItem, "item3");
4553     QTRY_VERIFY(item);
4554     QTRY_COMPARE(item->x(), -540.0);
4555
4556     text = findItem<QQuickText>(contentItem, "text3");
4557     QTRY_VERIFY(text);
4558     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
4559
4560     QCOMPARE(listview->contentX(), -640.);
4561
4562     // Ensure resizing maintains position relative to right edge
4563     qobject_cast<QQuickItem*>(canvas->rootObject())->setWidth(600);
4564     QTRY_COMPARE(listview->contentX(), -600.);
4565
4566     delete canvas;
4567 }
4568
4569 void tst_QQuickListView::test_mirroring()
4570 {
4571     QQuickView *canvasA = createView();
4572     canvasA->setSource(testFileUrl("rightToLeft.qml"));
4573     QQuickListView *listviewA = findItem<QQuickListView>(canvasA->rootObject(), "view");
4574     QTRY_VERIFY(listviewA != 0);
4575
4576     QQuickView *canvasB = createView();
4577     canvasB->setSource(testFileUrl("rightToLeft.qml"));
4578     QQuickListView *listviewB = findItem<QQuickListView>(canvasB->rootObject(), "view");
4579     QTRY_VERIFY(listviewA != 0);
4580     qApp->processEvents();
4581
4582     QList<QString> objectNames;
4583     objectNames << "item1" << "item2"; // << "item3"
4584
4585     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
4586     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
4587     QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
4588
4589     // LTR != RTL
4590     foreach (const QString objectName, objectNames)
4591         QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
4592
4593     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
4594     listviewB->setProperty("layoutDirection", Qt::LeftToRight);
4595
4596     // LTR == LTR
4597     foreach (const QString objectName, objectNames)
4598         QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
4599
4600     QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
4601     QQuickItemPrivate::get(listviewB)->setLayoutMirror(true);
4602     QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
4603
4604     // LTR != LTR+mirror
4605     foreach (const QString objectName, objectNames)
4606         QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
4607
4608     listviewA->setProperty("layoutDirection", Qt::RightToLeft);
4609
4610     // RTL == LTR+mirror
4611     foreach (const QString objectName, objectNames)
4612         QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
4613
4614     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
4615
4616     // RTL != RTL+mirror
4617     foreach (const QString objectName, objectNames)
4618         QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
4619
4620     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
4621
4622     // LTR == RTL+mirror
4623     foreach (const QString objectName, objectNames)
4624         QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
4625
4626     delete canvasA;
4627     delete canvasB;
4628 }
4629
4630 void tst_QQuickListView::margins()
4631 {
4632     QQuickView *canvas = createView();
4633
4634     QaimModel model;
4635     for (int i = 0; i < 50; i++)
4636         model.addItem("Item" + QString::number(i), "");
4637
4638     QQmlContext *ctxt = canvas->rootContext();
4639     ctxt->setContextProperty("testModel", &model);
4640
4641     canvas->setSource(testFileUrl("margins.qml"));
4642     canvas->show();
4643     qApp->processEvents();
4644
4645     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4646     QTRY_VERIFY(listview != 0);
4647     QQuickItem *contentItem = listview->contentItem();
4648     QTRY_VERIFY(contentItem != 0);
4649     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4650
4651     QCOMPARE(listview->contentY(), -30.);
4652     QCOMPARE(listview->originY(), 0.);
4653
4654     // check end bound
4655     listview->positionViewAtEnd();
4656     qreal pos = listview->contentY();
4657     listview->setContentY(pos + 80);
4658     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4659     listview->returnToBounds();
4660     QTRY_COMPARE(listview->contentY(), pos + 50);
4661
4662     // remove item before visible and check that top margin is maintained
4663     // and originY is updated
4664     listview->setContentY(100);
4665     model.removeItem(1);
4666     QTRY_COMPARE(listview->count(), model.count());
4667     listview->setContentY(-50);
4668     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4669     listview->returnToBounds();
4670     QCOMPARE(listview->originY(), 20.);
4671     QTRY_COMPARE(listview->contentY(), -10.);
4672
4673     // reduce top margin
4674     listview->setTopMargin(20);
4675     QCOMPARE(listview->originY(), 20.);
4676     QTRY_COMPARE(listview->contentY(), 0.);
4677
4678     // check end bound
4679     listview->positionViewAtEnd();
4680     pos = listview->contentY();
4681     listview->setContentY(pos + 80);
4682     listview->returnToBounds();
4683     QTRY_COMPARE(listview->contentY(), pos + 50);
4684
4685     // reduce bottom margin
4686     pos = listview->contentY();
4687     listview->setBottomMargin(40);
4688     QCOMPARE(listview->originY(), 20.);
4689     QTRY_COMPARE(listview->contentY(), pos-10);
4690
4691     delete canvas;
4692 }
4693
4694 // QTBUG-24028
4695 void tst_QQuickListView::marginsResize()
4696 {
4697     QFETCH(QQuickListView::Orientation, orientation);
4698     QFETCH(Qt::LayoutDirection, layoutDirection);
4699     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4700     QFETCH(qreal, start);
4701     QFETCH(qreal, end);
4702
4703     QPoint flickStart(20, 20);
4704     QPoint flickEnd(20, 20);
4705     if (orientation == QQuickListView::Vertical)
4706         flickStart.ry() += (verticalLayoutDirection == QQuickItemView::TopToBottom) ? 180 : -180;
4707     else
4708         flickStart.rx() += (layoutDirection == Qt::LeftToRight) ? 180 : -180;
4709
4710     QQuickView *canvas = getView();
4711
4712     canvas->setSource(testFileUrl("margins2.qml"));
4713     canvas->show();
4714     qApp->processEvents();
4715
4716     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "listview");
4717     QTRY_VERIFY(listview != 0);
4718
4719     listview->setOrientation(orientation);
4720     listview->setLayoutDirection(layoutDirection);
4721     listview->setVerticalLayoutDirection(verticalLayoutDirection);
4722     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4723
4724     // view is resized after componentCompleted - top margin should still be visible
4725     if (orientation == QQuickListView::Vertical)
4726         QCOMPARE(listview->contentY(), start);
4727     else
4728         QCOMPARE(listview->contentX(), start);
4729
4730     // move to last index and ensure bottom margin is visible.
4731     listview->setCurrentIndex(19);
4732     if (orientation == QQuickListView::Vertical)
4733         QTRY_COMPARE(listview->contentY(), end);
4734     else
4735         QTRY_COMPARE(listview->contentX(), end);
4736
4737     // flick past the end and check content pos still settles on correct extents
4738     flick(canvas, flickStart, flickEnd, 180);
4739     QTRY_VERIFY(listview->isMoving() == false);
4740     if (orientation == QQuickListView::Vertical)
4741         QTRY_COMPARE(listview->contentY(), end);
4742     else
4743         QTRY_COMPARE(listview->contentX(), end);
4744
4745     // back to top - top margin should be visible.
4746     listview->setCurrentIndex(0);
4747     if (orientation == QQuickListView::Vertical)
4748         QTRY_COMPARE(listview->contentY(), start);
4749     else
4750         QTRY_COMPARE(listview->contentX(), start);
4751
4752     // flick past the beginning and check content pos still settles on correct extents
4753     flick(canvas, flickEnd, flickStart, 180);
4754     QTRY_VERIFY(listview->isMoving() == false);
4755     if (orientation == QQuickListView::Vertical)
4756         QTRY_COMPARE(listview->contentY(), start);
4757     else
4758         QTRY_COMPARE(listview->contentX(), start);
4759
4760     releaseView(canvas);
4761 }
4762
4763 void tst_QQuickListView::marginsResize_data()
4764 {
4765     QTest::addColumn<QQuickListView::Orientation>("orientation");
4766     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
4767     QTest::addColumn<QQuickListView::VerticalLayoutDirection>("verticalLayoutDirection");
4768     QTest::addColumn<qreal>("start");
4769     QTest::addColumn<qreal>("end");
4770
4771     // in Right to Left mode, leftMargin still means leftMargin - it doesn't reverse to mean rightMargin
4772
4773     QTest::newRow("vertical")
4774             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4775             << -40.0 << 1020.0;
4776
4777     QTest::newRow("vertical, BottomToTop")
4778             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4779             << -180.0 << -1240.0;
4780
4781     QTest::newRow("horizontal")
4782             << QQuickListView::Horizontal<< Qt::LeftToRight << QQuickItemView::TopToBottom
4783             << -40.0 << 1020.0;
4784
4785     QTest::newRow("horizontal, rtl")
4786             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4787             << -180.0 << -1240.0;
4788 }
4789
4790 void tst_QQuickListView::snapToItem_data()
4791 {
4792     QTest::addColumn<QQuickListView::Orientation>("orientation");
4793     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
4794     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
4795     QTest::addColumn<int>("highlightRangeMode");
4796     QTest::addColumn<QPoint>("flickStart");
4797     QTest::addColumn<QPoint>("flickEnd");
4798     QTest::addColumn<qreal>("snapAlignment");
4799     QTest::addColumn<qreal>("endExtent");
4800     QTest::addColumn<qreal>("startExtent");
4801
4802     QTest::newRow("vertical, top to bottom")
4803         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
4804         << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 560.0 << 0.0;
4805
4806     QTest::newRow("vertical, bottom to top")
4807         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::NoHighlightRange)
4808         << QPoint(20, 20) << QPoint(20, 200) << -60.0 << -560.0 - 240.0 << -240.0;
4809
4810     QTest::newRow("horizontal, left to right")
4811         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
4812         << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 560.0 << 0.0;
4813
4814     QTest::newRow("horizontal, right to left")
4815         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
4816         << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -560.0 - 240.0 << -240.0;
4817
4818     QTest::newRow("vertical, top to bottom, enforce range")
4819         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
4820         << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 700.0 << -20.0;
4821
4822     QTest::newRow("vertical, bottom to top, enforce range")
4823         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::StrictlyEnforceRange)
4824         << QPoint(20, 20) << QPoint(20, 200) << -60.0 << -560.0 - 240.0 - 140.0 << -220.0;
4825
4826     QTest::newRow("horizontal, left to right, enforce range")
4827         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
4828         << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 700.0 << -20.0;
4829
4830     QTest::newRow("horizontal, right to left, enforce range")
4831         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
4832         << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -560.0 - 240.0 - 140.0 << -220.0;
4833 }
4834
4835 void tst_QQuickListView::snapToItem()
4836 {
4837     QFETCH(QQuickListView::Orientation, orientation);
4838     QFETCH(Qt::LayoutDirection, layoutDirection);
4839     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4840     QFETCH(int, highlightRangeMode);
4841     QFETCH(QPoint, flickStart);
4842     QFETCH(QPoint, flickEnd);
4843     QFETCH(qreal, snapAlignment);
4844     QFETCH(qreal, endExtent);
4845     QFETCH(qreal, startExtent);
4846
4847     QQuickView *canvas = getView();
4848
4849     canvas->setSource(testFileUrl("snapToItem.qml"));
4850     canvas->show();
4851     qApp->processEvents();
4852
4853     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
4854     QTRY_VERIFY(listview != 0);
4855
4856     listview->setOrientation(orientation);
4857     listview->setLayoutDirection(layoutDirection);
4858     listview->setVerticalLayoutDirection(verticalLayoutDirection);
4859     listview->setHighlightRangeMode(QQuickItemView::HighlightRangeMode(highlightRangeMode));
4860     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4861
4862     QQuickItem *contentItem = listview->contentItem();
4863     QTRY_VERIFY(contentItem != 0);
4864
4865     // confirm that a flick hits an item boundary
4866     flick(canvas, flickStart, flickEnd, 180);
4867     QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
4868     if (orientation == QQuickListView::Vertical)
4869         QCOMPARE(qreal(fmod(listview->contentY(),80.0)), snapAlignment);
4870     else
4871         QCOMPARE(qreal(fmod(listview->contentX(),80.0)), snapAlignment);
4872
4873     // flick to end
4874     do {
4875         flick(canvas, flickStart, flickEnd, 180);
4876         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
4877     } while (orientation == QQuickListView::Vertical
4878            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYEnd() : !listview->isAtYBeginning()
4879            : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
4880
4881     if (orientation == QQuickListView::Vertical)
4882         QCOMPARE(listview->contentY(), endExtent);
4883     else
4884         QCOMPARE(listview->contentX(), endExtent);
4885
4886     // flick to start
4887     do {
4888         flick(canvas, flickEnd, flickStart, 180);
4889         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
4890     } while (orientation == QQuickListView::Vertical
4891            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYBeginning() : !listview->isAtYEnd()
4892            : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
4893
4894     if (orientation == QQuickListView::Vertical)
4895         QCOMPARE(listview->contentY(), startExtent);
4896     else
4897         QCOMPARE(listview->contentX(), startExtent);
4898
4899     releaseView(canvas);
4900 }
4901
4902 void tst_QQuickListView::qListModelInterface_items()
4903 {
4904     items<QmlListModel>(testFileUrl("listviewtest.qml"), false);
4905 }
4906
4907 void tst_QQuickListView::qListModelInterface_package_items()
4908 {
4909     items<QmlListModel>(testFileUrl("listviewtest-package.qml"), true);
4910 }
4911
4912 void tst_QQuickListView::qAbstractItemModel_items()
4913 {
4914     items<QaimModel>(testFileUrl("listviewtest.qml"), false);
4915 }
4916
4917 void tst_QQuickListView::qListModelInterface_changed()
4918 {
4919     changed<QmlListModel>(testFileUrl("listviewtest.qml"), false);
4920 }
4921
4922 void tst_QQuickListView::qListModelInterface_package_changed()
4923 {
4924     changed<QmlListModel>(testFileUrl("listviewtest-package.qml"), true);
4925 }
4926
4927 void tst_QQuickListView::qAbstractItemModel_changed()
4928 {
4929     changed<QaimModel>(testFileUrl("listviewtest.qml"), false);
4930 }
4931
4932 void tst_QQuickListView::qListModelInterface_inserted()
4933 {
4934     inserted<QmlListModel>(testFileUrl("listviewtest.qml"));
4935 }
4936
4937 void tst_QQuickListView::qListModelInterface_package_inserted()
4938 {
4939     inserted<QmlListModel>(testFileUrl("listviewtest-package.qml"));
4940 }
4941
4942 void tst_QQuickListView::qListModelInterface_inserted_more()
4943 {
4944     inserted_more<QmlListModel>();
4945 }
4946
4947 void tst_QQuickListView::qListModelInterface_inserted_more_data()
4948 {
4949     inserted_more_data();
4950 }
4951
4952 void tst_QQuickListView::qAbstractItemModel_inserted()
4953 {
4954     inserted<QaimModel>(testFileUrl("listviewtest.qml"));
4955 }
4956
4957 void tst_QQuickListView::qAbstractItemModel_inserted_more()
4958 {
4959     inserted_more<QaimModel>();
4960 }
4961
4962 void tst_QQuickListView::qAbstractItemModel_inserted_more_data()
4963 {
4964     inserted_more_data();
4965 }
4966
4967 void tst_QQuickListView::qAbstractItemModel_inserted_more_bottomToTop()
4968 {
4969     inserted_more<QaimModel>(QQuickItemView::BottomToTop);
4970 }
4971
4972 void tst_QQuickListView::qAbstractItemModel_inserted_more_bottomToTop_data()
4973 {
4974     inserted_more_data();
4975 }
4976
4977 void tst_QQuickListView::qListModelInterface_removed()
4978 {
4979     removed<QmlListModel>(testFileUrl("listviewtest.qml"), false);
4980     removed<QmlListModel>(testFileUrl("listviewtest.qml"), true);
4981 }
4982
4983 void tst_QQuickListView::qListModelInterface_removed_more()
4984 {
4985     removed_more<QmlListModel>(testFileUrl("listviewtest.qml"));
4986 }
4987
4988 void tst_QQuickListView::qListModelInterface_removed_more_data()
4989 {
4990     removed_more_data();
4991 }
4992
4993 void tst_QQuickListView::qListModelInterface_package_removed()
4994 {
4995     removed<QmlListModel>(testFileUrl("listviewtest-package.qml"), false);
4996     removed<QmlListModel>(testFileUrl("listviewtest-package.qml"), true);
4997 }
4998
4999 void tst_QQuickListView::qAbstractItemModel_removed()
5000 {
5001     removed<QaimModel>(testFileUrl("listviewtest.qml"), false);
5002     removed<QaimModel>(testFileUrl("listviewtest.qml"), true);
5003 }
5004
5005 void tst_QQuickListView::qAbstractItemModel_removed_more()
5006 {
5007     removed_more<QaimModel>(testFileUrl("listviewtest.qml"));
5008 }
5009
5010 void tst_QQuickListView::qAbstractItemModel_removed_more_data()
5011 {
5012     removed_more_data();
5013 }
5014
5015 void tst_QQuickListView::qAbstractItemModel_removed_more_bottomToTop()
5016 {
5017     removed_more<QaimModel>(testFileUrl("listviewtest.qml"), QQuickItemView::BottomToTop);
5018 }
5019
5020 void tst_QQuickListView::qAbstractItemModel_removed_more_bottomToTop_data()
5021 {
5022     removed_more_data();
5023 }
5024
5025 void tst_QQuickListView::qListModelInterface_moved()
5026 {
5027     moved<QmlListModel>(testFileUrl("listviewtest.qml"));
5028 }
5029
5030 void tst_QQuickListView::qListModelInterface_moved_data()
5031 {
5032     moved_data();
5033 }
5034
5035 void tst_QQuickListView::qListModelInterface_package_moved()
5036 {
5037     moved<QmlListModel>(testFileUrl("listviewtest-package.qml"));
5038 }
5039
5040 void tst_QQuickListView::qListModelInterface_package_moved_data()
5041 {
5042     moved_data();
5043 }
5044
5045 void tst_QQuickListView::qAbstractItemModel_moved()
5046 {
5047     moved<QaimModel>(testFileUrl("listviewtest.qml"));
5048 }
5049
5050 void tst_QQuickListView::qAbstractItemModel_moved_data()
5051 {
5052     moved_data();
5053 }
5054
5055 void tst_QQuickListView::qAbstractItemModel_moved_bottomToTop()
5056 {
5057     moved<QaimModel>(testFileUrl("listviewtest-package.qml"), QQuickItemView::BottomToTop);
5058 }
5059
5060 void tst_QQuickListView::qAbstractItemModel_moved_bottomToTop_data()
5061 {
5062     moved_data();
5063 }
5064
5065 void tst_QQuickListView::qListModelInterface_clear()
5066 {
5067     clear<QmlListModel>(testFileUrl("listviewtest.qml"));
5068 }
5069
5070 void tst_QQuickListView::qListModelInterface_package_clear()
5071 {
5072     clear<QmlListModel>(testFileUrl("listviewtest-package.qml"));
5073 }
5074
5075 void tst_QQuickListView::qAbstractItemModel_clear()
5076 {
5077     clear<QaimModel>(testFileUrl("listviewtest.qml"));
5078 }
5079
5080 void tst_QQuickListView::qAbstractItemModel_clear_bottomToTop()
5081 {
5082     clear<QaimModel>(testFileUrl("listviewtest.qml"), QQuickItemView::BottomToTop);
5083 }
5084
5085 void tst_QQuickListView::qListModelInterface_sections()
5086 {
5087     sections<QmlListModel>(testFileUrl("listview-sections.qml"));
5088 }
5089
5090 void tst_QQuickListView::qListModelInterface_package_sections()
5091 {
5092     sections<QmlListModel>(testFileUrl("listview-sections-package.qml"));
5093 }
5094
5095 void tst_QQuickListView::qAbstractItemModel_sections()
5096 {
5097     sections<QaimModel>(testFileUrl("listview-sections.qml"));
5098 }
5099
5100 void tst_QQuickListView::creationContext()
5101 {
5102     QQuickView canvas;
5103     canvas.setGeometry(0,0,240,320);
5104     canvas.setSource(testFileUrl("creationContext.qml"));
5105     qApp->processEvents();
5106
5107     QQuickItem *rootItem = qobject_cast<QQuickItem *>(canvas.rootObject());
5108     QVERIFY(rootItem);
5109     QVERIFY(rootItem->property("count").toInt() > 0);
5110
5111     QQuickItem *item;
5112     QVERIFY(item = findItem<QQuickItem>(rootItem, "listItem"));
5113     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5114     QVERIFY(item = rootItem->findChild<QQuickItem *>("header"));
5115     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5116     QVERIFY(item = rootItem->findChild<QQuickItem *>("footer"));
5117     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5118     QVERIFY(item = rootItem->findChild<QQuickItem *>("section"));
5119     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5120 }
5121
5122 void tst_QQuickListView::QTBUG_21742()
5123 {
5124     QQuickView canvas;
5125     canvas.setGeometry(0,0,200,200);
5126     canvas.setSource(testFileUrl("qtbug-21742.qml"));
5127     qApp->processEvents();
5128
5129     QQuickItem *rootItem = qobject_cast<QQuickItem *>(canvas.rootObject());
5130     QVERIFY(rootItem);
5131     QCOMPARE(rootItem->property("count").toInt(), 1);
5132 }
5133
5134 void tst_QQuickListView::asynchronous()
5135 {
5136     QQuickView *canvas = createView();
5137     canvas->show();
5138     QQmlIncubationController controller;
5139     canvas->engine()->setIncubationController(&controller);
5140
5141     canvas->setSource(testFileUrl("asyncloader.qml"));
5142
5143     QQuickItem *rootObject = qobject_cast<QQuickItem*>(canvas->rootObject());
5144     QVERIFY(rootObject);
5145
5146     QQuickListView *listview = 0;
5147     while (!listview) {
5148         bool b = false;
5149         controller.incubateWhile(&b);
5150         listview = rootObject->findChild<QQuickListView*>("view");
5151     }
5152
5153     // items will be created one at a time
5154     for (int i = 0; i < 8; ++i) {
5155         QVERIFY(findItem<QQuickItem>(listview, "wrapper", i) == 0);
5156         QQuickItem *item = 0;
5157         while (!item) {
5158             bool b = false;
5159             controller.incubateWhile(&b);
5160             item = findItem<QQuickItem>(listview, "wrapper", i);
5161         }
5162     }
5163
5164     {
5165         bool b = true;
5166         controller.incubateWhile(&b);
5167     }
5168
5169     // verify positioning
5170     QQuickItem *contentItem = listview->contentItem();
5171     for (int i = 0; i < 8; ++i) {
5172         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5173         QTRY_COMPARE(item->y(), i*50.0);
5174     }
5175
5176     delete canvas;
5177 }
5178
5179 void tst_QQuickListView::snapOneItem_data()
5180 {
5181     QTest::addColumn<QQuickListView::Orientation>("orientation");
5182     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
5183     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
5184     QTest::addColumn<int>("highlightRangeMode");
5185     QTest::addColumn<QPoint>("flickStart");
5186     QTest::addColumn<QPoint>("flickEnd");
5187     QTest::addColumn<qreal>("snapAlignment");
5188     QTest::addColumn<qreal>("endExtent");
5189     QTest::addColumn<qreal>("startExtent");
5190
5191     QTest::newRow("vertical, top to bottom")
5192         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5193         << QPoint(20, 200) << QPoint(20, 20) << 180.0 << 560.0 << 0.0;
5194
5195     QTest::newRow("vertical, bottom to top")
5196         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::NoHighlightRange)
5197         << QPoint(20, 20) << QPoint(20, 200) << -420.0 << -560.0 - 240.0 << -240.0;
5198
5199     QTest::newRow("horizontal, left to right")
5200         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5201         << QPoint(200, 20) << QPoint(20, 20) << 180.0 << 560.0 << 0.0;
5202
5203     QTest::newRow("horizontal, right to left")
5204         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5205         << QPoint(20, 20) << QPoint(200, 20) << -420.0 << -560.0 - 240.0 << -240.0;
5206
5207     QTest::newRow("vertical, top to bottom, enforce range")
5208         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5209         << QPoint(20, 200) << QPoint(20, 20) << 180.0 << 580.0 << -20.0;
5210
5211     QTest::newRow("vertical, bottom to top, enforce range")
5212         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::StrictlyEnforceRange)
5213         << QPoint(20, 20) << QPoint(20, 200) << -420.0 << -580.0 - 240.0 << -220.0;
5214
5215     QTest::newRow("horizontal, left to right, enforce range")
5216         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5217         << QPoint(200, 20) << QPoint(20, 20) << 180.0 << 580.0 << -20.0;
5218
5219     QTest::newRow("horizontal, right to left, enforce range")
5220         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5221         << QPoint(20, 20) << QPoint(200, 20) << -420.0 << -580.0 - 240.0 << -220.0;
5222 }
5223
5224 void tst_QQuickListView::snapOneItem()
5225 {
5226     QFETCH(QQuickListView::Orientation, orientation);
5227     QFETCH(Qt::LayoutDirection, layoutDirection);
5228     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
5229     QFETCH(int, highlightRangeMode);
5230     QFETCH(QPoint, flickStart);
5231     QFETCH(QPoint, flickEnd);
5232     QFETCH(qreal, snapAlignment);
5233     QFETCH(qreal, endExtent);
5234     QFETCH(qreal, startExtent);
5235
5236 #ifdef Q_OS_MAC
5237     // This test seems to be unreliable - different test data fails on different runs
5238     QSKIP("QTBUG-24338");
5239 #endif
5240
5241     QQuickView *canvas = getView();
5242
5243     canvas->setSource(testFileUrl("snapOneItem.qml"));
5244     canvas->show();
5245     qApp->processEvents();
5246
5247     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
5248     QTRY_VERIFY(listview != 0);
5249
5250     listview->setOrientation(orientation);
5251     listview->setLayoutDirection(layoutDirection);
5252     listview->setVerticalLayoutDirection(verticalLayoutDirection);
5253     listview->setHighlightRangeMode(QQuickItemView::HighlightRangeMode(highlightRangeMode));
5254     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5255
5256     QQuickItem *contentItem = listview->contentItem();
5257     QTRY_VERIFY(contentItem != 0);
5258
5259     QSignalSpy currentIndexSpy(listview, SIGNAL(currentIndexChanged()));
5260
5261     // confirm that a flick hits the next item boundary
5262     flick(canvas, flickStart, flickEnd, 180);
5263     QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5264     if (orientation == QQuickListView::Vertical)
5265         QCOMPARE(listview->contentY(), snapAlignment);
5266     else
5267         QCOMPARE(listview->contentX(), snapAlignment);
5268
5269     if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
5270         QCOMPARE(listview->currentIndex(), 1);
5271         QCOMPARE(currentIndexSpy.count(), 1);
5272     }
5273
5274     // flick to end
5275     do {
5276         flick(canvas, flickStart, flickEnd, 180);
5277         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5278     } while (orientation == QQuickListView::Vertical
5279            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYEnd() : !listview->isAtYBeginning()
5280            : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
5281
5282     if (orientation == QQuickListView::Vertical)
5283         QCOMPARE(listview->contentY(), endExtent);
5284     else
5285         QCOMPARE(listview->contentX(), endExtent);
5286
5287     if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
5288         QCOMPARE(listview->currentIndex(), 3);
5289         QCOMPARE(currentIndexSpy.count(), 3);
5290     }
5291
5292     // flick to start
5293     do {
5294         flick(canvas, flickEnd, flickStart, 180);
5295         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5296     } while (orientation == QQuickListView::Vertical
5297            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYBeginning() : !listview->isAtYEnd()
5298            : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
5299
5300     if (orientation == QQuickListView::Vertical)
5301         QCOMPARE(listview->contentY(), startExtent);
5302     else
5303         QCOMPARE(listview->contentX(), startExtent);
5304
5305     if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
5306         QCOMPARE(listview->currentIndex(), 0);
5307         QCOMPARE(currentIndexSpy.count(), 6);
5308     }
5309
5310     releaseView(canvas);
5311 }
5312
5313 void tst_QQuickListView::unrequestedVisibility()
5314 {
5315     QmlListModel model;
5316     for (int i = 0; i < 30; i++)
5317         model.addItem("Item" + QString::number(i), QString::number(i));
5318
5319     QQuickView *canvas = new QQuickView(0);
5320     canvas->setGeometry(0,0,240,320);
5321
5322     QQmlContext *ctxt = canvas->rootContext();
5323     ctxt->setContextProperty("testModel", &model);
5324     ctxt->setContextProperty("testWrap", QVariant(false));
5325
5326     canvas->setSource(testFileUrl("unrequestedItems.qml"));
5327     canvas->show();
5328     qApp->processEvents();
5329
5330     QQuickListView *leftview = findItem<QQuickListView>(canvas->rootObject(), "leftList");
5331     QTRY_VERIFY(leftview != 0);
5332
5333     QQuickListView *rightview = findItem<QQuickListView>(canvas->rootObject(), "rightList");
5334     QTRY_VERIFY(rightview != 0);
5335
5336     QQuickItem *leftContent = leftview->contentItem();
5337     QTRY_VERIFY(leftContent != 0);
5338
5339     QQuickItem *rightContent = rightview->contentItem();
5340     QTRY_VERIFY(rightContent != 0);
5341
5342     rightview->setCurrentIndex(20);
5343
5344     QTRY_COMPARE(leftview->contentY(), 0.0);
5345     QTRY_COMPARE(rightview->contentY(), 100.0);
5346
5347     QQuickItem *item;
5348
5349     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5350     QCOMPARE(delegateVisible(item), true);
5351     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5352     QCOMPARE(delegateVisible(item), false);
5353
5354     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 19));
5355     QCOMPARE(delegateVisible(item), false);
5356     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 19));
5357     QCOMPARE(delegateVisible(item), true);
5358
5359     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 16));
5360     QCOMPARE(delegateVisible(item), true);
5361     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 17));
5362     QCOMPARE(delegateVisible(item), false);
5363     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 3));
5364     QCOMPARE(delegateVisible(item), false);
5365     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 4));
5366     QCOMPARE(delegateVisible(item), true);
5367
5368     rightview->setCurrentIndex(0);
5369
5370     QTRY_COMPARE(leftview->contentY(), 0.0);
5371     QTRY_COMPARE(rightview->contentY(), 0.0);
5372
5373     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5374     QCOMPARE(delegateVisible(item), true);
5375     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5376     QTRY_COMPARE(delegateVisible(item), true);
5377
5378     QVERIFY(!findItem<QQuickItem>(leftContent, "wrapper", 19));
5379     QVERIFY(!findItem<QQuickItem>(rightContent, "wrapper", 19));
5380
5381     leftview->setCurrentIndex(20);
5382
5383     QTRY_COMPARE(leftview->contentY(), 100.0);
5384     QTRY_COMPARE(rightview->contentY(), 0.0);
5385
5386     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5387     QTRY_COMPARE(delegateVisible(item), false);
5388     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5389     QCOMPARE(delegateVisible(item), true);
5390
5391     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 19));
5392     QCOMPARE(delegateVisible(item), true);
5393     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 19));
5394     QCOMPARE(delegateVisible(item), false);
5395
5396     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 3));
5397     QCOMPARE(delegateVisible(item), false);
5398     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5399     QCOMPARE(delegateVisible(item), true);
5400     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5401     QCOMPARE(delegateVisible(item), true);
5402     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5403     QCOMPARE(delegateVisible(item), false);
5404
5405     model.moveItems(19, 1, 1);
5406     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5407
5408     QTRY_VERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5409     QCOMPARE(delegateVisible(item), false);
5410     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5411     QCOMPARE(delegateVisible(item), true);
5412
5413     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 19));
5414     QCOMPARE(delegateVisible(item), true);
5415     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 19));
5416     QCOMPARE(delegateVisible(item), false);
5417
5418     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5419     QCOMPARE(delegateVisible(item), false);
5420     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5421     QCOMPARE(delegateVisible(item), true);
5422     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5423     QCOMPARE(delegateVisible(item), true);
5424     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5425     QCOMPARE(delegateVisible(item), false);
5426
5427     model.moveItems(3, 4, 1);
5428     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5429
5430     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5431     QCOMPARE(delegateVisible(item), false);
5432     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5433     QCOMPARE(delegateVisible(item), true);
5434     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5435     QCOMPARE(delegateVisible(item), true);
5436     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5437     QCOMPARE(delegateVisible(item), false);
5438
5439     model.moveItems(4, 3, 1);
5440     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5441
5442     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5443     QCOMPARE(delegateVisible(item), false);
5444     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5445     QCOMPARE(delegateVisible(item), true);
5446     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5447     QCOMPARE(delegateVisible(item), true);
5448     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5449     QCOMPARE(delegateVisible(item), false);
5450
5451     model.moveItems(16, 17, 1);
5452     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5453
5454     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5455     QCOMPARE(delegateVisible(item), false);
5456     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5457     QCOMPARE(delegateVisible(item), true);
5458     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5459     QCOMPARE(delegateVisible(item), true);
5460     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5461     QCOMPARE(delegateVisible(item), false);
5462
5463     model.moveItems(17, 16, 1);
5464     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5465
5466     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5467     QCOMPARE(delegateVisible(item), false);
5468     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5469     QCOMPARE(delegateVisible(item), true);
5470     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5471     QCOMPARE(delegateVisible(item), true);
5472     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5473     QCOMPARE(delegateVisible(item), false);
5474
5475     delete canvas;
5476 }
5477
5478 void tst_QQuickListView::populateTransitions()
5479 {
5480     QFETCH(bool, staticallyPopulate);
5481     QFETCH(bool, dynamicallyPopulate);
5482     QFETCH(bool, usePopulateTransition);
5483
5484     QPointF transitionFrom(-50, -50);
5485     QPointF transitionVia(100, 100);
5486     QaimModel model_transitionFrom;
5487     QaimModel model_transitionVia;
5488
5489     QaimModel model;
5490     if (staticallyPopulate) {
5491         for (int i = 0; i < 30; i++)
5492             model.addItem("item" + QString::number(i), "");
5493     }
5494
5495     QQuickView *canvas = getView();
5496     canvas->rootContext()->setContextProperty("testModel", &model);
5497     canvas->rootContext()->setContextProperty("testObject", new TestObject(canvas->rootContext()));
5498     canvas->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition);
5499     canvas->rootContext()->setContextProperty("dynamicallyPopulate", dynamicallyPopulate);
5500     canvas->rootContext()->setContextProperty("transitionFrom", transitionFrom);
5501     canvas->rootContext()->setContextProperty("transitionVia", transitionVia);
5502     canvas->rootContext()->setContextProperty("model_transitionFrom", &model_transitionFrom);
5503     canvas->rootContext()->setContextProperty("model_transitionVia", &model_transitionVia);
5504     canvas->setSource(testFileUrl("populateTransitions.qml"));
5505     canvas->show();
5506     QTest::qWaitForWindowShown(canvas);
5507
5508     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
5509     QVERIFY(listview);
5510     QQuickItem *contentItem = listview->contentItem();
5511     QVERIFY(contentItem);
5512
5513     if (staticallyPopulate && usePopulateTransition) {
5514         QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 16);
5515         QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
5516     } else if (dynamicallyPopulate) {
5517         QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
5518         QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 16);
5519     } else {
5520         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5521         QCOMPARE(listview->property("countPopulateTransitions").toInt(), 0);
5522         QCOMPARE(listview->property("countAddTransitions").toInt(), 0);
5523     }
5524
5525     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5526     for (int i=0; i < model.count() && i < itemCount; ++i) {
5527         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5528         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5529         QTRY_COMPARE(item->x(), 0.0);
5530         QTRY_COMPARE(item->y(), i*20.0);
5531         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5532         QVERIFY(name != 0);
5533         QTRY_COMPARE(name->text(), model.name(i));
5534     }
5535
5536     listview->setProperty("countPopulateTransitions", 0);
5537     listview->setProperty("countAddTransitions", 0);
5538
5539     // add an item and check this is done with add transition, not populate
5540     model.insertItem(0, "another item", "");
5541     QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 1);
5542     QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
5543
5544     // clear the model
5545     canvas->rootContext()->setContextProperty("testModel", QVariant());
5546     QTRY_COMPARE(listview->count(), 0);
5547     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
5548     listview->setProperty("countPopulateTransitions", 0);
5549     listview->setProperty("countAddTransitions", 0);
5550
5551     // set to a valid model and check populate transition is run a second time
5552     model.clear();
5553     for (int i = 0; i < 30; i++)
5554         model.addItem("item" + QString::number(i), "");
5555     canvas->rootContext()->setContextProperty("testModel", &model);
5556     QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 16 : 0);
5557     QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
5558
5559     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5560     for (int i=0; i < model.count() && i < itemCount; ++i) {
5561         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5562         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5563         QTRY_COMPARE(item->x(), 0.0);
5564         QTRY_COMPARE(item->y(), i*20.0);
5565         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5566         QVERIFY(name != 0);
5567         QTRY_COMPARE(name->text(), model.name(i));
5568     }
5569
5570     // reset model and check populate transition is run again
5571     listview->setProperty("countPopulateTransitions", 0);
5572     listview->setProperty("countAddTransitions", 0);
5573     model.reset();
5574     QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 16 : 0);
5575     QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
5576
5577     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5578     for (int i=0; i < model.count() && i < itemCount; ++i) {
5579         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5580         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5581         QTRY_COMPARE(item->x(), 0.0);
5582         QTRY_COMPARE(item->y(), i*20.0);
5583         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5584         QVERIFY(name != 0);
5585         QTRY_COMPARE(name->text(), model.name(i));
5586     }
5587
5588     releaseView(canvas);
5589 }
5590
5591 void tst_QQuickListView::populateTransitions_data()
5592 {
5593     QTest::addColumn<bool>("staticallyPopulate");
5594     QTest::addColumn<bool>("dynamicallyPopulate");
5595     QTest::addColumn<bool>("usePopulateTransition");
5596
5597     QTest::newRow("static") << true << false << true;
5598     QTest::newRow("static, no populate") << true << false << false;
5599
5600     QTest::newRow("dynamic") << false << true << true;
5601     QTest::newRow("dynamic, no populate") << false << true << false;
5602
5603     QTest::newRow("empty to start with") << false << false << true;
5604     QTest::newRow("empty to start with, no populate") << false << false << false;
5605 }
5606
5607 void tst_QQuickListView::addTransitions()
5608 {
5609     QFETCH(int, initialItemCount);
5610     QFETCH(bool, shouldAnimateTargets);
5611     QFETCH(qreal, contentY);
5612     QFETCH(int, insertionIndex);
5613     QFETCH(int, insertionCount);
5614     QFETCH(ListRange, expectedDisplacedIndexes);
5615
5616     // added items should start here
5617     QPointF targetItems_transitionFrom(-50, -50);
5618
5619     // displaced items should pass through this point
5620     QPointF displacedItems_transitionVia(100, 100);
5621
5622     QaimModel model;
5623     for (int i = 0; i < initialItemCount; i++)
5624         model.addItem("Original item" + QString::number(i), "");
5625     QaimModel model_targetItems_transitionFrom;
5626     QaimModel model_displacedItems_transitionVia;
5627
5628     QQuickView *canvas = getView();
5629     QQmlContext *ctxt = canvas->rootContext();
5630     TestObject *testObject = new TestObject;
5631     ctxt->setContextProperty("testModel", &model);
5632     ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
5633     ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
5634     ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
5635     ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
5636     ctxt->setContextProperty("testObject", testObject);
5637     canvas->setSource(testFileUrl("addTransitions.qml"));
5638     canvas->show();
5639     QTest::qWaitForWindowShown(canvas);
5640
5641     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
5642     QTRY_VERIFY(listview != 0);
5643     QQuickItem *contentItem = listview->contentItem();
5644     QVERIFY(contentItem != 0);
5645     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5646
5647     if (contentY != 0) {
5648         listview->setContentY(contentY);
5649         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5650     }
5651
5652     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
5653
5654     // only target items that will become visible should be animated
5655     QList<QPair<QString, QString> > newData;
5656     QList<QPair<QString, QString> > expectedTargetData;
5657     QList<int> targetIndexes;
5658     if (shouldAnimateTargets) {
5659         for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
5660             newData << qMakePair(QString("New item %1").arg(i), QString(""));
5661
5662             if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {  // only grab visible items
5663                 expectedTargetData << newData.last();
5664                 targetIndexes << i;
5665             }
5666         }
5667         QVERIFY(expectedTargetData.count() > 0);
5668     }
5669
5670     // start animation
5671     if (!newData.isEmpty()) {
5672         model.insertItems(insertionIndex, newData);
5673         QTRY_COMPARE(model.count(), listview->count());
5674     }
5675
5676     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
5677
5678     if (shouldAnimateTargets) {
5679         QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
5680         QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
5681                      expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
5682
5683         // check the target and displaced items were animated
5684         model_targetItems_transitionFrom.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
5685         model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
5686
5687         // check attached properties
5688         matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
5689         matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
5690         matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
5691         if (expectedDisplacedIndexes.isValid()) {
5692             // adjust expectedDisplacedIndexes to their final values after the move
5693             QList<int> displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount);
5694             matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
5695             matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
5696             matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
5697         }
5698
5699     } else {
5700         QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0);
5701         QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
5702     }
5703
5704     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
5705     int firstVisibleIndex = -1;
5706     int itemCount = items.count();
5707     for (int i=0; i<items.count(); i++) {
5708         if (items[i]->y() >= contentY) {
5709             QQmlExpression e(qmlContext(items[i]), items[i], "index");
5710             firstVisibleIndex = e.evaluate().toInt();
5711             break;
5712         }
5713     }
5714     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
5715
5716     // verify all items moved to the correct final positions
5717     for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
5718         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5719         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5720         QTRY_COMPARE(item->y(), i*20.0);
5721         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5722         QVERIFY(name != 0);
5723         QTRY_COMPARE(name->text(), model.name(i));
5724     }
5725
5726     releaseView(canvas);
5727     delete testObject;
5728 }
5729
5730 void tst_QQuickListView::addTransitions_data()
5731 {
5732     QTest::addColumn<int>("initialItemCount");
5733     QTest::addColumn<qreal>("contentY");
5734     QTest::addColumn<bool>("shouldAnimateTargets");
5735     QTest::addColumn<int>("insertionIndex");
5736     QTest::addColumn<int>("insertionCount");
5737     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
5738
5739     // if inserting before visible index, items should not appear or animate in, even if there are > 1 new items
5740     QTest::newRow("insert 1, just before start")
5741             << 30 << 20.0 << false
5742             << 0 << 1 << ListRange();
5743     QTest::newRow("insert 1, way before start")
5744             << 30 << 20.0 << false
5745             << 0 << 1 << ListRange();
5746     QTest::newRow("insert multiple, just before start")
5747             << 30 << 100.0 << false
5748             << 0 << 3 << ListRange();
5749     QTest::newRow("insert multiple, way before start")
5750             << 30 << 100.0 << false
5751             << 0 << 3 << ListRange();
5752
5753     QTest::newRow("insert 1 at start")
5754             << 30 << 0.0 << true
5755             << 0 << 1 << ListRange(0, 15);
5756     QTest::newRow("insert multiple at start")
5757             << 30 << 0.0 << true
5758             << 0 << 3 << ListRange(0, 15);
5759     QTest::newRow("insert 1 at start, content y not 0")
5760             << 30 << 40.0 << true  // first visible is index 2, so translate the displaced indexes by 2
5761             << 2 << 1 << ListRange(0 + 2, 15 + 2);
5762     QTest::newRow("insert multiple at start, content y not 0")
5763             << 30 << 40.0 << true    // first visible is index 2
5764             << 2 << 3 << ListRange(0 + 2, 15 + 2);
5765
5766     QTest::newRow("insert 1 at start, to empty list")
5767             << 0 << 0.0 << true
5768             << 0 << 1 << ListRange();
5769     QTest::newRow("insert multiple at start, to empty list")
5770             << 0 << 0.0 << true
5771             << 0 << 3 << ListRange();
5772
5773     QTest::newRow("insert 1 at middle")
5774             << 30 << 0.0 << true
5775             << 5 << 1 << ListRange(5, 15);
5776     QTest::newRow("insert multiple at middle")
5777             << 30 << 0.0 << true
5778             << 5 << 3 << ListRange(5, 15);
5779
5780     QTest::newRow("insert 1 at bottom")
5781             << 30 << 0.0 << true
5782             << 15 << 1 << ListRange(15, 15);
5783     QTest::newRow("insert multiple at bottom")
5784             << 30 << 0.0 << true
5785             << 15 << 3 << ListRange(15, 15);
5786     QTest::newRow("insert 1 at bottom, content y not 0")
5787             << 30 << 20.0 * 3 << true
5788             << 15 + 3 << 1 << ListRange(15 + 3, 15 + 3);
5789     QTest::newRow("insert multiple at bottom, content y not 0")
5790             << 30 << 20.0 * 3 << true
5791             << 15 + 3 << 3 << ListRange(15 + 3, 15 + 3);
5792
5793     // items added after the last visible will not be animated in, since they
5794     // do not appear in the final view
5795     QTest::newRow("insert 1 after end")
5796             << 30 << 0.0 << false
5797             << 17 << 1 << ListRange();
5798     QTest::newRow("insert multiple after end")
5799             << 30 << 0.0 << false
5800             << 17 << 3 << ListRange();
5801 }
5802
5803 void tst_QQuickListView::moveTransitions()
5804 {
5805     QFETCH(int, initialItemCount);
5806     QFETCH(qreal, contentY);
5807     QFETCH(qreal, itemsOffsetAfterMove);
5808     QFETCH(int, moveFrom);
5809     QFETCH(int, moveTo);
5810     QFETCH(int, moveCount);
5811     QFETCH(ListRange, expectedDisplacedIndexes);
5812
5813     // target and displaced items should pass through these points
5814     QPointF targetItems_transitionVia(-50, 50);
5815     QPointF displacedItems_transitionVia(100, 100);
5816
5817     QaimModel model;
5818     for (int i = 0; i < initialItemCount; i++)
5819         model.addItem("Original item" + QString::number(i), "");
5820     QaimModel model_targetItems_transitionVia;
5821     QaimModel model_displacedItems_transitionVia;
5822
5823     QQuickView *canvas = getView();
5824     QQmlContext *ctxt = canvas->rootContext();
5825     TestObject *testObject = new TestObject;
5826     ctxt->setContextProperty("testModel", &model);
5827     ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia);
5828     ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
5829     ctxt->setContextProperty("targetItems_transitionVia", targetItems_transitionVia);
5830     ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
5831     ctxt->setContextProperty("testObject", testObject);
5832     canvas->setSource(testFileUrl("moveTransitions.qml"));
5833     canvas->show();
5834     QTest::qWaitForWindowShown(canvas);
5835
5836     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
5837     QTRY_VERIFY(listview != 0);
5838     QQuickItem *contentItem = listview->contentItem();
5839     QVERIFY(contentItem != 0);
5840     QQuickText *name;
5841
5842     if (contentY != 0) {
5843         listview->setContentY(contentY);
5844         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5845     }
5846
5847     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
5848
5849     // Items moving to *or* from visible positions should be animated.
5850     // Otherwise, they should not be animated.
5851     QList<QPair<QString, QString> > expectedTargetData;
5852     QList<int> targetIndexes;
5853     for (int i=moveFrom; i<moveFrom+moveCount; i++) {
5854         int toIndex = moveTo + (i - moveFrom);
5855         if (i <= (contentY + listview->height()) / 20
5856                 || toIndex < (contentY + listview->height()) / 20) {
5857             expectedTargetData << qMakePair(model.name(i), model.number(i));
5858             targetIndexes << i;
5859         }
5860     }
5861     // ViewTransition.index provides the indices that items are moving to, not from
5862     targetIndexes = adjustIndexesForMove(targetIndexes, moveFrom, moveTo, moveCount);
5863
5864     // start animation
5865     model.moveItems(moveFrom, moveTo, moveCount);
5866
5867     QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
5868     QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
5869                  expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
5870
5871     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
5872
5873     // check the target and displaced items were animated
5874     model_targetItems_transitionVia.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
5875     model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
5876
5877     // check attached properties
5878     matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
5879     matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
5880     matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
5881     if (expectedDisplacedIndexes.isValid()) {
5882         // adjust expectedDisplacedIndexes to their final values after the move
5883         QList<int> displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, moveFrom, moveTo, moveCount);
5884         matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
5885         matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
5886         matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
5887     }
5888
5889     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
5890     int firstVisibleIndex = -1;
5891     for (int i=0; i<items.count(); i++) {
5892         if (items[i]->y() >= contentY) {
5893             QQmlExpression e(qmlContext(items[i]), items[i], "index");
5894             firstVisibleIndex = e.evaluate().toInt();
5895             break;
5896         }
5897     }
5898     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
5899
5900     // verify all items moved to the correct final positions
5901     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5902     for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
5903         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5904         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5905         QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
5906         name = findItem<QQuickText>(contentItem, "textName", i);
5907         QVERIFY(name != 0);
5908         QTRY_COMPARE(name->text(), model.name(i));
5909     }
5910
5911     releaseView(canvas);
5912     delete testObject;
5913 }
5914
5915 void tst_QQuickListView::moveTransitions_data()
5916 {
5917     QTest::addColumn<int>("initialItemCount");
5918     QTest::addColumn<qreal>("contentY");
5919     QTest::addColumn<qreal>("itemsOffsetAfterMove");
5920     QTest::addColumn<int>("moveFrom");
5921     QTest::addColumn<int>("moveTo");
5922     QTest::addColumn<int>("moveCount");
5923     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
5924
5925     // when removing from above the visible, all items shift down depending on how many
5926     // items have been removed from above the visible
5927     QTest::newRow("move from above view, outside visible items, move 1") << 30 << 4*20.0 << 20.0
5928             << 1 << 10 << 1 << ListRange(11, 15+4);
5929     QTest::newRow("move from above view, outside visible items, move 1 (first item)") << 30 << 4*20.0 << 20.0
5930             << 0 << 10 << 1 << ListRange(11, 15+4);
5931     QTest::newRow("move from above view, outside visible items, move multiple") << 30 << 4*20.0 << 2*20.0
5932             << 1 << 10 << 2 << ListRange(12, 15+4);
5933     QTest::newRow("move from above view, outside visible items, move multiple (first item)") << 30 << 4*20.0 << 3*20.0
5934             << 0 << 10 << 3 << ListRange(13, 15+4);
5935     QTest::newRow("move from above view, mix of visible/non-visible") << 30 << 4*20.0 << 3*20.0
5936             << 1 << 10 << 5 << ListRange(6, 14) + ListRange(15, 15+4);
5937     QTest::newRow("move from above view, mix of visible/non-visible (move first)") << 30 << 4*20.0 << 4*20.0
5938             << 0 << 10 << 5 << ListRange(5, 14) + ListRange(15, 15+4);
5939
5940     QTest::newRow("move within view, move 1 down") << 30 << 0.0 << 0.0
5941             << 1 << 10 << 1 << ListRange(2, 10);
5942     QTest::newRow("move within view, move 1 down, move first item") << 30 << 0.0 << 0.0
5943             << 0 << 10 << 1 << ListRange(1, 10);
5944     QTest::newRow("move within view, move 1 down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
5945             << 0+4 << 10+4 << 1 << ListRange(1+4, 10+4);
5946     QTest::newRow("move within view, move 1 down, to last item") << 30 << 0.0 << 0.0
5947             << 10 << 15 << 1 << ListRange(11, 15);
5948     QTest::newRow("move within view, move first->last") << 30 << 0.0 << 0.0
5949             << 0 << 15 << 1 << ListRange(1, 15);
5950
5951     QTest::newRow("move within view, move multiple down") << 30 << 0.0 << 0.0
5952             << 1 << 10 << 3 << ListRange(4, 12);
5953     QTest::newRow("move within view, move multiple down, move first item") << 30 << 0.0 << 0.0
5954             << 0 << 10 << 3 << ListRange(3, 12);
5955     QTest::newRow("move within view, move multiple down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
5956             << 0+4 << 10+4 << 3 << ListRange(3+4, 12+4);
5957     QTest::newRow("move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0
5958             << 5 << 13 << 3 << ListRange(8, 15);
5959     QTest::newRow("move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0
5960             << 0 << 13 << 3 << ListRange(3, 15);
5961
5962     QTest::newRow("move within view, move 1 up") << 30 << 0.0 << 0.0
5963             << 10 << 1 << 1 << ListRange(1, 9);
5964     QTest::newRow("move within view, move 1 up, move to first index") << 30 << 0.0 << 0.0
5965             << 10 << 0 << 1 << ListRange(0, 9);
5966     QTest::newRow("move within view, move 1 up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
5967             << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
5968     QTest::newRow("move within view, move 1 up, move to first index, contentY not on item border") << 30 << 4*20.0 - 10 << 0.0
5969             << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
5970     QTest::newRow("move within view, move 1 up, move last item") << 30 << 0.0 << 0.0
5971             << 15 << 10 << 1 << ListRange(10, 14);
5972     QTest::newRow("move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0
5973             << 15 << 0 << 1 << ListRange(0, 14);
5974
5975     QTest::newRow("move within view, move multiple up") << 30 << 0.0 << 0.0
5976             << 10 << 1 << 3 << ListRange(1, 9);
5977     QTest::newRow("move within view, move multiple up, move to first index") << 30 << 0.0 << 0.0
5978             << 10 << 0 << 3 << ListRange(0, 9);
5979     QTest::newRow("move within view, move multiple up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
5980             << 10+4 << 0+4 << 3 << ListRange(0+4, 9+4);
5981     QTest::newRow("move within view, move multiple up, move last item") << 30 << 0.0 << 0.0
5982             << 13 << 5 << 3 << ListRange(5, 12);
5983     QTest::newRow("move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0
5984             << 13 << 0 << 3 << ListRange(0, 12);
5985
5986     QTest::newRow("move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0
5987             << 20 << 0 << 1 << ListRange(0, 15);
5988     QTest::newRow("move from below view, move 1 up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
5989             << 25 << 4 << 1 << ListRange(0+4, 15+4);
5990     QTest::newRow("move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0
5991             << 20 << 0 << 3 << ListRange(0, 15);
5992     QTest::newRow("move from below view, move multiple up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
5993             << 25 << 4 << 3 << ListRange(0+4, 15+4);
5994
5995     QTest::newRow("move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0
5996             << 20 << 15 << 1 << ListRange(15, 15);
5997     QTest::newRow("move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
5998             << 25 << 15+4 << 1 << ListRange(15+4, 15+4);
5999     QTest::newRow("move from below view, move multiple up, move to to bottom") << 30 << 0.0 << 0.0
6000             << 20 << 15 << 3 << ListRange(15, 15);
6001     QTest::newRow("move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
6002             << 25 << 15+4 << 3 << ListRange(15+4, 15+4);
6003 }
6004
6005 void tst_QQuickListView::removeTransitions()
6006 {
6007     QFETCH(int, initialItemCount);
6008     QFETCH(bool, shouldAnimateTargets);
6009     QFETCH(qreal, contentY);
6010     QFETCH(int, removalIndex);
6011     QFETCH(int, removalCount);
6012     QFETCH(ListRange, expectedDisplacedIndexes);
6013
6014     // added items should end here
6015     QPointF targetItems_transitionTo(-50, -50);
6016
6017     // displaced items should pass through this points
6018     QPointF displacedItems_transitionVia(100, 100);
6019
6020     QaimModel model;
6021     for (int i = 0; i < initialItemCount; i++)
6022         model.addItem("Original item" + QString::number(i), "");
6023     QaimModel model_targetItems_transitionTo;
6024     QaimModel model_displacedItems_transitionVia;
6025
6026     QQuickView *canvas = getView();
6027     QQmlContext *ctxt = canvas->rootContext();
6028     TestObject *testObject = new TestObject;
6029     ctxt->setContextProperty("testModel", &model);
6030     ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo);
6031     ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
6032     ctxt->setContextProperty("targetItems_transitionTo", targetItems_transitionTo);
6033     ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
6034     ctxt->setContextProperty("testObject", testObject);
6035     canvas->setSource(testFileUrl("removeTransitions.qml"));
6036     canvas->show();
6037     QTest::qWaitForWindowShown(canvas);
6038
6039     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
6040     QTRY_VERIFY(listview != 0);
6041     QQuickItem *contentItem = listview->contentItem();
6042     QVERIFY(contentItem != 0);
6043     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6044
6045     if (contentY != 0) {
6046         listview->setContentY(contentY);
6047         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6048     }
6049
6050     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
6051
6052     // only target items that are visible should be animated
6053     QList<QPair<QString, QString> > expectedTargetData;
6054     QList<int> targetIndexes;
6055     if (shouldAnimateTargets) {
6056         for (int i=removalIndex; i<removalIndex+removalCount; i++) {
6057             if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {
6058                 expectedTargetData << qMakePair(model.name(i), model.number(i));
6059                 targetIndexes << i;
6060             }
6061         }
6062         QVERIFY(expectedTargetData.count() > 0);
6063     }
6064
6065     // calculate targetItems and expectedTargets before model changes
6066     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
6067     QVariantMap expectedTargets;
6068     for (int i=0; i<targetIndexes.count(); i++)
6069         expectedTargets[model.name(targetIndexes[i])] = targetIndexes[i];
6070
6071     // start animation
6072     model.removeItems(removalIndex, removalCount);
6073     QTRY_COMPARE(model.count(), listview->count());
6074
6075     if (shouldAnimateTargets) {
6076         QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
6077         QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
6078                      expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
6079
6080         // check the target and displaced items were animated
6081         model_targetItems_transitionTo.matchAgainst(expectedTargetData, "wasn't animated to target 'to' pos", "shouldn't have been animated to target 'to' pos");
6082         model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
6083
6084         // check attached properties
6085         QCOMPARE(listview->property("targetTrans_items").toMap(), expectedTargets);
6086         matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
6087         matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
6088         if (expectedDisplacedIndexes.isValid()) {
6089             // adjust expectedDisplacedIndexes to their final values after the move
6090             QList<int> displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, removalIndex, removalCount);
6091             matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
6092             matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
6093             matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
6094         }
6095     } else {
6096         QTRY_COMPARE(model_targetItems_transitionTo.count(), 0);
6097         QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
6098     }
6099
6100     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6101     int firstVisibleIndex = -1;
6102     int itemCount = items.count();
6103
6104     for (int i=0; i<items.count(); i++) {
6105         QQmlExpression e(qmlContext(items[i]), items[i], "index");
6106         int index = e.evaluate().toInt();
6107         if (firstVisibleIndex < 0 && items[i]->y() >= contentY)
6108             firstVisibleIndex = index;
6109         if (index < 0)
6110             itemCount--;    // exclude deleted items
6111     }
6112     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
6113
6114     // verify all items moved to the correct final positions
6115     for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
6116         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6117         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6118         QCOMPARE(item->x(), 0.0);
6119         QCOMPARE(item->y(), contentY + (i-firstVisibleIndex) * 20.0);
6120         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6121         QVERIFY(name != 0);
6122         QTRY_COMPARE(name->text(), model.name(i));
6123     }
6124
6125     releaseView(canvas);
6126     delete testObject;
6127 }
6128
6129 void tst_QQuickListView::removeTransitions_data()
6130 {
6131     QTest::addColumn<int>("initialItemCount");
6132     QTest::addColumn<qreal>("contentY");
6133     QTest::addColumn<bool>("shouldAnimateTargets");
6134     QTest::addColumn<int>("removalIndex");
6135     QTest::addColumn<int>("removalCount");
6136     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
6137
6138     // All items that are visible following the remove operation should be animated.
6139     // Remove targets that are outside of the view should not be animated.
6140
6141     QTest::newRow("remove 1 before start")
6142             << 30 << 20.0 * 3 << false
6143             << 2 << 1 << ListRange();
6144     QTest::newRow("remove multiple, all before start")
6145             << 30 << 20.0 * 3 << false
6146             << 0 << 3 << ListRange();
6147     QTest::newRow("remove mix of before and after start")
6148             << 30 << 20.0 * 3 << true
6149             << 2 << 3 << ListRange(5, 20);  // 5-20 are visible after the remove
6150
6151     QTest::newRow("remove 1 from start")
6152             << 30 << 0.0 << true
6153             << 0 << 1 << ListRange(1, 16);  // 1-16 are visible after the remove
6154     QTest::newRow("remove multiple from start")
6155             << 30 << 0.0 << true
6156             << 0 << 3 << ListRange(3, 18);  // 3-18 are visible after the remove
6157     QTest::newRow("remove 1 from start, content y not 0")
6158             << 30 << 20.0 * 2 << true  // first visible is index 2, so translate the displaced indexes by 2
6159             << 2 << 1 << ListRange(1 + 2, 16 + 2);
6160     QTest::newRow("remove multiple from start, content y not 0")
6161             << 30 << 20.0 * 2 << true    // first visible is index 2
6162             << 2 << 3 << ListRange(3 + 2, 18 + 2);
6163
6164     QTest::newRow("remove 1 from middle")
6165             << 30 << 0.0 << true
6166             << 5 << 1 << ListRange(6, 16);
6167     QTest::newRow("remove multiple from middle")
6168             << 30 << 0.0 << true
6169             << 5 << 3 << ListRange(8, 18);
6170
6171
6172     QTest::newRow("remove 1 from bottom")
6173             << 30 << 0.0 << true
6174             << 15 << 1 << ListRange(16, 16);
6175
6176     // remove 15, 16, 17
6177     // 15 will animate as the target item, 16 & 17 won't be animated since they are outside
6178     // the view, and 18 will be animated as the displaced item to replace the last item
6179     QTest::newRow("remove multiple from bottom")
6180             << 30 << 0.0 << true
6181             << 15 << 3 << ListRange(18, 18);
6182
6183     QTest::newRow("remove 1 from bottom, content y not 0")
6184             << 30 << 20.0 * 2 << true
6185             << 15 + 2 << 1 << ListRange(16 + 2, 16 + 2);
6186     QTest::newRow("remove multiple from bottom, content y not 0")
6187             << 30 << 20.0 * 2 << true
6188             << 15 + 2 << 3 << ListRange(18 + 2, 18 + 2);
6189
6190
6191     QTest::newRow("remove 1 after end")
6192             << 30 << 0.0 << false
6193             << 17 << 1 << ListRange();
6194     QTest::newRow("remove multiple after end")
6195             << 30 << 0.0 << false
6196             << 17 << 3 << ListRange();
6197 }
6198
6199 void tst_QQuickListView::displacedTransitions()
6200 {
6201     QFETCH(bool, useDisplaced);
6202     QFETCH(bool, displacedEnabled);
6203     QFETCH(bool, useAddDisplaced);
6204     QFETCH(bool, addDisplacedEnabled);
6205     QFETCH(bool, useMoveDisplaced);
6206     QFETCH(bool, moveDisplacedEnabled);
6207     QFETCH(bool, useRemoveDisplaced);
6208     QFETCH(bool, removeDisplacedEnabled);
6209     QFETCH(ListChange, change);
6210     QFETCH(ListRange, expectedDisplacedIndexes);
6211
6212     QaimModel model;
6213     for (int i = 0; i < 30; i++)
6214         model.addItem("Original item" + QString::number(i), "");
6215     QaimModel model_displaced_transitionVia;
6216     QaimModel model_addDisplaced_transitionVia;
6217     QaimModel model_moveDisplaced_transitionVia;
6218     QaimModel model_removeDisplaced_transitionVia;
6219
6220     QPointF displaced_transitionVia(-50, -100);
6221     QPointF addDisplaced_transitionVia(-150, 100);
6222     QPointF moveDisplaced_transitionVia(50, -100);
6223     QPointF removeDisplaced_transitionVia(150, 100);
6224
6225     QQuickView *canvas = getView();
6226     QQmlContext *ctxt = canvas->rootContext();
6227     TestObject *testObject = new TestObject(canvas);
6228     ctxt->setContextProperty("testModel", &model);
6229     ctxt->setContextProperty("testObject", testObject);
6230     ctxt->setContextProperty("model_displaced_transitionVia", &model_displaced_transitionVia);
6231     ctxt->setContextProperty("model_addDisplaced_transitionVia", &model_addDisplaced_transitionVia);
6232     ctxt->setContextProperty("model_moveDisplaced_transitionVia", &model_moveDisplaced_transitionVia);
6233     ctxt->setContextProperty("model_removeDisplaced_transitionVia", &model_removeDisplaced_transitionVia);
6234     ctxt->setContextProperty("displaced_transitionVia", displaced_transitionVia);
6235     ctxt->setContextProperty("addDisplaced_transitionVia", addDisplaced_transitionVia);
6236     ctxt->setContextProperty("moveDisplaced_transitionVia", moveDisplaced_transitionVia);
6237     ctxt->setContextProperty("removeDisplaced_transitionVia", removeDisplaced_transitionVia);
6238     ctxt->setContextProperty("useDisplaced", useDisplaced);
6239     ctxt->setContextProperty("displacedEnabled", displacedEnabled);
6240     ctxt->setContextProperty("useAddDisplaced", useAddDisplaced);
6241     ctxt->setContextProperty("addDisplacedEnabled", addDisplacedEnabled);
6242     ctxt->setContextProperty("useMoveDisplaced", useMoveDisplaced);
6243     ctxt->setContextProperty("moveDisplacedEnabled", moveDisplacedEnabled);
6244     ctxt->setContextProperty("useRemoveDisplaced", useRemoveDisplaced);
6245     ctxt->setContextProperty("removeDisplacedEnabled", removeDisplacedEnabled);
6246     canvas->setSource(testFileUrl("displacedTransitions.qml"));
6247     canvas->show();
6248     qApp->processEvents();
6249
6250     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
6251     QTRY_VERIFY(listview != 0);
6252     QQuickItem *contentItem = listview->contentItem();
6253     QVERIFY(contentItem != 0);
6254     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6255
6256     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
6257     listview->setProperty("displaceTransitionsDone", false);
6258
6259     switch (change.type) {
6260         case ListChange::Inserted:
6261         {
6262             QList<QPair<QString, QString> > targetItemData;
6263             for (int i=change.index; i<change.index + change.count; ++i)
6264                 targetItemData << qMakePair(QString("new item %1").arg(i), QString::number(i));
6265             model.insertItems(change.index, targetItemData);
6266             QTRY_COMPARE(model.count(), listview->count());
6267             break;
6268         }
6269         case ListChange::Removed:
6270             model.removeItems(change.index, change.count);
6271             QTRY_COMPARE(model.count(), listview->count());
6272             break;
6273         case ListChange::Moved:
6274             model.moveItems(change.index, change.to, change.count);
6275             QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6276             break;
6277         case ListChange::SetCurrent:
6278         case ListChange::SetContentY:
6279         case ListChange::Polish:
6280             break;
6281     }
6282
6283     QVariantList resultTargetIndexes = listview->property("displacedTargetIndexes").toList();
6284     QVariantList resultTargetItems = listview->property("displacedTargetItems").toList();
6285
6286     if ((useDisplaced && displacedEnabled)
6287             || (useAddDisplaced && addDisplacedEnabled)
6288             || (useMoveDisplaced && moveDisplacedEnabled)
6289             || (useRemoveDisplaced && removeDisplacedEnabled)) {
6290         QTRY_VERIFY(listview->property("displaceTransitionsDone").toBool());
6291
6292         // check the correct number of target items and indexes were received
6293         QCOMPARE(resultTargetIndexes.count(), expectedDisplacedIndexes.count());
6294         for (int i=0; i<resultTargetIndexes.count(); i++)
6295             QCOMPARE(resultTargetIndexes[i].value<QList<int> >().count(), change.count);
6296         QCOMPARE(resultTargetItems.count(), expectedDisplacedIndexes.count());
6297         for (int i=0; i<resultTargetItems.count(); i++)
6298             QCOMPARE(resultTargetItems[i].toList().count(), change.count);
6299     } else {
6300         QCOMPARE(resultTargetIndexes.count(), 0);
6301         QCOMPARE(resultTargetItems.count(), 0);
6302     }
6303
6304     if (change.type == ListChange::Inserted && useAddDisplaced && addDisplacedEnabled)
6305         model_addDisplaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with add displaced", "shouldn't have been animated with add displaced");
6306     else
6307         QCOMPARE(model_addDisplaced_transitionVia.count(), 0);
6308     if (change.type == ListChange::Moved && useMoveDisplaced && moveDisplacedEnabled)
6309         model_moveDisplaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with move displaced", "shouldn't have been animated with move displaced");
6310     else
6311         QCOMPARE(model_moveDisplaced_transitionVia.count(), 0);
6312     if (change.type == ListChange::Removed && useRemoveDisplaced && removeDisplacedEnabled)
6313         model_removeDisplaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with remove displaced", "shouldn't have been animated with remove displaced");
6314     else
6315         QCOMPARE(model_removeDisplaced_transitionVia.count(), 0);
6316
6317     if (useDisplaced && displacedEnabled
6318             && ( (change.type == ListChange::Inserted && (!useAddDisplaced || !addDisplacedEnabled))
6319                  || (change.type == ListChange::Moved && (!useMoveDisplaced || !moveDisplacedEnabled))
6320                  || (change.type == ListChange::Removed && (!useRemoveDisplaced || !removeDisplacedEnabled))) ) {
6321         model_displaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with generic displaced", "shouldn't have been animated with generic displaced");
6322     } else {
6323         QCOMPARE(model_displaced_transitionVia.count(), 0);
6324     }
6325
6326     // verify all items moved to the correct final positions
6327     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6328     for (int i=0; i < model.count() && i < items.count(); ++i) {
6329         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6330         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6331         QCOMPARE(item->x(), 0.0);
6332         QCOMPARE(item->y(), i * 20.0);
6333         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6334         QVERIFY(name != 0);
6335         QTRY_COMPARE(name->text(), model.name(i));
6336     }
6337
6338     releaseView(canvas);
6339 }
6340
6341 void tst_QQuickListView::displacedTransitions_data()
6342 {
6343     QTest::addColumn<bool>("useDisplaced");
6344     QTest::addColumn<bool>("displacedEnabled");
6345     QTest::addColumn<bool>("useAddDisplaced");
6346     QTest::addColumn<bool>("addDisplacedEnabled");
6347     QTest::addColumn<bool>("useMoveDisplaced");
6348     QTest::addColumn<bool>("moveDisplacedEnabled");
6349     QTest::addColumn<bool>("useRemoveDisplaced");
6350     QTest::addColumn<bool>("removeDisplacedEnabled");
6351     QTest::addColumn<ListChange>("change");
6352     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
6353
6354     QTest::newRow("no displaced transitions at all")
6355             << false << false
6356             << false << false
6357             << false << false
6358             << false << false
6359             << ListChange::insert(0, 1) << ListRange(0, 15);
6360
6361     QTest::newRow("just displaced")
6362             << true << true
6363             << false << false
6364             << false << false
6365             << false << false
6366             << ListChange::insert(0, 1) << ListRange(0, 15);
6367
6368     QTest::newRow("just displaced (not enabled)")
6369             << true << false
6370             << false << false
6371             << false << false
6372             << false << false
6373             << ListChange::insert(0, 1) << ListRange(0, 15);
6374
6375     QTest::newRow("displaced + addDisplaced")
6376             << true << true
6377             << true << true
6378             << false << false
6379             << false << false
6380             << ListChange::insert(0, 1) << ListRange(0, 15);
6381
6382     QTest::newRow("displaced + addDisplaced (not enabled)")
6383             << true << true
6384             << true << false
6385             << false << false
6386             << false << false
6387             << ListChange::insert(0, 1) << ListRange(0, 15);
6388
6389     QTest::newRow("displaced + moveDisplaced")
6390             << true << true
6391             << false << false
6392             << true << true
6393             << false << false
6394             << ListChange::move(0, 10, 1) << ListRange(1, 10);
6395
6396     QTest::newRow("displaced + moveDisplaced (not enabled)")
6397             << true << true
6398             << false << false
6399             << true << false
6400             << false << false
6401             << ListChange::move(0, 10, 1) << ListRange(1, 10);
6402
6403     QTest::newRow("displaced + removeDisplaced")
6404             << true << true
6405             << false << false
6406             << false << false
6407             << true << true
6408             << ListChange::remove(0, 1) << ListRange(1, 16);
6409
6410     QTest::newRow("displaced + removeDisplaced (not enabled)")
6411             << true << true
6412             << false << false
6413             << false << false
6414             << true << false
6415             << ListChange::remove(0, 1) << ListRange(1, 16);
6416
6417
6418     QTest::newRow("displaced + add, should use generic displaced for a remove")
6419             << true << true
6420             << true << true
6421             << false << false
6422             << true << false
6423             << ListChange::remove(0, 1) << ListRange(1, 16);
6424 }
6425
6426 void tst_QQuickListView::multipleTransitions()
6427 {
6428     // Tests that if you interrupt a transition in progress with another action that
6429     // cancels the previous transition, the resulting items are still placed correctly.
6430
6431     QFETCH(int, initialCount);
6432     QFETCH(qreal, contentY);
6433     QFETCH(QList<ListChange>, changes);
6434     QFETCH(bool, enableAddTransitions);
6435     QFETCH(bool, enableMoveTransitions);
6436     QFETCH(bool, enableRemoveTransitions);
6437     QFETCH(bool, rippleAddDisplaced);
6438
6439     QPointF addTargets_transitionFrom(-50, -50);
6440     QPointF addDisplaced_transitionFrom(-50, 50);
6441     QPointF moveTargets_transitionFrom(50, -50);
6442     QPointF moveDisplaced_transitionFrom(50, 50);
6443     QPointF removeTargets_transitionTo(-100, 300);
6444     QPointF removeDisplaced_transitionFrom(100, 300);
6445
6446     QmlListModel model;
6447     for (int i = 0; i < initialCount; i++)
6448         model.addItem("Original item" + QString::number(i), "");
6449
6450     QQuickView *canvas = getView();
6451     QQmlContext *ctxt = canvas->rootContext();
6452     TestObject *testObject = new TestObject;
6453     ctxt->setContextProperty("testModel", &model);
6454     ctxt->setContextProperty("testObject", testObject);
6455     ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom);
6456     ctxt->setContextProperty("addDisplaced_transitionFrom", addDisplaced_transitionFrom);
6457     ctxt->setContextProperty("moveTargets_transitionFrom", moveTargets_transitionFrom);
6458     ctxt->setContextProperty("moveDisplaced_transitionFrom", moveDisplaced_transitionFrom);
6459     ctxt->setContextProperty("removeTargets_transitionTo", removeTargets_transitionTo);
6460     ctxt->setContextProperty("removeDisplaced_transitionFrom", removeDisplaced_transitionFrom);
6461     ctxt->setContextProperty("enableAddTransitions", enableAddTransitions);
6462     ctxt->setContextProperty("enableMoveTransitions", enableMoveTransitions);
6463     ctxt->setContextProperty("enableRemoveTransitions", enableRemoveTransitions);
6464     ctxt->setContextProperty("rippleAddDisplaced", rippleAddDisplaced);
6465     canvas->setSource(testFileUrl("multipleTransitions.qml"));
6466     canvas->show();
6467     QTest::qWaitForWindowShown(canvas);
6468
6469     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
6470     QTRY_VERIFY(listview != 0);
6471     QQuickItem *contentItem = listview->contentItem();
6472     QVERIFY(contentItem != 0);
6473     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6474
6475     if (contentY != 0) {
6476         listview->setContentY(contentY);
6477         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6478     }
6479
6480     int timeBetweenActions = canvas->rootObject()->property("timeBetweenActions").toInt();
6481
6482     for (int i=0; i<changes.count(); i++) {
6483         switch (changes[i].type) {
6484             case ListChange::Inserted:
6485             {
6486                 QList<QPair<QString, QString> > targetItems;
6487                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
6488                     targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
6489                 model.insertItems(changes[i].index, targetItems);
6490                 QTRY_COMPARE(model.count(), listview->count());
6491                 if (i == changes.count() - 1) {
6492                     QTRY_VERIFY(!listview->property("runningAddTargets").toBool());
6493                     QTRY_VERIFY(!listview->property("runningAddDisplaced").toBool());
6494                 } else {
6495                     QTest::qWait(timeBetweenActions);
6496                 }
6497                 break;
6498             }
6499             case ListChange::Removed:
6500                 model.removeItems(changes[i].index, changes[i].count);
6501                 QTRY_COMPARE(model.count(), listview->count());
6502                 if (i == changes.count() - 1) {
6503                     QTRY_VERIFY(!listview->property("runningRemoveTargets").toBool());
6504                     QTRY_VERIFY(!listview->property("runningRemoveDisplaced").toBool());
6505                 } else {
6506                     QTest::qWait(timeBetweenActions);
6507                 }
6508                 break;
6509             case ListChange::Moved:
6510                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
6511                 QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6512                 if (i == changes.count() - 1) {
6513                     QTRY_VERIFY(!listview->property("runningMoveTargets").toBool());
6514                     QTRY_VERIFY(!listview->property("runningMoveDisplaced").toBool());
6515                 } else {
6516                     QTest::qWait(timeBetweenActions);
6517                 }
6518                 break;
6519             case ListChange::SetCurrent:
6520                 listview->setCurrentIndex(changes[i].index);
6521                 break;
6522             case ListChange::SetContentY:
6523                 listview->setContentY(changes[i].pos);
6524                 QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6525                 break;
6526             case ListChange::Polish:
6527                 break;
6528         }
6529     }
6530     QCOMPARE(listview->count(), model.count());
6531
6532     // verify all items moved to the correct final positions
6533     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6534     for (int i=0; i < model.count() && i < items.count(); ++i) {
6535         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6536         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6537         QTRY_COMPARE(item->x(), 0.0);
6538         QTRY_COMPARE(item->y(), i*20.0);
6539         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6540         QVERIFY(name != 0);
6541         QTRY_COMPARE(name->text(), model.name(i));
6542     }
6543
6544     releaseView(canvas);
6545     delete testObject;
6546 }
6547
6548 void tst_QQuickListView::multipleTransitions_data()
6549 {
6550     QTest::addColumn<int>("initialCount");
6551     QTest::addColumn<qreal>("contentY");
6552     QTest::addColumn<QList<ListChange> >("changes");
6553     QTest::addColumn<bool>("enableAddTransitions");
6554     QTest::addColumn<bool>("enableMoveTransitions");
6555     QTest::addColumn<bool>("enableRemoveTransitions");
6556     QTest::addColumn<bool>("rippleAddDisplaced");
6557
6558     // the added item and displaced items should move to final dest correctly
6559     QTest::newRow("add item, then move it immediately") << 10 << 0.0 << (QList<ListChange>()
6560             << ListChange::insert(0, 1)
6561             << ListChange::move(0, 3, 1)
6562             )
6563             << true << true << true << false;
6564
6565     // items affected by the add should change from move to add transition
6566     QTest::newRow("move, then insert item before the moved item") << 20 << 0.0 << (QList<ListChange>()
6567             << ListChange::move(1, 10, 3)
6568             << ListChange::insert(0, 1)
6569             )
6570             << true << true << true << false;
6571
6572     // items should be placed correctly if you trigger a transition then refill for that index
6573     QTest::newRow("add at 0, flick down, flick back to top and add at 0 again") << 20 << 0.0 << (QList<ListChange>()
6574             << ListChange::insert(0, 1)
6575             << ListChange::setContentY(80.0)
6576             << ListChange::setContentY(0.0)
6577             << ListChange::insert(0, 1)
6578             )
6579             << true << true << true << false;
6580
6581     QTest::newRow("insert then remove same index, with ripple effect on add displaced") << 20 << 0.0 << (QList<ListChange>()
6582             << ListChange::insert(1, 1)
6583             << ListChange::remove(1, 1)
6584             )
6585             << true << true << true << true;
6586
6587     // if item is removed while undergoing a displaced transition, all other items should end up at their correct positions,
6588     // even if a remove-displace transition is not present to re-animate them
6589     QTest::newRow("insert then remove, with remove disabled") << 20 << 0.0 << (QList<ListChange>()
6590             << ListChange::insert(0, 1)
6591             << ListChange::remove(2, 1)
6592             )
6593             << true << true << false << false;
6594
6595     // if last item is not flush with the edge of the view, it should still be refilled in correctly after a
6596     // remove has changed the position of where it will move to
6597     QTest::newRow("insert twice then remove, with remove disabled") << 20 << 0.0 << (QList<ListChange>()
6598             << ListChange::setContentY(-10.0)
6599             << ListChange::insert(0, 1)
6600             << ListChange::insert(0, 1)
6601             << ListChange::remove(2, 1)
6602             )
6603             << true << true << false << false;
6604 }
6605
6606 void tst_QQuickListView::multipleDisplaced()
6607 {
6608     // multiple move() operations should only restart displace transitions for items that
6609     // moved from previously set positions, and not those that have moved from their current
6610     // item positions (which may e.g. still be changing from easing bounces in the last transition)
6611
6612     QmlListModel model;
6613     for (int i = 0; i < 30; i++)
6614         model.addItem("Original item" + QString::number(i), "");
6615
6616     QQuickView *canvas = getView();
6617     QQmlContext *ctxt = canvas->rootContext();
6618     ctxt->setContextProperty("testModel", &model);
6619     ctxt->setContextProperty("testObject", new TestObject(canvas));
6620     canvas->setSource(testFileUrl("multipleDisplaced.qml"));
6621     canvas->show();
6622     QTest::qWaitForWindowShown(canvas);
6623
6624     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
6625     QTRY_VERIFY(listview != 0);
6626     QQuickItem *contentItem = listview->contentItem();
6627     QVERIFY(contentItem != 0);
6628     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6629
6630     model.moveItems(12, 8, 1);
6631     QTest::qWait(canvas->rootObject()->property("duration").toInt() / 2);
6632     model.moveItems(8, 3, 1);
6633     QTRY_VERIFY(listview->property("displaceTransitionsDone").toBool());
6634
6635     QVariantMap transitionsStarted = listview->property("displaceTransitionsStarted").toMap();
6636     foreach (const QString &name, transitionsStarted.keys()) {
6637         QVERIFY2(transitionsStarted[name] == 1,
6638                  QTest::toString(QString("%1 was displaced %2 times").arg(name).arg(transitionsStarted[name].toInt())));
6639     }
6640
6641     // verify all items moved to the correct final positions
6642     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6643     for (int i=0; i < model.count() && i < items.count(); ++i) {
6644         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6645         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6646         QTRY_COMPARE(item->x(), 0.0);
6647         QTRY_COMPARE(item->y(), i*20.0);
6648         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6649         QVERIFY(name != 0);
6650         QTRY_COMPARE(name->text(), model.name(i));
6651     }
6652
6653     releaseView(canvas);
6654 }
6655
6656 QList<int> tst_QQuickListView::toIntList(const QVariantList &list)
6657 {
6658     QList<int> ret;
6659     bool ok = true;
6660     for (int i=0; i<list.count(); i++) {
6661         ret << list[i].toInt(&ok);
6662         if (!ok)
6663             qWarning() << "tst_QQuickListView::toIntList(): not a number:" << list[i];
6664     }
6665
6666     return ret;
6667 }
6668
6669 void tst_QQuickListView::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
6670 {
6671     for (int i=0; i<indexLists.count(); i++) {
6672         QSet<int> current = indexLists[i].value<QList<int> >().toSet();
6673         if (current != expectedIndexes.toSet())
6674             qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
6675         QCOMPARE(current, expectedIndexes.toSet());
6676     }
6677 }
6678
6679 void tst_QQuickListView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
6680 {
6681     for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
6682         QVERIFY(it.value().type() == QVariant::Int);
6683         QString name = it.key();
6684         int itemIndex = it.value().toInt();
6685         QVERIFY2(expectedIndexes.contains(itemIndex), QTest::toString(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
6686         if (model.name(itemIndex) != name)
6687             qDebug() << itemIndex;
6688         QCOMPARE(model.name(itemIndex), name);
6689     }
6690     QCOMPARE(items.count(), expectedIndexes.count());
6691 }
6692
6693 void tst_QQuickListView::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
6694 {
6695     for (int i=0; i<itemLists.count(); i++) {
6696         QVERIFY(itemLists[i].type() == QVariant::List);
6697         QVariantList current = itemLists[i].toList();
6698         for (int j=0; j<current.count(); j++) {
6699             QQuickItem *o = qobject_cast<QQuickItem*>(current[j].value<QObject*>());
6700             QVERIFY2(o, QTest::toString(QString("Invalid actual item at %1").arg(j)));
6701             QVERIFY2(expectedItems.contains(o), QTest::toString(QString("Cannot match item %1").arg(j)));
6702         }
6703         QCOMPARE(current.count(), expectedItems.count());
6704     }
6705 }
6706
6707 void tst_QQuickListView::flickBeyondBounds()
6708 {
6709     QQuickView *canvas = createView();
6710
6711     canvas->setSource(testFileUrl("flickBeyondBoundsBug.qml"));
6712     canvas->show();
6713     qApp->processEvents();
6714
6715     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
6716     QTRY_VERIFY(listview != 0);
6717
6718     QQuickItem *contentItem = listview->contentItem();
6719     QTRY_VERIFY(contentItem != 0);
6720     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6721
6722     // Flick view up beyond bounds
6723     flick(canvas, QPoint(10, 10), QPoint(10, -1000), 180);
6724     QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() == 0);
6725
6726     // We're really testing that we don't get stuck in a loop,
6727     // but also confirm items positioned correctly.
6728     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 2);
6729     for (int i = 0; i < 2; ++i) {
6730         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6731         if (!item) qWarning() << "Item" << i << "not found";
6732         QTRY_VERIFY(item);
6733         QTRY_VERIFY(item->y() == i*45);
6734     }
6735
6736     delete canvas;
6737 }
6738
6739 void tst_QQuickListView::destroyItemOnCreation()
6740 {
6741     QmlListModel model;
6742     QQuickView *canvas = createView();
6743     canvas->rootContext()->setContextProperty("testModel", &model);
6744
6745     canvas->setSource(testFileUrl("destroyItemOnCreation.qml"));
6746     canvas->show();
6747     qApp->processEvents();
6748
6749     QQuickListView *listview = findItem<QQuickListView>(canvas->rootObject(), "list");
6750     QVERIFY(listview != 0);
6751
6752     QQuickItem *contentItem = listview->contentItem();
6753     QVERIFY(contentItem != 0);
6754     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6755
6756     QCOMPARE(canvas->rootObject()->property("createdIndex").toInt(), -1);
6757     model.addItem("new item", "");
6758     QTRY_COMPARE(canvas->rootObject()->property("createdIndex").toInt(), 0);
6759
6760     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
6761     QCOMPARE(model.count(), 0);
6762
6763     delete canvas;
6764 }
6765
6766 void tst_QQuickListView::parentBinding()
6767 {
6768     QQuickView *canvas = createView();
6769
6770     m_errorCount = 0;
6771     QtMsgHandler old = qInstallMsgHandler(errorMsgHandler);
6772
6773     canvas->setSource(testFileUrl("parentBinding.qml"));
6774     canvas->show();
6775     qApp->processEvents();
6776
6777     QQuickListView *listview = qobject_cast<QQuickListView*>(canvas->rootObject());
6778     QVERIFY(listview != 0);
6779
6780     QQuickItem *contentItem = listview->contentItem();
6781     QVERIFY(contentItem != 0);
6782     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6783
6784     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
6785     QVERIFY(item);
6786     QCOMPARE(item->width(), listview->width());
6787     QCOMPARE(item->height(), listview->height()/12);
6788
6789     // there should be no transient binding error
6790     QVERIFY(!m_errorCount);
6791
6792     qInstallMsgHandler(old);
6793
6794     delete canvas;
6795 }
6796
6797 QTEST_MAIN(tst_QQuickListView)
6798
6799 #include "tst_qquicklistview.moc"
6800
6801