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