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