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