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