tst_QQuickListView::sectionsDelegate_headerVisibility(): restore QSKIP
[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     QSKIP("QTBUG-24395");
2172
2173     QQuickView *window = createView();
2174
2175     QaimModel model;
2176     for (int i = 0; i < 30; i++)
2177         model.addItem("Item" + QString::number(i), QString::number(i/5));
2178
2179     window->rootContext()->setContextProperty("testModel", &model);
2180     window->setSource(testFileUrl("listview-sections_delegate.qml"));
2181     window->show();
2182     window->requestActivateWindow();
2183     QTest::qWaitForWindowActive(window);
2184
2185     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2186     QTRY_VERIFY(listview != 0);
2187
2188     QQuickItem *contentItem = listview->contentItem();
2189     QTRY_VERIFY(contentItem != 0);
2190     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2191
2192     // ensure section header is maintained in view
2193     listview->setCurrentIndex(20);
2194     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2195     QTRY_VERIFY(qFuzzyCompare(listview->contentY(), 200.0));
2196     QTRY_VERIFY(listview->isMoving() == false);
2197     listview->setCurrentIndex(0);
2198     QTRY_VERIFY(qFuzzyIsNull(listview->contentY()));
2199
2200     delete window;
2201 }
2202
2203 void tst_QQuickListView::sectionsPositioning()
2204 {
2205     QQuickView *window = createView();
2206
2207     QaimModel model;
2208     for (int i = 0; i < 30; i++)
2209         model.addItem("Item" + QString::number(i), QString::number(i/5));
2210
2211     QQmlContext *ctxt = window->rootContext();
2212     ctxt->setContextProperty("testModel", &model);
2213
2214     window->setSource(testFileUrl("listview-sections_delegate.qml"));
2215     window->show();
2216     qApp->processEvents();
2217     window->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart | QQuickViewSection::NextLabelAtEnd)));
2218
2219     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2220     QTRY_VERIFY(listview != 0);
2221     QQuickItem *contentItem = listview->contentItem();
2222     QTRY_VERIFY(contentItem != 0);
2223     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2224
2225     for (int i = 0; i < 3; ++i) {
2226         QQuickItem *item = findItem<QQuickItem>(contentItem, "sect_" + QString::number(i));
2227         QVERIFY(item);
2228         QTRY_COMPARE(item->y(), qreal(i*20*6));
2229     }
2230
2231     QQuickItem *topItem = findVisibleChild(contentItem, "sect_0"); // section header
2232     QVERIFY(topItem);
2233     QCOMPARE(topItem->y(), 0.);
2234
2235     QQuickItem *bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
2236     QVERIFY(bottomItem);
2237     QCOMPARE(bottomItem->y(), 300.);
2238
2239     // move down a little and check that section header is at top
2240     listview->setContentY(10);
2241     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2242     QCOMPARE(topItem->y(), 0.);
2243
2244     // push the top header up
2245     listview->setContentY(110);
2246     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2247     topItem = findVisibleChild(contentItem, "sect_0"); // section header
2248     QVERIFY(topItem);
2249     QCOMPARE(topItem->y(), 100.);
2250
2251     QQuickItem *item = findVisibleChild(contentItem, "sect_1");
2252     QVERIFY(item);
2253     QCOMPARE(item->y(), 120.);
2254
2255     bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
2256     QVERIFY(bottomItem);
2257     QCOMPARE(bottomItem->y(), 410.);
2258
2259     // Move past section 0
2260     listview->setContentY(120);
2261     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2262     topItem = findVisibleChild(contentItem, "sect_0"); // section header
2263     QVERIFY(!topItem);
2264
2265     // Push section footer down
2266     listview->setContentY(70);
2267     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2268     bottomItem = findVisibleChild(contentItem, "sect_4"); // section footer
2269     QVERIFY(bottomItem);
2270     QCOMPARE(bottomItem->y(), 380.);
2271
2272     // Change current section, and verify case insensitive comparison
2273     listview->setContentY(10);
2274     model.modifyItem(0, "One", "aaa");
2275     model.modifyItem(1, "Two", "AAA");
2276     model.modifyItem(2, "Three", "aAa");
2277     model.modifyItem(3, "Four", "aaA");
2278     model.modifyItem(4, "Five", "Aaa");
2279     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2280
2281     QTRY_COMPARE(listview->currentSection(), QString("aaa"));
2282
2283     for (int i = 0; i < 3; ++i) {
2284         QQuickItem *item = findItem<QQuickItem>(contentItem,
2285                 "sect_" + (i == 0 ? QString("aaa") : QString::number(i)));
2286         QVERIFY(item);
2287         QTRY_COMPARE(item->y(), qreal(i*20*6));
2288     }
2289
2290     QTRY_VERIFY(topItem = findVisibleChild(contentItem, "sect_aaa")); // section header
2291     QCOMPARE(topItem->y(), 10.);
2292
2293     // remove section boundary
2294     listview->setContentY(120);
2295     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2296     model.removeItem(5);
2297     QTRY_COMPARE(listview->count(), model.count());
2298     for (int i = 1; i < 3; ++i) {
2299         QQuickItem *item = findVisibleChild(contentItem,
2300                 "sect_" + QString::number(i));
2301         QVERIFY(item);
2302         QTRY_COMPARE(item->y(), qreal(i*20*6));
2303     }
2304
2305     QVERIFY(topItem = findVisibleChild(contentItem, "sect_1"));
2306     QTRY_COMPARE(topItem->y(), 120.);
2307
2308     // Change the next section
2309     listview->setContentY(0);
2310     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2311     bottomItem = findVisibleChild(contentItem, "sect_3"); // section footer
2312     QVERIFY(bottomItem);
2313     QTRY_COMPARE(bottomItem->y(), 300.);
2314
2315     model.modifyItem(14, "New", "new");
2316     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2317
2318     QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_new")); // section footer
2319     QTRY_COMPARE(bottomItem->y(), 300.);
2320
2321     // delegate size increase should push section footer down
2322     listview->setContentY(70);
2323     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2324     QTRY_VERIFY(bottomItem = findVisibleChild(contentItem, "sect_3")); // section footer
2325     QTRY_COMPARE(bottomItem->y(), 370.);
2326     QQuickItem *inlineSection = findVisibleChild(contentItem, "sect_new");
2327     item = findItem<QQuickItem>(contentItem, "wrapper", 13);
2328     QVERIFY(item);
2329     item->setHeight(40.);
2330     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2331     QTRY_COMPARE(bottomItem->y(), 380.);
2332     QCOMPARE(inlineSection->y(), 360.);
2333     item->setHeight(20.);
2334
2335     // Turn sticky footer off
2336     listview->setContentY(20);
2337     window->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart)));
2338     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2339     QTRY_VERIFY(item = findVisibleChild(contentItem, "sect_new")); // inline label restored
2340     QCOMPARE(item->y(), 340.);
2341
2342     // Turn sticky header off
2343     listview->setContentY(30);
2344     window->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels)));
2345     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2346     QTRY_VERIFY(item = findVisibleChild(contentItem, "sect_aaa")); // inline label restored
2347     QCOMPARE(item->y(), 0.);
2348
2349     // if an empty model is set the header/footer should be cleaned up
2350     window->rootObject()->setProperty("sectionPositioning", QVariant(int(QQuickViewSection::InlineLabels | QQuickViewSection::CurrentLabelAtStart | QQuickViewSection::NextLabelAtEnd)));
2351     QTRY_VERIFY(findVisibleChild(contentItem, "sect_aaa")); // section header
2352     QTRY_VERIFY(findVisibleChild(contentItem, "sect_new")); // section footer
2353     QaimModel model1;
2354     ctxt->setContextProperty("testModel", &model1);
2355     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_aaa")); // section header
2356     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_new")); // section footer
2357
2358     // clear model - header/footer should be cleaned up
2359     ctxt->setContextProperty("testModel", &model);
2360     QTRY_VERIFY(findVisibleChild(contentItem, "sect_aaa")); // section header
2361     QTRY_VERIFY(findVisibleChild(contentItem, "sect_new")); // section footer
2362     model.clear();
2363     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_aaa")); // section header
2364     QTRY_VERIFY(!findVisibleChild(contentItem, "sect_new")); // section footer
2365
2366     delete window;
2367 }
2368
2369 void tst_QQuickListView::sectionPropertyChange()
2370 {
2371     QQuickView *window = createView();
2372
2373     window->setSource(testFileUrl("sectionpropertychange.qml"));
2374     window->show();
2375     qApp->processEvents();
2376
2377     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2378     QTRY_VERIFY(listview != 0);
2379
2380     QQuickItem *contentItem = listview->contentItem();
2381     QTRY_VERIFY(contentItem != 0);
2382
2383     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2384
2385     // Confirm items positioned correctly
2386     for (int i = 0; i < 2; ++i) {
2387         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2388         QTRY_VERIFY(item);
2389         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2390     }
2391
2392     QMetaObject::invokeMethod(window->rootObject(), "switchGroups");
2393     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2394
2395     // Confirm items positioned correctly
2396     for (int i = 0; i < 2; ++i) {
2397         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2398         QTRY_VERIFY(item);
2399         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2400     }
2401
2402     QMetaObject::invokeMethod(window->rootObject(), "switchGroups");
2403     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2404
2405     // Confirm items positioned correctly
2406     for (int i = 0; i < 2; ++i) {
2407         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2408         QTRY_VERIFY(item);
2409         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2410     }
2411
2412     QMetaObject::invokeMethod(window->rootObject(), "switchGrouped");
2413     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2414
2415     // Confirm items positioned correctly
2416     for (int i = 0; i < 2; ++i) {
2417         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2418         QTRY_VERIFY(item);
2419         QTRY_COMPARE(item->y(), qreal(25. + i*50.));
2420     }
2421
2422     QMetaObject::invokeMethod(window->rootObject(), "switchGrouped");
2423     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2424
2425     // Confirm items positioned correctly
2426     for (int i = 0; i < 2; ++i) {
2427         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2428         QTRY_VERIFY(item);
2429         QTRY_COMPARE(item->y(), qreal(25. + i*75.));
2430     }
2431
2432     delete window;
2433 }
2434
2435 void tst_QQuickListView::sectionDelegateChange()
2436 {
2437     QQuickView *window = createView();
2438
2439     window->setSource(testFileUrl("sectiondelegatechange.qml"));
2440     window->show();
2441     qApp->processEvents();
2442
2443     QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject());
2444     QVERIFY(listview != 0);
2445
2446     QQuickItem *contentItem = listview->contentItem();
2447     QVERIFY(contentItem != 0);
2448
2449     QQUICK_VERIFY_POLISH(listview);
2450
2451     QVERIFY(findItems<QQuickItem>(contentItem, "section1").count() > 0);
2452     QCOMPARE(findItems<QQuickItem>(contentItem, "section2").count(), 0);
2453
2454     for (int i = 0; i < 3; ++i) {
2455         QQuickItem *item = findItem<QQuickItem>(contentItem, "item", i);
2456         QTRY_VERIFY(item);
2457         QTRY_COMPARE(item->y(), qreal(25. + i*50.));
2458     }
2459
2460     QMetaObject::invokeMethod(window->rootObject(), "switchDelegates");
2461     QQUICK_VERIFY_POLISH(listview);
2462
2463     QCOMPARE(findItems<QQuickItem>(contentItem, "section1").count(), 0);
2464     QVERIFY(findItems<QQuickItem>(contentItem, "section2").count() > 0);
2465
2466     for (int i = 0; i < 3; ++i) {
2467         QQuickItem *item = findItem<QQuickItem>(contentItem, "item", i);
2468         QVERIFY(item);
2469         QTRY_COMPARE(item->y(), qreal(50. + i*75.));
2470     }
2471
2472     delete window;
2473 }
2474
2475 void tst_QQuickListView::currentIndex_delayedItemCreation()
2476 {
2477     QFETCH(bool, setCurrentToZero);
2478
2479     QQuickView *window = getView();
2480
2481     // test currentIndexChanged() is emitted even if currentIndex = 0 on start up
2482     // (since the currentItem will have changed and that shares the same index)
2483     window->rootContext()->setContextProperty("setCurrentToZero", setCurrentToZero);
2484
2485     window->setSource(testFileUrl("fillModelOnComponentCompleted.qml"));
2486     qApp->processEvents();
2487
2488     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2489     QTRY_VERIFY(listview != 0);
2490     QQuickItem *contentItem = listview->contentItem();
2491     QTRY_VERIFY(contentItem != 0);
2492
2493     QSignalSpy spy(listview, SIGNAL(currentItemChanged()));
2494     QCOMPARE(listview->currentIndex(), 0);
2495     QTRY_COMPARE(spy.count(), 1);
2496
2497     releaseView(window);
2498 }
2499
2500 void tst_QQuickListView::currentIndex_delayedItemCreation_data()
2501 {
2502     QTest::addColumn<bool>("setCurrentToZero");
2503
2504     QTest::newRow("set to 0") << true;
2505     QTest::newRow("don't set to 0") << false;
2506 }
2507
2508 void tst_QQuickListView::currentIndex()
2509 {
2510     QaimModel initModel;
2511
2512     for (int i = 0; i < 30; i++)
2513         initModel.addItem("Item" + QString::number(i), QString::number(i));
2514
2515     QQuickView *window = new QQuickView(0);
2516     window->setGeometry(0,0,240,320);
2517
2518     QQmlContext *ctxt = window->rootContext();
2519     ctxt->setContextProperty("testModel", &initModel);
2520     ctxt->setContextProperty("testWrap", QVariant(false));
2521
2522     QString filename(testFile("listview-initCurrent.qml"));
2523     window->setSource(QUrl::fromLocalFile(filename));
2524     window->show();
2525     qApp->processEvents();
2526
2527     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2528     QTRY_VERIFY(listview != 0);
2529     QQuickItem *contentItem = listview->contentItem();
2530     QTRY_VERIFY(contentItem != 0);
2531     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2532
2533     // currentIndex is initialized to 20
2534     // currentItem should be in view
2535     QCOMPARE(listview->currentIndex(), 20);
2536     QCOMPARE(listview->contentY(), 100.0);
2537     QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 20));
2538     QCOMPARE(listview->highlightItem()->y(), listview->currentItem()->y());
2539
2540     // changing model should reset currentIndex to 0
2541     QaimModel model;
2542     for (int i = 0; i < 30; i++)
2543         model.addItem("Item" + QString::number(i), QString::number(i));
2544     ctxt->setContextProperty("testModel", &model);
2545
2546     QCOMPARE(listview->currentIndex(), 0);
2547     QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
2548
2549     // confirm that the velocity is updated
2550     listview->setCurrentIndex(20);
2551     QTRY_VERIFY(listview->verticalVelocity() != 0.0);
2552     listview->setCurrentIndex(0);
2553     QTRY_VERIFY(listview->verticalVelocity() == 0.0);
2554
2555     // footer should become visible if it is out of view, and then current index is set to count-1
2556     window->rootObject()->setProperty("showFooter", true);
2557     QTRY_VERIFY(listview->footerItem());
2558     listview->setCurrentIndex(model.count()-2);
2559     QTRY_VERIFY(listview->footerItem()->y() > listview->contentY() + listview->height());
2560     listview->setCurrentIndex(model.count()-1);
2561     QTRY_COMPARE(listview->contentY() + listview->height(), (20.0 * model.count()) + listview->footerItem()->height());
2562     window->rootObject()->setProperty("showFooter", false);
2563
2564     // header should become visible if it is out of view, and then current index is set to 0
2565     window->rootObject()->setProperty("showHeader", true);
2566     QTRY_VERIFY(listview->headerItem());
2567     listview->setCurrentIndex(1);
2568     QTRY_VERIFY(listview->headerItem()->y() + listview->headerItem()->height() < listview->contentY());
2569     listview->setCurrentIndex(0);
2570     QTRY_COMPARE(listview->contentY(), -listview->headerItem()->height());
2571     window->rootObject()->setProperty("showHeader", false);
2572
2573     // turn off auto highlight
2574     listview->setHighlightFollowsCurrentItem(false);
2575     QVERIFY(listview->highlightFollowsCurrentItem() == false);
2576
2577     QVERIFY(listview->highlightItem());
2578     qreal hlPos = listview->highlightItem()->y();
2579
2580     listview->setCurrentIndex(4);
2581     QTRY_COMPARE(listview->highlightItem()->y(), hlPos);
2582
2583     // insert item before currentIndex
2584     listview->setCurrentIndex(28);
2585     model.insertItem(0, "Foo", "1111");
2586     QTRY_COMPARE(window->rootObject()->property("current").toInt(), 29);
2587
2588     // check removing highlight by setting currentIndex to -1;
2589     listview->setCurrentIndex(-1);
2590
2591     QCOMPARE(listview->currentIndex(), -1);
2592     QVERIFY(!listview->highlightItem());
2593     QVERIFY(!listview->currentItem());
2594
2595     // moving currentItem out of view should make it invisible
2596     listview->setCurrentIndex(0);
2597     QTRY_VERIFY(delegateVisible(listview->currentItem()));
2598     listview->setContentY(200);
2599     QTRY_VERIFY(!delegateVisible(listview->currentItem()));
2600
2601     delete window;
2602 }
2603
2604 void tst_QQuickListView::noCurrentIndex()
2605 {
2606     QaimModel model;
2607     for (int i = 0; i < 30; i++)
2608         model.addItem("Item" + QString::number(i), QString::number(i));
2609
2610     QQuickView *window = new QQuickView(0);
2611     window->setGeometry(0,0,240,320);
2612
2613     QQmlContext *ctxt = window->rootContext();
2614     ctxt->setContextProperty("testModel", &model);
2615
2616     QString filename(testFile("listview-noCurrent.qml"));
2617     window->setSource(QUrl::fromLocalFile(filename));
2618     window->show();
2619     qApp->processEvents();
2620
2621     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2622     QTRY_VERIFY(listview != 0);
2623     QQuickItem *contentItem = listview->contentItem();
2624     QTRY_VERIFY(contentItem != 0);
2625     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2626
2627     // current index should be -1 at startup
2628     // and we should not have a currentItem or highlightItem
2629     QCOMPARE(listview->currentIndex(), -1);
2630     QCOMPARE(listview->contentY(), 0.0);
2631     QVERIFY(!listview->highlightItem());
2632     QVERIFY(!listview->currentItem());
2633
2634     listview->setCurrentIndex(2);
2635     QCOMPARE(listview->currentIndex(), 2);
2636     QVERIFY(listview->highlightItem());
2637     QVERIFY(listview->currentItem());
2638
2639     delete window;
2640 }
2641
2642 void tst_QQuickListView::keyNavigation()
2643 {
2644     QFETCH(QQuickListView::Orientation, orientation);
2645     QFETCH(Qt::LayoutDirection, layoutDirection);
2646     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
2647     QFETCH(Qt::Key, forwardsKey);
2648     QFETCH(Qt::Key, backwardsKey);
2649     QFETCH(QPointF, contentPosAtFirstItem);
2650     QFETCH(QPointF, contentPosAtLastItem);
2651
2652     QaimModel model;
2653     for (int i = 0; i < 30; i++)
2654         model.addItem("Item" + QString::number(i), "");
2655
2656     QQuickView *window = getView();
2657     TestObject *testObject = new TestObject;
2658     window->rootContext()->setContextProperty("testModel", &model);
2659     window->rootContext()->setContextProperty("testObject", testObject);
2660     window->setSource(testFileUrl("listviewtest.qml"));
2661     window->show();
2662     QTest::qWaitForWindowActive(window);
2663
2664     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2665     QTRY_VERIFY(listview != 0);
2666
2667     listview->setOrientation(orientation);
2668     listview->setLayoutDirection(layoutDirection);
2669     listview->setVerticalLayoutDirection(verticalLayoutDirection);
2670     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2671
2672     window->requestActivateWindow();
2673     QTest::qWaitForWindowActive(window);
2674     QTRY_VERIFY(qGuiApp->focusWindow() == window);
2675
2676     QTest::keyClick(window, forwardsKey);
2677     QCOMPARE(listview->currentIndex(), 1);
2678
2679     QTest::keyClick(window, backwardsKey);
2680     QCOMPARE(listview->currentIndex(), 0);
2681
2682     // hold down a key to go forwards
2683     for (int i=0; i<model.count()-1; i++) {
2684         QTest::simulateEvent(window, true, forwardsKey, Qt::NoModifier, "", true);
2685         QTRY_COMPARE(listview->currentIndex(), i+1);
2686     }
2687     QTest::keyRelease(window, forwardsKey);
2688     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
2689     QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
2690     QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
2691
2692     // hold down a key to go backwards
2693     for (int i=model.count()-1; i > 0; i--) {
2694         QTest::simulateEvent(window, true, backwardsKey, Qt::NoModifier, "", true);
2695         QTRY_COMPARE(listview->currentIndex(), i-1);
2696     }
2697     QTest::keyRelease(window, backwardsKey);
2698     QTRY_COMPARE(listview->currentIndex(), 0);
2699     QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
2700     QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
2701
2702     // no wrap
2703     QVERIFY(!listview->isWrapEnabled());
2704     listview->incrementCurrentIndex();
2705     QCOMPARE(listview->currentIndex(), 1);
2706     listview->decrementCurrentIndex();
2707     QCOMPARE(listview->currentIndex(), 0);
2708
2709     listview->decrementCurrentIndex();
2710     QCOMPARE(listview->currentIndex(), 0);
2711
2712     // with wrap
2713     listview->setWrapEnabled(true);
2714     QVERIFY(listview->isWrapEnabled());
2715
2716     listview->decrementCurrentIndex();
2717     QCOMPARE(listview->currentIndex(), model.count()-1);
2718     QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
2719     QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
2720
2721     listview->incrementCurrentIndex();
2722     QCOMPARE(listview->currentIndex(), 0);
2723     QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
2724     QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
2725
2726     releaseView(window);
2727     delete testObject;
2728 }
2729
2730 void tst_QQuickListView::keyNavigation_data()
2731 {
2732     QTest::addColumn<QQuickListView::Orientation>("orientation");
2733     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
2734     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
2735     QTest::addColumn<Qt::Key>("forwardsKey");
2736     QTest::addColumn<Qt::Key>("backwardsKey");
2737     QTest::addColumn<QPointF>("contentPosAtFirstItem");
2738     QTest::addColumn<QPointF>("contentPosAtLastItem");
2739
2740     QTest::newRow("Vertical, TopToBottom")
2741             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
2742             << Qt::Key_Down << Qt::Key_Up
2743             << QPointF(0, 0)
2744             << QPointF(0, 30*20 - 320);
2745
2746     QTest::newRow("Vertical, BottomToTop")
2747             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
2748             << Qt::Key_Up << Qt::Key_Down
2749             << QPointF(0, -320)
2750             << QPointF(0, -(30 * 20));
2751
2752     QTest::newRow("Horizontal, LeftToRight")
2753             << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
2754             << Qt::Key_Right << Qt::Key_Left
2755             << QPointF(0, 0)
2756             << QPointF(30*240 - 240, 0);
2757
2758     QTest::newRow("Horizontal, RightToLeft")
2759             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
2760             << Qt::Key_Left << Qt::Key_Right
2761             << QPointF(-240, 0)
2762             << QPointF(-(30 * 240), 0);
2763 }
2764
2765 void tst_QQuickListView::itemList()
2766 {
2767     QQuickView *window = createView();
2768     window->setSource(testFileUrl("itemlist.qml"));
2769     window->show();
2770     qApp->processEvents();
2771
2772     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "view");
2773     QTRY_VERIFY(listview != 0);
2774
2775     QQuickItem *contentItem = listview->contentItem();
2776     QTRY_VERIFY(contentItem != 0);
2777
2778     QQuickVisualItemModel *model = window->rootObject()->findChild<QQuickVisualItemModel*>("itemModel");
2779     QTRY_VERIFY(model != 0);
2780
2781     QTRY_VERIFY(model->count() == 3);
2782     QTRY_COMPARE(listview->currentIndex(), 0);
2783
2784     QQuickItem *item = findItem<QQuickItem>(contentItem, "item1");
2785     QTRY_VERIFY(item);
2786     QTRY_COMPARE(item->x(), 0.0);
2787     QCOMPARE(item->height(), listview->height());
2788
2789     QQuickText *text = findItem<QQuickText>(contentItem, "text1");
2790     QTRY_VERIFY(text);
2791     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
2792
2793     listview->setCurrentIndex(2);
2794
2795     item = findItem<QQuickItem>(contentItem, "item3");
2796     QTRY_VERIFY(item);
2797     QTRY_COMPARE(item->x(), 480.0);
2798
2799     text = findItem<QQuickText>(contentItem, "text3");
2800     QTRY_VERIFY(text);
2801     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
2802
2803     delete window;
2804 }
2805
2806 void tst_QQuickListView::itemListFlicker()
2807 {
2808     QQuickView *window = createView();
2809     window->setSource(testFileUrl("itemlist-flicker.qml"));
2810     window->show();
2811     qApp->processEvents();
2812
2813     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "view");
2814     QTRY_VERIFY(listview != 0);
2815
2816     QQuickItem *contentItem = listview->contentItem();
2817     QTRY_VERIFY(contentItem != 0);
2818
2819     QQuickVisualItemModel *model = window->rootObject()->findChild<QQuickVisualItemModel*>("itemModel");
2820     QTRY_VERIFY(model != 0);
2821
2822     QTRY_VERIFY(model->count() == 3);
2823     QTRY_COMPARE(listview->currentIndex(), 0);
2824
2825     QQuickItem *item;
2826
2827     QVERIFY(item = findItem<QQuickItem>(contentItem, "item1"));
2828     QVERIFY(delegateVisible(item));
2829     QVERIFY(item = findItem<QQuickItem>(contentItem, "item2"));
2830     QVERIFY(delegateVisible(item));
2831     QVERIFY(item = findItem<QQuickItem>(contentItem, "item3"));
2832     QVERIFY(delegateVisible(item));
2833
2834     listview->setCurrentIndex(1);
2835
2836     QVERIFY(item = findItem<QQuickItem>(contentItem, "item1"));
2837     QVERIFY(delegateVisible(item));
2838     QVERIFY(item = findItem<QQuickItem>(contentItem, "item2"));
2839     QVERIFY(delegateVisible(item));
2840     QVERIFY(item = findItem<QQuickItem>(contentItem, "item3"));
2841     QVERIFY(delegateVisible(item));
2842
2843     listview->setCurrentIndex(2);
2844
2845     QVERIFY(item = findItem<QQuickItem>(contentItem, "item1"));
2846     QVERIFY(delegateVisible(item));
2847     QVERIFY(item = findItem<QQuickItem>(contentItem, "item2"));
2848     QVERIFY(delegateVisible(item));
2849     QVERIFY(item = findItem<QQuickItem>(contentItem, "item3"));
2850     QVERIFY(delegateVisible(item));
2851
2852     delete window;
2853 }
2854
2855 void tst_QQuickListView::cacheBuffer()
2856 {
2857     QQuickView *window = createView();
2858
2859     QaimModel model;
2860     for (int i = 0; i < 90; i++)
2861         model.addItem("Item" + QString::number(i), "");
2862
2863     QQmlContext *ctxt = window->rootContext();
2864     ctxt->setContextProperty("testModel", &model);
2865
2866     TestObject *testObject = new TestObject;
2867     ctxt->setContextProperty("testObject", testObject);
2868
2869     window->setSource(testFileUrl("listviewtest.qml"));
2870     window->show();
2871     qApp->processEvents();
2872
2873     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2874     QTRY_VERIFY(listview != 0);
2875
2876     QQuickItem *contentItem = listview->contentItem();
2877     QTRY_VERIFY(contentItem != 0);
2878     QTRY_VERIFY(listview->delegate() != 0);
2879     QTRY_VERIFY(listview->model() != 0);
2880     QTRY_VERIFY(listview->highlight() != 0);
2881
2882     // Confirm items positioned correctly
2883     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2884     for (int i = 0; i < model.count() && i < itemCount; ++i) {
2885         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2886         if (!item) qWarning() << "Item" << i << "not found";
2887         QTRY_VERIFY(item);
2888         QTRY_VERIFY(item->y() == i*20);
2889     }
2890
2891     QQmlIncubationController controller;
2892     window->engine()->setIncubationController(&controller);
2893
2894     testObject->setCacheBuffer(200);
2895     QTRY_VERIFY(listview->cacheBuffer() == 200);
2896
2897     // items will be created one at a time
2898     for (int i = itemCount; i < qMin(itemCount+10,model.count()); ++i) {
2899         QVERIFY(findItem<QQuickItem>(listview, "wrapper", i) == 0);
2900         QQuickItem *item = 0;
2901         while (!item) {
2902             bool b = false;
2903             controller.incubateWhile(&b);
2904             item = findItem<QQuickItem>(listview, "wrapper", i);
2905         }
2906     }
2907
2908     {
2909         bool b = true;
2910         controller.incubateWhile(&b);
2911     }
2912
2913     int newItemCount = 0;
2914     newItemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
2915
2916     // Confirm items positioned correctly
2917     for (int i = 0; i < model.count() && i < newItemCount; ++i) {
2918         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2919         if (!item) qWarning() << "Item" << i << "not found";
2920         QTRY_VERIFY(item);
2921         QTRY_VERIFY(item->y() == i*20);
2922     }
2923
2924     // move view and confirm items in view are visible immediately and outside are created async
2925     listview->setContentY(300);
2926
2927     for (int i = 15; i < 32; ++i) {
2928         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
2929         if (!item) qWarning() << "Item" << i << "not found";
2930         QVERIFY(item);
2931         QVERIFY(item->y() == i*20);
2932     }
2933
2934     QVERIFY(findItem<QQuickItem>(listview, "wrapper", 32) == 0);
2935
2936     // ensure buffered items are created
2937     for (int i = 32; i < qMin(41,model.count()); ++i) {
2938         QQuickItem *item = 0;
2939         while (!item) {
2940             qGuiApp->processEvents(); // allow refill to happen
2941             bool b = false;
2942             controller.incubateWhile(&b);
2943             item = findItem<QQuickItem>(listview, "wrapper", i);
2944         }
2945     }
2946
2947     {
2948         bool b = true;
2949         controller.incubateWhile(&b);
2950     }
2951
2952     delete window;
2953     delete testObject;
2954 }
2955
2956 void tst_QQuickListView::positionViewAtBeginningEnd()
2957 {
2958     QQuickView *window = createView();
2959
2960     QaimModel model;
2961     for (int i = 0; i < 40; i++)
2962         model.addItem("Item" + QString::number(i), "");
2963
2964     QQmlContext *ctxt = window->rootContext();
2965     ctxt->setContextProperty("testModel", &model);
2966
2967     TestObject *testObject = new TestObject;
2968     ctxt->setContextProperty("testObject", testObject);
2969     window->show();
2970     window->setSource(testFileUrl("listviewtest.qml"));
2971     qApp->processEvents();
2972
2973     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
2974     QTRY_VERIFY(listview != 0);
2975     QQuickItem *contentItem = listview->contentItem();
2976     QTRY_VERIFY(contentItem != 0);
2977     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
2978
2979     listview->setContentY(100);
2980
2981     // positionAtBeginnging
2982     listview->positionViewAtBeginning();
2983     QTRY_COMPARE(listview->contentY(), 0.);
2984
2985     listview->setContentY(80);
2986     window->rootObject()->setProperty("showHeader", true);
2987     listview->positionViewAtBeginning();
2988     QTRY_COMPARE(listview->contentY(), -30.);
2989
2990     // positionAtEnd
2991     listview->positionViewAtEnd();
2992     QTRY_COMPARE(listview->contentY(), 480.); // 40*20 - 320
2993
2994     listview->setContentY(80);
2995     window->rootObject()->setProperty("showFooter", true);
2996     listview->positionViewAtEnd();
2997     QTRY_COMPARE(listview->contentY(), 510.);
2998
2999     // set current item to outside visible view, position at beginning
3000     // and ensure highlight moves to current item
3001     listview->setCurrentIndex(1);
3002     listview->positionViewAtBeginning();
3003     QTRY_COMPARE(listview->contentY(), -30.);
3004     QVERIFY(listview->highlightItem());
3005     QCOMPARE(listview->highlightItem()->y(), 20.);
3006
3007     delete window;
3008     delete testObject;
3009 }
3010
3011 void tst_QQuickListView::positionViewAtIndex()
3012 {
3013     QFETCH(bool, enforceRange);
3014     QFETCH(qreal, initContentY);
3015     QFETCH(int, index);
3016     QFETCH(QQuickListView::PositionMode, mode);
3017     QFETCH(qreal, contentY);
3018
3019     QQuickView *window = getView();
3020
3021     QaimModel model;
3022     for (int i = 0; i < 40; i++)
3023         model.addItem("Item" + QString::number(i), "");
3024
3025     QQmlContext *ctxt = window->rootContext();
3026     ctxt->setContextProperty("testModel", &model);
3027
3028     TestObject *testObject = new TestObject;
3029     ctxt->setContextProperty("testObject", testObject);
3030     window->show();
3031     window->setSource(testFileUrl("listviewtest.qml"));
3032     qApp->processEvents();
3033
3034     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3035     QTRY_VERIFY(listview != 0);
3036     QQuickItem *contentItem = listview->contentItem();
3037     QTRY_VERIFY(contentItem != 0);
3038     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3039
3040     window->rootObject()->setProperty("enforceRange", enforceRange);
3041     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3042
3043     listview->setContentY(initContentY);
3044
3045     listview->positionViewAtIndex(index, mode);
3046     QTRY_COMPARE(listview->contentY(), contentY);
3047
3048     // Confirm items positioned correctly
3049     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3050     for (int i = index; i < model.count() && i < itemCount-index-1; ++i) {
3051         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3052         if (!item) qWarning() << "Item" << i << "not found";
3053         QTRY_VERIFY(item);
3054         QTRY_COMPARE(item->y(), i*20.);
3055     }
3056
3057     releaseView(window);
3058 }
3059
3060 void tst_QQuickListView::positionViewAtIndex_data()
3061 {
3062     QTest::addColumn<bool>("enforceRange");
3063     QTest::addColumn<qreal>("initContentY");
3064     QTest::addColumn<int>("index");
3065     QTest::addColumn<QQuickListView::PositionMode>("mode");
3066     QTest::addColumn<qreal>("contentY");
3067
3068     QTest::newRow("no range, 3 at Beginning") << false << 0. << 3 << QQuickListView::Beginning << 60.;
3069     QTest::newRow("no range, 3 at End") << false << 0. << 3 << QQuickListView::End << 0.;
3070     QTest::newRow("no range, 22 at Beginning") << false << 0. << 22 << QQuickListView::Beginning << 440.;
3071     // Position on an item that would leave empty space if positioned at the top
3072     QTest::newRow("no range, 28 at Beginning") << false << 0. << 28 << QQuickListView::Beginning << 480.;
3073     // Position at End using last index
3074     QTest::newRow("no range, last at End") << false << 0. << 39 << QQuickListView::End << 480.;
3075     // Position at End
3076     QTest::newRow("no range, 20 at End") << false << 0. << 20 << QQuickListView::End << 100.;
3077     // Position in Center
3078     QTest::newRow("no range, 15 at Center") << false << 0. << 15 << QQuickListView::Center << 150.;
3079     // Ensure at least partially visible
3080     QTest::newRow("no range, 15 visible => Visible") << false << 150. << 15 << QQuickListView::Visible << 150.;
3081     QTest::newRow("no range, 15 partially visible => Visible") << false << 302. << 15 << QQuickListView::Visible << 302.;
3082     QTest::newRow("no range, 15 before visible => Visible") << false << 320. << 15 << QQuickListView::Visible << 300.;
3083     QTest::newRow("no range, 20 visible => Visible") << false << 85. << 20 << QQuickListView::Visible << 85.;
3084     QTest::newRow("no range, 20 before visible => Visible") << false << 75. << 20 << QQuickListView::Visible << 100.;
3085     QTest::newRow("no range, 20 after visible => Visible") << false << 480. << 20 << QQuickListView::Visible << 400.;
3086     // Ensure completely visible
3087     QTest::newRow("no range, 20 visible => Contain") << false << 120. << 20 << QQuickListView::Contain << 120.;
3088     QTest::newRow("no range, 15 partially visible => Contain") << false << 302. << 15 << QQuickListView::Contain << 300.;
3089     QTest::newRow("no range, 20 partially visible => Contain") << false << 85. << 20 << QQuickListView::Contain << 100.;
3090
3091     QTest::newRow("strict range, 3 at End") << true << 0. << 3 << QQuickListView::End << -120.;
3092     QTest::newRow("strict range, 38 at Beginning") << true << 0. << 38 << QQuickListView::Beginning << 660.;
3093     QTest::newRow("strict range, 15 at Center") << true << 0. << 15 << QQuickListView::Center << 140.;
3094     QTest::newRow("strict range, 3 at SnapPosition") << true << 0. << 3 << QQuickListView::SnapPosition << -60.;
3095     QTest::newRow("strict range, 10 at SnapPosition") << true << 0. << 10 << QQuickListView::SnapPosition << 80.;
3096     QTest::newRow("strict range, 38 at SnapPosition") << true << 0. << 38 << QQuickListView::SnapPosition << 640.;
3097 }
3098
3099 void tst_QQuickListView::resetModel()
3100 {
3101     QQuickView *window = createView();
3102
3103     QStringList strings;
3104     strings << "one" << "two" << "three";
3105     QStringListModel model(strings);
3106
3107     QQmlContext *ctxt = window->rootContext();
3108     ctxt->setContextProperty("testModel", &model);
3109
3110     window->setSource(testFileUrl("displaylist.qml"));
3111     window->show();
3112     qApp->processEvents();
3113
3114     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3115     QTRY_VERIFY(listview != 0);
3116     QQuickItem *contentItem = listview->contentItem();
3117     QTRY_VERIFY(contentItem != 0);
3118     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3119
3120     QTRY_COMPARE(listview->count(), model.rowCount());
3121
3122     for (int i = 0; i < model.rowCount(); ++i) {
3123         QQuickText *display = findItem<QQuickText>(contentItem, "displayText", i);
3124         QTRY_VERIFY(display != 0);
3125         QTRY_COMPARE(display->text(), strings.at(i));
3126     }
3127
3128     strings.clear();
3129     strings << "four" << "five" << "six" << "seven";
3130     model.setStringList(strings);
3131
3132     QTRY_COMPARE(listview->count(), model.rowCount());
3133
3134     for (int i = 0; i < model.rowCount(); ++i) {
3135         QQuickText *display = findItem<QQuickText>(contentItem, "displayText", i);
3136         QTRY_VERIFY(display != 0);
3137         QTRY_COMPARE(display->text(), strings.at(i));
3138     }
3139
3140     delete window;
3141 }
3142
3143 void tst_QQuickListView::propertyChanges()
3144 {
3145     QQuickView *window = createView();
3146     QTRY_VERIFY(window);
3147     window->setSource(testFileUrl("propertychangestest.qml"));
3148
3149     QQuickListView *listView = window->rootObject()->findChild<QQuickListView*>("listView");
3150     QTRY_VERIFY(listView);
3151
3152     QSignalSpy highlightFollowsCurrentItemSpy(listView, SIGNAL(highlightFollowsCurrentItemChanged()));
3153     QSignalSpy preferredHighlightBeginSpy(listView, SIGNAL(preferredHighlightBeginChanged()));
3154     QSignalSpy preferredHighlightEndSpy(listView, SIGNAL(preferredHighlightEndChanged()));
3155     QSignalSpy highlightRangeModeSpy(listView, SIGNAL(highlightRangeModeChanged()));
3156     QSignalSpy keyNavigationWrapsSpy(listView, SIGNAL(keyNavigationWrapsChanged()));
3157     QSignalSpy cacheBufferSpy(listView, SIGNAL(cacheBufferChanged()));
3158     QSignalSpy snapModeSpy(listView, SIGNAL(snapModeChanged()));
3159
3160     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), true);
3161     QTRY_COMPARE(listView->preferredHighlightBegin(), 0.0);
3162     QTRY_COMPARE(listView->preferredHighlightEnd(), 0.0);
3163     QTRY_COMPARE(listView->highlightRangeMode(), QQuickListView::ApplyRange);
3164     QTRY_COMPARE(listView->isWrapEnabled(), true);
3165     QTRY_COMPARE(listView->cacheBuffer(), 10);
3166     QTRY_COMPARE(listView->snapMode(), QQuickListView::SnapToItem);
3167
3168     listView->setHighlightFollowsCurrentItem(false);
3169     listView->setPreferredHighlightBegin(1.0);
3170     listView->setPreferredHighlightEnd(1.0);
3171     listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
3172     listView->setWrapEnabled(false);
3173     listView->setCacheBuffer(3);
3174     listView->setSnapMode(QQuickListView::SnapOneItem);
3175
3176     QTRY_COMPARE(listView->highlightFollowsCurrentItem(), false);
3177     QTRY_COMPARE(listView->preferredHighlightBegin(), 1.0);
3178     QTRY_COMPARE(listView->preferredHighlightEnd(), 1.0);
3179     QTRY_COMPARE(listView->highlightRangeMode(), QQuickListView::StrictlyEnforceRange);
3180     QTRY_COMPARE(listView->isWrapEnabled(), false);
3181     QTRY_COMPARE(listView->cacheBuffer(), 3);
3182     QTRY_COMPARE(listView->snapMode(), QQuickListView::SnapOneItem);
3183
3184     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
3185     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
3186     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
3187     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
3188     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
3189     QTRY_COMPARE(cacheBufferSpy.count(),1);
3190     QTRY_COMPARE(snapModeSpy.count(),1);
3191
3192     listView->setHighlightFollowsCurrentItem(false);
3193     listView->setPreferredHighlightBegin(1.0);
3194     listView->setPreferredHighlightEnd(1.0);
3195     listView->setHighlightRangeMode(QQuickListView::StrictlyEnforceRange);
3196     listView->setWrapEnabled(false);
3197     listView->setCacheBuffer(3);
3198     listView->setSnapMode(QQuickListView::SnapOneItem);
3199
3200     QTRY_COMPARE(highlightFollowsCurrentItemSpy.count(),1);
3201     QTRY_COMPARE(preferredHighlightBeginSpy.count(),1);
3202     QTRY_COMPARE(preferredHighlightEndSpy.count(),1);
3203     QTRY_COMPARE(highlightRangeModeSpy.count(),1);
3204     QTRY_COMPARE(keyNavigationWrapsSpy.count(),1);
3205     QTRY_COMPARE(cacheBufferSpy.count(),1);
3206     QTRY_COMPARE(snapModeSpy.count(),1);
3207
3208     delete window;
3209 }
3210
3211 void tst_QQuickListView::componentChanges()
3212 {
3213     QQuickView *window = createView();
3214     QTRY_VERIFY(window);
3215     window->setSource(testFileUrl("propertychangestest.qml"));
3216
3217     QQuickListView *listView = window->rootObject()->findChild<QQuickListView*>("listView");
3218     QTRY_VERIFY(listView);
3219
3220     QQmlComponent component(window->engine());
3221     component.setData("import QtQuick 2.0; Rectangle { color: \"blue\"; }", QUrl::fromLocalFile(""));
3222
3223     QQmlComponent delegateComponent(window->engine());
3224     delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", QUrl::fromLocalFile(""));
3225
3226     QSignalSpy highlightSpy(listView, SIGNAL(highlightChanged()));
3227     QSignalSpy delegateSpy(listView, SIGNAL(delegateChanged()));
3228     QSignalSpy headerSpy(listView, SIGNAL(headerChanged()));
3229     QSignalSpy footerSpy(listView, SIGNAL(footerChanged()));
3230
3231     listView->setHighlight(&component);
3232     listView->setHeader(&component);
3233     listView->setFooter(&component);
3234     listView->setDelegate(&delegateComponent);
3235
3236     QTRY_COMPARE(listView->highlight(), &component);
3237     QTRY_COMPARE(listView->header(), &component);
3238     QTRY_COMPARE(listView->footer(), &component);
3239     QTRY_COMPARE(listView->delegate(), &delegateComponent);
3240
3241     QTRY_COMPARE(highlightSpy.count(),1);
3242     QTRY_COMPARE(delegateSpy.count(),1);
3243     QTRY_COMPARE(headerSpy.count(),1);
3244     QTRY_COMPARE(footerSpy.count(),1);
3245
3246     listView->setHighlight(&component);
3247     listView->setHeader(&component);
3248     listView->setFooter(&component);
3249     listView->setDelegate(&delegateComponent);
3250
3251     QTRY_COMPARE(highlightSpy.count(),1);
3252     QTRY_COMPARE(delegateSpy.count(),1);
3253     QTRY_COMPARE(headerSpy.count(),1);
3254     QTRY_COMPARE(footerSpy.count(),1);
3255
3256     delete window;
3257 }
3258
3259 void tst_QQuickListView::modelChanges()
3260 {
3261     QQuickView *window = createView();
3262     QTRY_VERIFY(window);
3263     window->setSource(testFileUrl("propertychangestest.qml"));
3264
3265     QQuickListView *listView = window->rootObject()->findChild<QQuickListView*>("listView");
3266     QTRY_VERIFY(listView);
3267
3268     QQuickListModel *alternateModel = window->rootObject()->findChild<QQuickListModel*>("alternateModel");
3269     QTRY_VERIFY(alternateModel);
3270     QVariant modelVariant = QVariant::fromValue<QObject *>(alternateModel);
3271     QSignalSpy modelSpy(listView, SIGNAL(modelChanged()));
3272
3273     listView->setModel(modelVariant);
3274     QTRY_COMPARE(listView->model(), modelVariant);
3275     QTRY_COMPARE(modelSpy.count(),1);
3276
3277     listView->setModel(modelVariant);
3278     QTRY_COMPARE(modelSpy.count(),1);
3279
3280     listView->setModel(QVariant());
3281     QTRY_COMPARE(modelSpy.count(),2);
3282
3283     delete window;
3284 }
3285
3286 void tst_QQuickListView::QTBUG_9791()
3287 {
3288     QQuickView *window = createView();
3289
3290     window->setSource(testFileUrl("strictlyenforcerange.qml"));
3291     qApp->processEvents();
3292
3293     QQuickListView *listview = qobject_cast<QQuickListView*>(window->rootObject());
3294     QTRY_VERIFY(listview != 0);
3295
3296     QQuickItem *contentItem = listview->contentItem();
3297     QTRY_VERIFY(contentItem != 0);
3298     QTRY_VERIFY(listview->delegate() != 0);
3299     QTRY_VERIFY(listview->model() != 0);
3300
3301     QMetaObject::invokeMethod(listview, "fillModel");
3302     qApp->processEvents();
3303
3304     // Confirm items positioned correctly
3305     int itemCount = findItems<QQuickItem>(contentItem, "wrapper", false).count();
3306     QCOMPARE(itemCount, 3);
3307
3308     for (int i = 0; i < itemCount; ++i) {
3309         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3310         if (!item) qWarning() << "Item" << i << "not found";
3311         QTRY_VERIFY(item);
3312         QTRY_COMPARE(item->x(), i*300.0);
3313     }
3314
3315     // check that view is positioned correctly
3316     QTRY_COMPARE(listview->contentX(), 590.0);
3317
3318     delete window;
3319 }
3320
3321 void tst_QQuickListView::manualHighlight()
3322 {
3323     QQuickView *window = new QQuickView(0);
3324     window->setGeometry(0,0,240,320);
3325
3326     QString filename(testFile("manual-highlight.qml"));
3327     window->setSource(QUrl::fromLocalFile(filename));
3328
3329     qApp->processEvents();
3330
3331     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3332     QTRY_VERIFY(listview != 0);
3333
3334     QQuickItem *contentItem = listview->contentItem();
3335     QTRY_VERIFY(contentItem != 0);
3336
3337     QTRY_COMPARE(listview->currentIndex(), 0);
3338     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
3339     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3340
3341     listview->setCurrentIndex(2);
3342
3343     QTRY_COMPARE(listview->currentIndex(), 2);
3344     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 2));
3345     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3346
3347     // QTBUG-15972
3348     listview->positionViewAtIndex(3, QQuickListView::Contain);
3349
3350     QTRY_COMPARE(listview->currentIndex(), 2);
3351     QTRY_COMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 2));
3352     QTRY_COMPARE(listview->highlightItem()->y() - 5, listview->currentItem()->y());
3353
3354     delete window;
3355 }
3356
3357 void tst_QQuickListView::QTBUG_11105()
3358 {
3359     QQuickView *window = createView();
3360
3361     QaimModel model;
3362     for (int i = 0; i < 30; i++)
3363         model.addItem("Item" + QString::number(i), "");
3364
3365     QQmlContext *ctxt = window->rootContext();
3366     ctxt->setContextProperty("testModel", &model);
3367
3368     TestObject *testObject = new TestObject;
3369     ctxt->setContextProperty("testObject", testObject);
3370
3371     window->setSource(testFileUrl("listviewtest.qml"));
3372     window->show();
3373     qApp->processEvents();
3374
3375     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3376     QTRY_VERIFY(listview != 0);
3377     QQuickItem *contentItem = listview->contentItem();
3378     QTRY_VERIFY(contentItem != 0);
3379     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3380
3381     // Confirm items positioned correctly
3382     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3383     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3384         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3385         if (!item) qWarning() << "Item" << i << "not found";
3386         QTRY_VERIFY(item);
3387         QTRY_VERIFY(item->y() == i*20);
3388     }
3389
3390     listview->positionViewAtIndex(20, QQuickListView::Beginning);
3391     QCOMPARE(listview->contentY(), 280.);
3392
3393     QaimModel model2;
3394     for (int i = 0; i < 5; i++)
3395         model2.addItem("Item" + QString::number(i), "");
3396
3397     ctxt->setContextProperty("testModel", &model2);
3398
3399     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3400     QCOMPARE(itemCount, 5);
3401
3402     delete window;
3403     delete testObject;
3404 }
3405
3406 void tst_QQuickListView::initialZValues()
3407 {
3408     QQuickView *window = createView();
3409     window->setSource(testFileUrl("initialZValues.qml"));
3410     qApp->processEvents();
3411
3412     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3413     QTRY_VERIFY(listview != 0);
3414     QQuickItem *contentItem = listview->contentItem();
3415     QTRY_VERIFY(contentItem != 0);
3416
3417     QVERIFY(listview->headerItem());
3418     QTRY_COMPARE(listview->headerItem()->z(), listview->property("initialZ").toReal());
3419
3420     QVERIFY(listview->footerItem());
3421     QTRY_COMPARE(listview->footerItem()->z(), listview->property("initialZ").toReal());
3422
3423     delete window;
3424 }
3425
3426 void tst_QQuickListView::header()
3427 {
3428     QFETCH(QQuickListView::Orientation, orientation);
3429     QFETCH(Qt::LayoutDirection, layoutDirection);
3430     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3431     QFETCH(QPointF, initialHeaderPos);
3432     QFETCH(QPointF, changedHeaderPos);
3433     QFETCH(QPointF, initialContentPos);
3434     QFETCH(QPointF, changedContentPos);
3435     QFETCH(QPointF, firstDelegatePos);
3436     QFETCH(QPointF, resizeContentPos);
3437
3438     QaimModel model;
3439     for (int i = 0; i < 30; i++)
3440         model.addItem("Item" + QString::number(i), "");
3441
3442     QQuickView *window = getView();
3443     window->rootContext()->setContextProperty("testModel", &model);
3444     window->rootContext()->setContextProperty("initialViewWidth", 240);
3445     window->rootContext()->setContextProperty("initialViewHeight", 320);
3446     window->setSource(testFileUrl("header.qml"));
3447     window->show();
3448     qApp->processEvents();
3449
3450     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3451     QTRY_VERIFY(listview != 0);
3452     listview->setOrientation(orientation);
3453     listview->setLayoutDirection(layoutDirection);
3454     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3455     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3456
3457     QQuickItem *contentItem = listview->contentItem();
3458     QTRY_VERIFY(contentItem != 0);
3459
3460     QQuickText *header = 0;
3461     QTRY_VERIFY(header = findItem<QQuickText>(contentItem, "header"));
3462     QVERIFY(header == listview->headerItem());
3463
3464     QCOMPARE(header->width(), 100.);
3465     QCOMPARE(header->height(), 30.);
3466     QCOMPARE(header->pos(), initialHeaderPos);
3467     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3468
3469     if (orientation == QQuickListView::Vertical)
3470         QCOMPARE(listview->contentHeight(), model.count() * 30. + header->height());
3471     else
3472         QCOMPARE(listview->contentWidth(), model.count() * 240. + header->width());
3473
3474     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3475     QVERIFY(item);
3476     QCOMPARE(item->pos(), firstDelegatePos);
3477
3478     model.clear();
3479     QTRY_COMPARE(listview->count(), model.count());
3480     QCOMPARE(header->pos(), initialHeaderPos); // header should stay where it is
3481     if (orientation == QQuickListView::Vertical)
3482         QCOMPARE(listview->contentHeight(), header->height());
3483     else
3484         QCOMPARE(listview->contentWidth(), header->width());
3485
3486     for (int i = 0; i < 30; i++)
3487         model.addItem("Item" + QString::number(i), "");
3488
3489     QSignalSpy headerItemSpy(listview, SIGNAL(headerItemChanged()));
3490     QMetaObject::invokeMethod(window->rootObject(), "changeHeader");
3491
3492     QCOMPARE(headerItemSpy.count(), 1);
3493
3494     header = findItem<QQuickText>(contentItem, "header");
3495     QVERIFY(!header);
3496     header = findItem<QQuickText>(contentItem, "header2");
3497     QVERIFY(header);
3498
3499     QVERIFY(header == listview->headerItem());
3500
3501     QCOMPARE(header->pos(), changedHeaderPos);
3502     QCOMPARE(header->width(), 50.);
3503     QCOMPARE(header->height(), 20.);
3504     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3505
3506     item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3507     QVERIFY(item);
3508     QCOMPARE(item->pos(), firstDelegatePos);
3509
3510     listview->positionViewAtBeginning();
3511     header->setHeight(10);
3512     header->setWidth(40);
3513     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3514
3515     releaseView(window);
3516
3517
3518     // QTBUG-21207 header should become visible if view resizes from initial empty size
3519
3520     window = getView();
3521     window->rootContext()->setContextProperty("testModel", &model);
3522     window->rootContext()->setContextProperty("initialViewWidth", 0.0);
3523     window->rootContext()->setContextProperty("initialViewHeight", 0.0);
3524     window->setSource(testFileUrl("header.qml"));
3525     window->show();
3526     qApp->processEvents();
3527
3528     listview = findItem<QQuickListView>(window->rootObject(), "list");
3529     QTRY_VERIFY(listview != 0);
3530     listview->setOrientation(orientation);
3531     listview->setLayoutDirection(layoutDirection);
3532     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3533     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3534
3535     listview->setWidth(240);
3536     listview->setHeight(320);
3537     QTRY_COMPARE(listview->headerItem()->pos(), initialHeaderPos);
3538     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3539
3540     releaseView(window);
3541 }
3542
3543 void tst_QQuickListView::header_data()
3544 {
3545     QTest::addColumn<QQuickListView::Orientation>("orientation");
3546     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3547     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
3548     QTest::addColumn<QPointF>("initialHeaderPos");
3549     QTest::addColumn<QPointF>("changedHeaderPos");
3550     QTest::addColumn<QPointF>("initialContentPos");
3551     QTest::addColumn<QPointF>("changedContentPos");
3552     QTest::addColumn<QPointF>("firstDelegatePos");
3553     QTest::addColumn<QPointF>("resizeContentPos");
3554
3555     // header1 = 100 x 30
3556     // header2 = 50 x 20
3557     // delegates = 240 x 30
3558     // view width = 240
3559
3560     // header above items, top left
3561     QTest::newRow("vertical, left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3562         << QPointF(0, -30)
3563         << QPointF(0, -20)
3564         << QPointF(0, -30)
3565         << QPointF(0, -20)
3566         << QPointF(0, 0)
3567         << QPointF(0, -10);
3568
3569     // header above items, top right
3570     QTest::newRow("vertical, layout right to left") << QQuickListView::Vertical << Qt::RightToLeft << QQuickItemView::TopToBottom
3571         << QPointF(0, -30)
3572         << QPointF(0, -20)
3573         << QPointF(0, -30)
3574         << QPointF(0, -20)
3575         << QPointF(0, 0)
3576         << QPointF(0, -10);
3577
3578     // header to left of items
3579     QTest::newRow("horizontal, layout left to right") << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3580         << QPointF(-100, 0)
3581         << QPointF(-50, 0)
3582         << QPointF(-100, 0)
3583         << QPointF(-50, 0)
3584         << QPointF(0, 0)
3585         << QPointF(-40, 0);
3586
3587     // header to right of items
3588     QTest::newRow("horizontal, layout right to left") << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3589         << QPointF(0, 0)
3590         << QPointF(0, 0)
3591         << QPointF(-240 + 100, 0)
3592         << QPointF(-240 + 50, 0)
3593         << QPointF(-240, 0)
3594         << QPointF(-240 + 40, 0);
3595
3596     // header below items
3597     QTest::newRow("vertical, bottom to top") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3598         << QPointF(0, 0)
3599         << QPointF(0, 0)
3600         << QPointF(0, -320 + 30)
3601         << QPointF(0, -320 + 20)
3602         << QPointF(0, -30)
3603         << QPointF(0, -320 + 10);
3604 }
3605
3606 void tst_QQuickListView::header_delayItemCreation()
3607 {
3608     QQuickView *window = createView();
3609
3610     QaimModel model;
3611
3612     window->rootContext()->setContextProperty("setCurrentToZero", QVariant(false));
3613     window->setSource(testFileUrl("fillModelOnComponentCompleted.qml"));
3614     qApp->processEvents();
3615
3616     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3617     QTRY_VERIFY(listview != 0);
3618
3619     QQuickItem *contentItem = listview->contentItem();
3620     QTRY_VERIFY(contentItem != 0);
3621
3622     QQuickText *header = findItem<QQuickText>(contentItem, "header");
3623     QVERIFY(header);
3624     QCOMPARE(header->y(), -header->height());
3625
3626     QCOMPARE(listview->contentY(), -header->height());
3627
3628     model.clear();
3629     QTRY_COMPARE(header->y(), -header->height());
3630
3631     delete window;
3632 }
3633
3634 void tst_QQuickListView::footer()
3635 {
3636     QFETCH(QQuickListView::Orientation, orientation);
3637     QFETCH(Qt::LayoutDirection, layoutDirection);
3638     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3639     QFETCH(QPointF, initialFooterPos);
3640     QFETCH(QPointF, firstDelegatePos);
3641     QFETCH(QPointF, initialContentPos);
3642     QFETCH(QPointF, changedFooterPos);
3643     QFETCH(QPointF, changedContentPos);
3644     QFETCH(QPointF, resizeContentPos);
3645
3646     QQuickView *window = getView();
3647
3648     QaimModel model;
3649     for (int i = 0; i < 3; i++)
3650         model.addItem("Item" + QString::number(i), "");
3651
3652     QQmlContext *ctxt = window->rootContext();
3653     ctxt->setContextProperty("testModel", &model);
3654
3655     window->setSource(testFileUrl("footer.qml"));
3656     window->show();
3657     qApp->processEvents();
3658
3659     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3660     QTRY_VERIFY(listview != 0);
3661     listview->setOrientation(orientation);
3662     listview->setLayoutDirection(layoutDirection);
3663     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3664     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3665
3666     QQuickItem *contentItem = listview->contentItem();
3667     QTRY_VERIFY(contentItem != 0);
3668
3669     QQuickText *footer = findItem<QQuickText>(contentItem, "footer");
3670     QVERIFY(footer);
3671
3672     QVERIFY(footer == listview->footerItem());
3673
3674     QCOMPARE(footer->pos(), initialFooterPos);
3675     QCOMPARE(footer->width(), 100.);
3676     QCOMPARE(footer->height(), 30.);
3677     QCOMPARE(QPointF(listview->contentX(), listview->contentY()), initialContentPos);
3678
3679     if (orientation == QQuickListView::Vertical)
3680         QCOMPARE(listview->contentHeight(), model.count() * 20. + footer->height());
3681     else
3682         QCOMPARE(listview->contentWidth(), model.count() * 40. + footer->width());
3683
3684     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3685     QVERIFY(item);
3686     QCOMPARE(item->pos(), firstDelegatePos);
3687
3688     // remove one item
3689     model.removeItem(1);
3690
3691     if (orientation == QQuickListView::Vertical) {
3692         QTRY_COMPARE(footer->y(), verticalLayoutDirection == QQuickItemView::TopToBottom ?
3693                 initialFooterPos.y() - 20 : initialFooterPos.y() + 20);  // delegate width = 40
3694     } else {
3695         QTRY_COMPARE(footer->x(), layoutDirection == Qt::LeftToRight ?
3696                 initialFooterPos.x() - 40 : initialFooterPos.x() + 40);  // delegate width = 40
3697     }
3698
3699     // remove all items
3700     model.clear();
3701     if (orientation == QQuickListView::Vertical)
3702         QTRY_COMPARE(listview->contentHeight(), footer->height());
3703     else
3704         QTRY_COMPARE(listview->contentWidth(), footer->width());
3705
3706     QPointF posWhenNoItems(0, 0);
3707     if (orientation == QQuickListView::Horizontal && layoutDirection == Qt::RightToLeft)
3708         posWhenNoItems.setX(-100);
3709     else if (orientation == QQuickListView::Vertical && verticalLayoutDirection == QQuickItemView::BottomToTop)
3710         posWhenNoItems.setY(-30);
3711     QTRY_COMPARE(footer->pos(), posWhenNoItems);
3712
3713     // if header is present, it's at a negative pos, so the footer should not move
3714     window->rootObject()->setProperty("showHeader", true);
3715     QTRY_COMPARE(footer->pos(), posWhenNoItems);
3716     window->rootObject()->setProperty("showHeader", false);
3717
3718     // add 30 items
3719     for (int i = 0; i < 30; i++)
3720         model.addItem("Item" + QString::number(i), "");
3721
3722     QSignalSpy footerItemSpy(listview, SIGNAL(footerItemChanged()));
3723     QMetaObject::invokeMethod(window->rootObject(), "changeFooter");
3724
3725     QCOMPARE(footerItemSpy.count(), 1);
3726
3727     footer = findItem<QQuickText>(contentItem, "footer");
3728     QVERIFY(!footer);
3729     footer = findItem<QQuickText>(contentItem, "footer2");
3730     QVERIFY(footer);
3731
3732     QVERIFY(footer == listview->footerItem());
3733
3734     QCOMPARE(footer->pos(), changedFooterPos);
3735     QCOMPARE(footer->width(), 50.);
3736     QCOMPARE(footer->height(), 20.);
3737     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), changedContentPos);
3738
3739     item = findItem<QQuickItem>(contentItem, "wrapper", 0);
3740     QVERIFY(item);
3741     QCOMPARE(item->pos(), firstDelegatePos);
3742
3743     listview->positionViewAtEnd();
3744     footer->setHeight(10);
3745     footer->setWidth(40);
3746     QTRY_COMPARE(QPointF(listview->contentX(), listview->contentY()), resizeContentPos);
3747
3748     releaseView(window);
3749 }
3750
3751 void tst_QQuickListView::footer_data()
3752 {
3753     QTest::addColumn<QQuickListView::Orientation>("orientation");
3754     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3755     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
3756     QTest::addColumn<QPointF>("initialFooterPos");
3757     QTest::addColumn<QPointF>("changedFooterPos");
3758     QTest::addColumn<QPointF>("initialContentPos");
3759     QTest::addColumn<QPointF>("changedContentPos");
3760     QTest::addColumn<QPointF>("firstDelegatePos");
3761     QTest::addColumn<QPointF>("resizeContentPos");
3762
3763     // footer1 = 100 x 30
3764     // footer2 = 50 x 20
3765     // delegates = 40 x 20
3766     // view width = 240
3767     // view height = 320
3768
3769     // footer below items, bottom left
3770     QTest::newRow("vertical, layout left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3771         << QPointF(0, 3 * 20)
3772         << QPointF(0, 30 * 20)  // added 30 items
3773         << QPointF(0, 0)
3774         << QPointF(0, 0)
3775         << QPointF(0, 0)
3776         << QPointF(0, 30 * 20 - 320 + 10);
3777
3778     // footer below items, bottom right
3779     QTest::newRow("vertical, layout right to left") << QQuickListView::Vertical << Qt::RightToLeft << QQuickItemView::TopToBottom
3780         << QPointF(0, 3 * 20)
3781         << QPointF(0, 30 * 20)
3782         << QPointF(0, 0)
3783         << QPointF(0, 0)
3784         << QPointF(0, 0)
3785         << QPointF(0, 30 * 20 - 320 + 10);
3786
3787     // footer to right of items
3788     QTest::newRow("horizontal, layout left to right") << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3789         << QPointF(40 * 3, 0)
3790         << QPointF(40 * 30, 0)
3791         << QPointF(0, 0)
3792         << QPointF(0, 0)
3793         << QPointF(0, 0)
3794         << QPointF(40 * 30 - 240 + 40, 0);
3795
3796     // footer to left of items
3797     QTest::newRow("horizontal, layout right to left") << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3798         << QPointF(-(40 * 3) - 100, 0)
3799         << QPointF(-(40 * 30) - 50, 0)     // 50 = new footer width
3800         << QPointF(-240, 0)
3801         << QPointF(-240, 0)
3802         << QPointF(-40, 0)
3803         << QPointF(-(40 * 30) - 40, 0);
3804
3805     // footer above items
3806     QTest::newRow("vertical, layout left to right") << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3807         << QPointF(0, -(3 * 20) - 30)
3808         << QPointF(0, -(30 * 20) - 20)
3809         << QPointF(0, -320)
3810         << QPointF(0, -320)
3811         << QPointF(0, -20)
3812         << QPointF(0, -(30 * 20) - 10);
3813 }
3814
3815 class LVAccessor : public QQuickListView
3816 {
3817 public:
3818     qreal minY() const { return minYExtent(); }
3819     qreal maxY() const { return maxYExtent(); }
3820     qreal minX() const { return minXExtent(); }
3821     qreal maxX() const { return maxXExtent(); }
3822 };
3823
3824
3825 void tst_QQuickListView::extents()
3826 {
3827     QFETCH(QQuickListView::Orientation, orientation);
3828     QFETCH(Qt::LayoutDirection, layoutDirection);
3829     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
3830     QFETCH(QPointF, headerPos);
3831     QFETCH(QPointF, footerPos);
3832     QFETCH(QPointF, minPos);
3833     QFETCH(QPointF, maxPos);
3834     QFETCH(QPointF, origin_empty);
3835     QFETCH(QPointF, origin_nonEmpty);
3836
3837     QQuickView *window = getView();
3838
3839     QaimModel model;
3840     QQmlContext *ctxt = window->rootContext();
3841
3842     ctxt->setContextProperty("testModel", &model);
3843     window->setSource(testFileUrl("headerfooter.qml"));
3844     window->show();
3845     qApp->processEvents();
3846
3847     QQuickListView *listview = qobject_cast<QQuickListView*>(window->rootObject());
3848     QTRY_VERIFY(listview != 0);
3849     listview->setOrientation(orientation);
3850     listview->setLayoutDirection(layoutDirection);
3851     listview->setVerticalLayoutDirection(verticalLayoutDirection);
3852     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3853
3854     QQuickItem *contentItem = listview->contentItem();
3855     QTRY_VERIFY(contentItem != 0);
3856
3857     QQuickItem *header = findItem<QQuickItem>(contentItem, "header");
3858     QVERIFY(header);
3859     QCOMPARE(header->pos(), headerPos);
3860
3861     QQuickItem *footer = findItem<QQuickItem>(contentItem, "footer");
3862     QVERIFY(footer);
3863     QCOMPARE(footer->pos(), footerPos);
3864
3865     QCOMPARE(static_cast<LVAccessor*>(listview)->minX(), minPos.x());
3866     QCOMPARE(static_cast<LVAccessor*>(listview)->minY(), minPos.y());
3867     QCOMPARE(static_cast<LVAccessor*>(listview)->maxX(), maxPos.x());
3868     QCOMPARE(static_cast<LVAccessor*>(listview)->maxY(), maxPos.y());
3869
3870     QCOMPARE(listview->originX(), origin_empty.x());
3871     QCOMPARE(listview->originY(), origin_empty.y());
3872     for (int i=0; i<30; i++)
3873         model.addItem("Item" + QString::number(i), "");
3874     QTRY_COMPARE(listview->count(), model.count());
3875     QCOMPARE(listview->originX(), origin_nonEmpty.x());
3876     QCOMPARE(listview->originY(), origin_nonEmpty.y());
3877
3878     releaseView(window);
3879 }
3880
3881 void tst_QQuickListView::extents_data()
3882 {
3883     QTest::addColumn<QQuickListView::Orientation>("orientation");
3884     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
3885     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
3886     QTest::addColumn<QPointF>("headerPos");
3887     QTest::addColumn<QPointF>("footerPos");
3888     QTest::addColumn<QPointF>("minPos");
3889     QTest::addColumn<QPointF>("maxPos");
3890     QTest::addColumn<QPointF>("origin_empty");
3891     QTest::addColumn<QPointF>("origin_nonEmpty");
3892
3893     // header is 240x20 (or 20x320 in Horizontal orientation)
3894     // footer is 240x30 (or 30x320 in Horizontal orientation)
3895
3896     QTest::newRow("Vertical, TopToBottom")
3897             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
3898             << QPointF(0, -20) << QPointF(0, 0)
3899             << QPointF(0, 20) << QPointF(240, 20)
3900             << QPointF(0, -20) << QPointF(0, -20);
3901
3902     QTest::newRow("Vertical, BottomToTop")
3903             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
3904             << QPointF(0, 0) << QPointF(0, -30)
3905             << QPointF(0, 320 - 20) << QPointF(240, 320 - 20)  // content flow is reversed
3906             << QPointF(0, -30) << QPointF(0, (-30.0 * 30) - 30);
3907
3908     QTest::newRow("Horizontal, LeftToRight")
3909             << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom
3910             << QPointF(-20, 0) << QPointF(0, 0)
3911             << QPointF(20, 0) << QPointF(20, 320)
3912             << QPointF(-20, 0) << QPointF(-20, 0);
3913
3914     QTest::newRow("Horizontal, RightToLeft")
3915             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
3916             << QPointF(0, 0) << QPointF(-30, 0)
3917             << QPointF(240 - 20, 0) << QPointF(240 - 20, 320)  // content flow is reversed
3918             << QPointF(-30, 0) << QPointF((-240.0 * 30) - 30, 0);
3919 }
3920
3921 void tst_QQuickListView::resetModel_headerFooter()
3922 {
3923     // Resetting a model shouldn't crash in views with header/footer
3924
3925     QQuickView *window = createView();
3926
3927     QaimModel model;
3928     for (int i = 0; i < 4; i++)
3929         model.addItem("Item" + QString::number(i), "");
3930     QQmlContext *ctxt = window->rootContext();
3931     ctxt->setContextProperty("testModel", &model);
3932
3933     window->setSource(testFileUrl("headerfooter.qml"));
3934     qApp->processEvents();
3935
3936     QQuickListView *listview = qobject_cast<QQuickListView*>(window->rootObject());
3937     QTRY_VERIFY(listview != 0);
3938
3939     QQuickItem *contentItem = listview->contentItem();
3940     QTRY_VERIFY(contentItem != 0);
3941
3942     QQuickItem *header = findItem<QQuickItem>(contentItem, "header");
3943     QVERIFY(header);
3944     QCOMPARE(header->y(), -header->height());
3945
3946     QQuickItem *footer = findItem<QQuickItem>(contentItem, "footer");
3947     QVERIFY(footer);
3948     QCOMPARE(footer->y(), 30.*4);
3949
3950     model.reset();
3951
3952     header = findItem<QQuickItem>(contentItem, "header");
3953     QVERIFY(header);
3954     QCOMPARE(header->y(), -header->height());
3955
3956     footer = findItem<QQuickItem>(contentItem, "footer");
3957     QVERIFY(footer);
3958     QCOMPARE(footer->y(), 30.*4);
3959
3960     delete window;
3961 }
3962
3963 void tst_QQuickListView::resizeView()
3964 {
3965     QQuickView *window = createView();
3966
3967     QaimModel model;
3968     for (int i = 0; i < 40; i++)
3969         model.addItem("Item" + QString::number(i), "");
3970
3971     QQmlContext *ctxt = window->rootContext();
3972     ctxt->setContextProperty("testModel", &model);
3973
3974     TestObject *testObject = new TestObject;
3975     ctxt->setContextProperty("testObject", testObject);
3976
3977     window->setSource(testFileUrl("listviewtest.qml"));
3978     window->show();
3979     qApp->processEvents();
3980
3981     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
3982     QTRY_VERIFY(listview != 0);
3983     QQuickItem *contentItem = listview->contentItem();
3984     QTRY_VERIFY(contentItem != 0);
3985     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
3986
3987     // Confirm items positioned correctly
3988     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
3989     for (int i = 0; i < model.count() && i < itemCount; ++i) {
3990         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
3991         if (!item) qWarning() << "Item" << i << "not found";
3992         QTRY_VERIFY(item);
3993         QTRY_COMPARE(item->y(), i*20.);
3994     }
3995
3996     QVariant heightRatio;
3997     QMetaObject::invokeMethod(window->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
3998     QCOMPARE(heightRatio.toReal(), 0.4);
3999
4000     listview->setHeight(200);
4001     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4002
4003     QMetaObject::invokeMethod(window->rootObject(), "heightRatio", Q_RETURN_ARG(QVariant, heightRatio));
4004     QCOMPARE(heightRatio.toReal(), 0.25);
4005
4006     // Ensure we handle -ve sizes
4007     listview->setHeight(-100);
4008     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 1);
4009
4010     listview->setCacheBuffer(200);
4011     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 11);
4012
4013     // ensure items in cache become visible
4014     listview->setHeight(200);
4015     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 21);
4016
4017     itemCount = findItems<QQuickItem>(contentItem, "wrapper", false).count();
4018     for (int i = 0; i < model.count() && i < itemCount; ++i) {
4019         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4020         if (!item) qWarning() << "Item" << i << "not found";
4021         QTRY_VERIFY(item);
4022         QTRY_COMPARE(item->y(), i*20.);
4023         QCOMPARE(delegateVisible(item), i < 11); // inside view visible, outside not visible
4024     }
4025
4026     // ensure items outside view become invisible
4027     listview->setHeight(100);
4028     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper", false).count(), 16);
4029
4030     itemCount = findItems<QQuickItem>(contentItem, "wrapper", false).count();
4031     for (int i = 0; i < model.count() && i < itemCount; ++i) {
4032         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4033         if (!item) qWarning() << "Item" << i << "not found";
4034         QTRY_VERIFY(item);
4035         QTRY_COMPARE(item->y(), i*20.);
4036         QCOMPARE(delegateVisible(item), i < 6); // inside view visible, outside not visible
4037     }
4038
4039     delete window;
4040     delete testObject;
4041 }
4042
4043 void tst_QQuickListView::resizeViewAndRepaint()
4044 {
4045     QQuickView *window = createView();
4046
4047     QaimModel model;
4048     for (int i = 0; i < 40; i++)
4049         model.addItem("Item" + QString::number(i), "");
4050
4051     QQmlContext *ctxt = window->rootContext();
4052     ctxt->setContextProperty("testModel", &model);
4053     ctxt->setContextProperty("initialHeight", 100);
4054
4055     window->setSource(testFileUrl("resizeview.qml"));
4056     window->show();
4057     qApp->processEvents();
4058
4059     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4060     QTRY_VERIFY(listview != 0);
4061     QQuickItem *contentItem = listview->contentItem();
4062     QTRY_VERIFY(contentItem != 0);
4063     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4064
4065     // item at index 10 should not be currently visible
4066     QVERIFY(!findItem<QQuickItem>(contentItem, "wrapper", 10));
4067
4068     listview->setHeight(320);
4069
4070     QTRY_VERIFY(findItem<QQuickItem>(contentItem, "wrapper", 10));
4071
4072     listview->setHeight(100);
4073     QTRY_VERIFY(!findItem<QQuickItem>(contentItem, "wrapper", 10));
4074
4075     delete window;
4076 }
4077
4078 void tst_QQuickListView::sizeLessThan1()
4079 {
4080     QQuickView *window = createView();
4081
4082     QaimModel model;
4083     for (int i = 0; i < 30; i++)
4084         model.addItem("Item" + QString::number(i), "");
4085
4086     QQmlContext *ctxt = window->rootContext();
4087     ctxt->setContextProperty("testModel", &model);
4088
4089     TestObject *testObject = new TestObject;
4090     ctxt->setContextProperty("testObject", testObject);
4091
4092     window->setSource(testFileUrl("sizelessthan1.qml"));
4093     window->show();
4094     qApp->processEvents();
4095
4096     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4097     QTRY_VERIFY(listview != 0);
4098     QQuickItem *contentItem = listview->contentItem();
4099     QTRY_VERIFY(contentItem != 0);
4100     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4101
4102     // Confirm items positioned correctly
4103     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
4104     for (int i = 0; i < model.count() && i < itemCount; ++i) {
4105         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4106         if (!item) qWarning() << "Item" << i << "not found";
4107         QTRY_VERIFY(item);
4108         QTRY_COMPARE(item->y(), i*0.5);
4109     }
4110
4111     delete window;
4112     delete testObject;
4113 }
4114
4115 void tst_QQuickListView::QTBUG_14821()
4116 {
4117     QQuickView *window = createView();
4118
4119     window->setSource(testFileUrl("qtbug14821.qml"));
4120     qApp->processEvents();
4121
4122     QQuickListView *listview = qobject_cast<QQuickListView*>(window->rootObject());
4123     QVERIFY(listview != 0);
4124
4125     QQuickItem *contentItem = listview->contentItem();
4126     QVERIFY(contentItem != 0);
4127
4128     listview->decrementCurrentIndex();
4129     QCOMPARE(listview->currentIndex(), 99);
4130
4131     listview->incrementCurrentIndex();
4132     QCOMPARE(listview->currentIndex(), 0);
4133
4134     delete window;
4135 }
4136
4137 void tst_QQuickListView::resizeDelegate()
4138 {
4139     QQuickView *window = createView();
4140
4141     QStringList strings;
4142     for (int i = 0; i < 30; ++i)
4143         strings << QString::number(i);
4144     QStringListModel model(strings);
4145
4146     QQmlContext *ctxt = window->rootContext();
4147     ctxt->setContextProperty("testModel", &model);
4148
4149     window->setSource(testFileUrl("displaylist.qml"));
4150     window->show();
4151     qApp->processEvents();
4152
4153     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4154     QVERIFY(listview != 0);
4155     QQuickItem *contentItem = listview->contentItem();
4156     QVERIFY(contentItem != 0);
4157     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4158
4159     QCOMPARE(listview->count(), model.rowCount());
4160
4161     listview->setCurrentIndex(25);
4162     listview->setContentY(0);
4163     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4164
4165     for (int i = 0; i < 16; ++i) {
4166         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4167         QVERIFY(item != 0);
4168         QCOMPARE(item->y(), i*20.0);
4169     }
4170
4171     QCOMPARE(listview->currentItem()->y(), 500.0);
4172     QTRY_COMPARE(listview->highlightItem()->y(), 500.0);
4173
4174     window->rootObject()->setProperty("delegateHeight", 30);
4175     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4176
4177     for (int i = 0; i < 11; ++i) {
4178         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4179         QVERIFY(item != 0);
4180         QTRY_COMPARE(item->y(), i*30.0);
4181     }
4182
4183     QTRY_COMPARE(listview->currentItem()->y(), 750.0);
4184     QTRY_COMPARE(listview->highlightItem()->y(), 750.0);
4185
4186     listview->setCurrentIndex(1);
4187     listview->positionViewAtIndex(25, QQuickListView::Beginning);
4188     listview->positionViewAtIndex(5, QQuickListView::Beginning);
4189     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4190
4191     for (int i = 5; i < 16; ++i) {
4192         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4193         QVERIFY(item != 0);
4194         QCOMPARE(item->y(), i*30.0);
4195     }
4196
4197     QTRY_COMPARE(listview->currentItem()->y(), 30.0);
4198     QTRY_COMPARE(listview->highlightItem()->y(), 30.0);
4199
4200     window->rootObject()->setProperty("delegateHeight", 20);
4201     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4202
4203     for (int i = 5; i < 11; ++i) {
4204         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
4205         QVERIFY(item != 0);
4206         QTRY_COMPARE(item->y(), 150 + (i-5)*20.0);
4207     }
4208
4209     QTRY_COMPARE(listview->currentItem()->y(), 70.0);
4210     QTRY_COMPARE(listview->highlightItem()->y(), 70.0);
4211
4212     delete window;
4213 }
4214
4215 void tst_QQuickListView::resizeFirstDelegate()
4216 {
4217     // QTBUG-20712: Content Y jumps constantly if first delegate height == 0
4218     // and other delegates have height > 0
4219
4220     QQuickView *window = createView();
4221
4222     // bug only occurs when all items in the model are visible
4223     QaimModel model;
4224     for (int i = 0; i < 10; i++)
4225         model.addItem("Item" + QString::number(i), "");
4226
4227     QQmlContext *ctxt = window->rootContext();
4228     ctxt->setContextProperty("testModel", &model);
4229
4230     TestObject *testObject = new TestObject;
4231     ctxt->setContextProperty("testObject", testObject);
4232
4233     window->setSource(testFileUrl("listviewtest.qml"));
4234     window->show();
4235     qApp->processEvents();
4236
4237     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4238     QVERIFY(listview != 0);
4239     QQuickItem *contentItem = listview->contentItem();
4240     QVERIFY(contentItem != 0);
4241     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4242
4243     QQuickItem *item = 0;
4244     for (int i = 0; i < model.count(); ++i) {
4245         item = findItem<QQuickItem>(contentItem, "wrapper", i);
4246         QVERIFY(item != 0);
4247         QCOMPARE(item->y(), i*20.0);
4248     }
4249
4250     item = findItem<QQuickItem>(contentItem, "wrapper", 0);
4251     item->setHeight(0);
4252
4253     // check the content y has not jumped up and down
4254     QCOMPARE(listview->contentY(), 0.0);
4255     QSignalSpy spy(listview, SIGNAL(contentYChanged()));
4256     QTest::qWait(100);
4257     QCOMPARE(spy.count(), 0);
4258
4259     for (int i = 1; i < model.count(); ++i) {
4260         item = findItem<QQuickItem>(contentItem, "wrapper", i);
4261         QVERIFY(item != 0);
4262         QTRY_COMPARE(item->y(), (i-1)*20.0);
4263     }
4264
4265
4266     // QTBUG-22014: refill doesn't clear items scrolling off the top of the
4267     // list if they follow a zero-sized delegate
4268
4269     for (int i = 0; i < 10; i++)
4270         model.addItem("Item" + QString::number(i), "");
4271     QTRY_COMPARE(listview->count(), model.count());
4272
4273     item = findItem<QQuickItem>(contentItem, "wrapper", 1);
4274     QVERIFY(item);
4275     item->setHeight(0);
4276
4277     listview->setCurrentIndex(19);
4278     qApp->processEvents();
4279     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4280
4281     // items 0-2 should have been deleted
4282     for (int i=0; i<3; i++) {
4283         QTRY_VERIFY(!findItem<QQuickItem>(contentItem, "wrapper", i));
4284     }
4285
4286     delete testObject;
4287     delete window;
4288 }
4289
4290 void tst_QQuickListView::repositionResizedDelegate()
4291 {
4292     QFETCH(QQuickListView::Orientation, orientation);
4293     QFETCH(Qt::LayoutDirection, layoutDirection);
4294     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4295     QFETCH(QPointF, contentPos_itemFirstHalfVisible);
4296     QFETCH(QPointF, contentPos_itemSecondHalfVisible);
4297     QFETCH(QRectF, origPositionerRect);
4298     QFETCH(QRectF, resizedPositionerRect);
4299
4300     QQuickView *window = getView();
4301     QQmlContext *ctxt = window->rootContext();
4302     ctxt->setContextProperty("testHorizontal", orientation == QQuickListView::Horizontal);
4303     ctxt->setContextProperty("testRightToLeft", layoutDirection == Qt::RightToLeft);
4304     ctxt->setContextProperty("testBottomToTop", verticalLayoutDirection == QQuickListView::BottomToTop);
4305     window->setSource(testFileUrl("repositionResizedDelegate.qml"));
4306     window->show();
4307     qApp->processEvents();
4308
4309     QQuickListView *listview = qobject_cast<QQuickListView*>(window->rootObject());
4310     QTRY_VERIFY(listview != 0);
4311     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4312
4313     QQuickItem *positioner = findItem<QQuickItem>(window->rootObject(), "positioner");
4314     QVERIFY(positioner);
4315     QTRY_COMPARE(positioner->boundingRect().size(), origPositionerRect.size());
4316     QTRY_COMPARE(positioner->pos(), origPositionerRect.topLeft());
4317     QSignalSpy spy(listview, orientation == QQuickListView::Vertical ? SIGNAL(contentYChanged()) : SIGNAL(contentXChanged()));
4318     int prevSpyCount = 0;
4319
4320     // When an item is resized while it is partially visible, it should resize in the
4321     // direction of the content flow. If a RightToLeft or BottomToTop layout is used,
4322     // the item should also be re-positioned so its end position stays the same.
4323
4324     listview->setContentX(contentPos_itemFirstHalfVisible.x());
4325     listview->setContentY(contentPos_itemFirstHalfVisible.y());
4326     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4327     prevSpyCount = spy.count();
4328     QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "incrementRepeater"));
4329     QTRY_COMPARE(positioner->boundingRect().size(), resizedPositionerRect.size());
4330     QTRY_COMPARE(positioner->pos(), resizedPositionerRect.topLeft());
4331     QCOMPARE(listview->contentX(), contentPos_itemFirstHalfVisible.x());
4332     QCOMPARE(listview->contentY(), contentPos_itemFirstHalfVisible.y());
4333     QCOMPARE(spy.count(), prevSpyCount);
4334
4335     QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "decrementRepeater"));
4336     QTRY_COMPARE(positioner->boundingRect().size(), origPositionerRect.size());
4337     QTRY_COMPARE(positioner->pos(), origPositionerRect.topLeft());
4338     QCOMPARE(listview->contentX(), contentPos_itemFirstHalfVisible.x());
4339     QCOMPARE(listview->contentY(), contentPos_itemFirstHalfVisible.y());
4340
4341     listview->setContentX(contentPos_itemSecondHalfVisible.x());
4342     listview->setContentY(contentPos_itemSecondHalfVisible.y());
4343     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4344     prevSpyCount = spy.count();
4345
4346     QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "incrementRepeater"));
4347     positioner = findItem<QQuickItem>(window->rootObject(), "positioner");
4348     QTRY_COMPARE(positioner->boundingRect().size(), resizedPositionerRect.size());
4349     QTRY_COMPARE(positioner->pos(), resizedPositionerRect.topLeft());
4350     QCOMPARE(listview->contentX(), contentPos_itemSecondHalfVisible.x());
4351     QCOMPARE(listview->contentY(), contentPos_itemSecondHalfVisible.y());
4352     qApp->processEvents();
4353     QCOMPARE(spy.count(), prevSpyCount);
4354
4355     releaseView(window);
4356 }
4357
4358 void tst_QQuickListView::repositionResizedDelegate_data()
4359 {
4360     QTest::addColumn<QQuickListView::Orientation>("orientation");
4361     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
4362     QTest::addColumn<QQuickListView::VerticalLayoutDirection>("verticalLayoutDirection");
4363     QTest::addColumn<QPointF>("contentPos_itemFirstHalfVisible");
4364     QTest::addColumn<QPointF>("contentPos_itemSecondHalfVisible");
4365     QTest::addColumn<QRectF>("origPositionerRect");
4366     QTest::addColumn<QRectF>("resizedPositionerRect");
4367
4368     QTest::newRow("vertical")
4369             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4370             << QPointF(0, 60) << QPointF(0, 200 + 60)
4371             << QRectF(0, 200, 120, 120)
4372             << QRectF(0, 200, 120, 120 * 2);
4373
4374     QTest::newRow("vertical, BottomToTop")
4375             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4376             << QPointF(0, -200 - 60) << QPointF(0, -200 - 260)
4377             << QRectF(0, -200 - 120, 120, 120)
4378             << QRectF(0, -200 - 120*2, 120, 120 * 2);
4379
4380     QTest::newRow("horizontal")
4381             << QQuickListView::Horizontal<< Qt::LeftToRight << QQuickItemView::TopToBottom
4382             << QPointF(60, 0) << QPointF(260, 0)
4383             << QRectF(200, 0, 120, 120)
4384             << QRectF(200, 0, 120 * 2, 120);
4385
4386     QTest::newRow("horizontal, rtl")
4387             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4388             << QPointF(-200 - 60, 0) << QPointF(-200 - 260, 0)
4389             << QRectF(-200 - 120, 0, 120, 120)
4390             << QRectF(-200 - 120 * 2, 0, 120 * 2, 120);
4391 }
4392
4393 void tst_QQuickListView::QTBUG_16037()
4394 {
4395     QQuickView *window = createView();
4396     window->show();
4397
4398     window->setSource(testFileUrl("qtbug16037.qml"));
4399     qApp->processEvents();
4400
4401     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "listview");
4402     QTRY_VERIFY(listview != 0);
4403
4404     QVERIFY(listview->contentHeight() <= 0.0);
4405
4406     QMetaObject::invokeMethod(window->rootObject(), "setModel");
4407
4408     QTRY_COMPARE(listview->contentHeight(), 80.0);
4409
4410     delete window;
4411 }
4412
4413 void tst_QQuickListView::indexAt_itemAt_data()
4414 {
4415     QTest::addColumn<qreal>("x");
4416     QTest::addColumn<qreal>("y");
4417     QTest::addColumn<int>("index");
4418
4419     QTest::newRow("Item 0 - 0, 0") << 0. << 0. << 0;
4420     QTest::newRow("Item 0 - 0, 19") << 0. << 19. << 0;
4421     QTest::newRow("Item 0 - 239, 19") << 239. << 19. << 0;
4422     QTest::newRow("Item 1 - 0, 20") << 0. << 20. << 1;
4423     QTest::newRow("No Item - 240, 20") << 240. << 20. << -1;
4424 }
4425
4426 void tst_QQuickListView::indexAt_itemAt()
4427 {
4428     QFETCH(qreal, x);
4429     QFETCH(qreal, y);
4430     QFETCH(int, index);
4431
4432     QQuickView *window = getView();
4433
4434     QaimModel model;
4435     for (int i = 0; i < 30; i++)
4436         model.addItem("Item" + QString::number(i), "");
4437
4438     QQmlContext *ctxt = window->rootContext();
4439     ctxt->setContextProperty("testModel", &model);
4440
4441     TestObject *testObject = new TestObject;
4442     ctxt->setContextProperty("testObject", testObject);
4443
4444     window->setSource(testFileUrl("listviewtest.qml"));
4445     window->show();
4446     qApp->processEvents();
4447
4448     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4449     QTRY_VERIFY(listview != 0);
4450
4451     QQuickItem *contentItem = listview->contentItem();
4452     QTRY_VERIFY(contentItem != 0);
4453     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4454
4455     QQuickItem *item = 0;
4456     if (index >= 0) {
4457         item = findItem<QQuickItem>(contentItem, "wrapper", index);
4458         QVERIFY(item);
4459     }
4460     QCOMPARE(listview->indexAt(x,y), index);
4461     QVERIFY(listview->itemAt(x,y) == item);
4462
4463     releaseView(window);
4464     delete testObject;
4465 }
4466
4467 void tst_QQuickListView::incrementalModel()
4468 {
4469     QQuickView *window = createView();
4470
4471     IncrementalModel model;
4472     QQmlContext *ctxt = window->rootContext();
4473     ctxt->setContextProperty("testModel", &model);
4474
4475     window->setSource(testFileUrl("displaylist.qml"));
4476     qApp->processEvents();
4477
4478     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4479     QTRY_VERIFY(listview != 0);
4480
4481     QQuickItem *contentItem = listview->contentItem();
4482     QTRY_VERIFY(contentItem != 0);
4483
4484     QTRY_COMPARE(listview->count(), 20);
4485
4486     listview->positionViewAtIndex(10, QQuickListView::Beginning);
4487
4488     QTRY_COMPARE(listview->count(), 25);
4489
4490     delete window;
4491 }
4492
4493 void tst_QQuickListView::onAdd()
4494 {
4495     QFETCH(int, initialItemCount);
4496     QFETCH(int, itemsToAdd);
4497
4498     const int delegateHeight = 10;
4499     QaimModel model;
4500
4501     // these initial items should not trigger ListView.onAdd
4502     for (int i=0; i<initialItemCount; i++)
4503         model.addItem("dummy value", "dummy value");
4504
4505     QQuickView *window = createView();
4506     window->setGeometry(0,0,200, delegateHeight * (initialItemCount + itemsToAdd));
4507     QQmlContext *ctxt = window->rootContext();
4508     ctxt->setContextProperty("testModel", &model);
4509     ctxt->setContextProperty("delegateHeight", delegateHeight);
4510     window->setSource(testFileUrl("attachedSignals.qml"));
4511
4512     QObject *object = window->rootObject();
4513     object->setProperty("width", window->width());
4514     object->setProperty("height", window->height());
4515     qApp->processEvents();
4516
4517     QList<QPair<QString, QString> > items;
4518     for (int i=0; i<itemsToAdd; i++)
4519         items << qMakePair(QString("value %1").arg(i), QString::number(i));
4520     model.addItems(items);
4521     QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
4522
4523     QVariantList result = object->property("addedDelegates").toList();
4524     QCOMPARE(result.count(), items.count());
4525     for (int i=0; i<items.count(); i++)
4526         QCOMPARE(result[i].toString(), items[i].first);
4527
4528     delete window;
4529 }
4530
4531 void tst_QQuickListView::onAdd_data()
4532 {
4533     QTest::addColumn<int>("initialItemCount");
4534     QTest::addColumn<int>("itemsToAdd");
4535
4536     QTest::newRow("0, add 1") << 0 << 1;
4537     QTest::newRow("0, add 2") << 0 << 2;
4538     QTest::newRow("0, add 10") << 0 << 10;
4539
4540     QTest::newRow("1, add 1") << 1 << 1;
4541     QTest::newRow("1, add 2") << 1 << 2;
4542     QTest::newRow("1, add 10") << 1 << 10;
4543
4544     QTest::newRow("5, add 1") << 5 << 1;
4545     QTest::newRow("5, add 2") << 5 << 2;
4546     QTest::newRow("5, add 10") << 5 << 10;
4547 }
4548
4549 void tst_QQuickListView::onRemove()
4550 {
4551     QFETCH(int, initialItemCount);
4552     QFETCH(int, indexToRemove);
4553     QFETCH(int, removeCount);
4554
4555     const int delegateHeight = 10;
4556     QaimModel model;
4557     for (int i=0; i<initialItemCount; i++)
4558         model.addItem(QString("value %1").arg(i), "dummy value");
4559
4560     QQuickView *window = getView();
4561     QQmlContext *ctxt = window->rootContext();
4562     ctxt->setContextProperty("testModel", &model);
4563     ctxt->setContextProperty("delegateHeight", delegateHeight);
4564     window->setSource(testFileUrl("attachedSignals.qml"));
4565
4566     QObject *object = window->rootObject();
4567
4568     model.removeItems(indexToRemove, removeCount);
4569     QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
4570
4571     QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
4572
4573     releaseView(window);
4574 }
4575
4576 void tst_QQuickListView::onRemove_data()
4577 {
4578     QTest::addColumn<int>("initialItemCount");
4579     QTest::addColumn<int>("indexToRemove");
4580     QTest::addColumn<int>("removeCount");
4581
4582     QTest::newRow("remove first") << 1 << 0 << 1;
4583     QTest::newRow("two items, remove first") << 2 << 0 << 1;
4584     QTest::newRow("two items, remove last") << 2 << 1 << 1;
4585     QTest::newRow("two items, remove all") << 2 << 0 << 2;
4586
4587     QTest::newRow("four items, remove first") << 4 << 0 << 1;
4588     QTest::newRow("four items, remove 0-2") << 4 << 0 << 2;
4589     QTest::newRow("four items, remove 1-3") << 4 << 1 << 2;
4590     QTest::newRow("four items, remove 2-4") << 4 << 2 << 2;
4591     QTest::newRow("four items, remove last") << 4 << 3 << 1;
4592     QTest::newRow("four items, remove all") << 4 << 0 << 4;
4593
4594     QTest::newRow("ten items, remove 1-8") << 10 << 0 << 8;
4595     QTest::newRow("ten items, remove 2-7") << 10 << 2 << 5;
4596     QTest::newRow("ten items, remove 4-10") << 10 << 4 << 6;
4597 }
4598
4599 void tst_QQuickListView::rightToLeft()
4600 {
4601     QQuickView *window = createView();
4602     window->setGeometry(0,0,640,320);
4603     window->setSource(testFileUrl("rightToLeft.qml"));
4604     window->show();
4605     qApp->processEvents();
4606
4607     QVERIFY(window->rootObject() != 0);
4608     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "view");
4609     QTRY_VERIFY(listview != 0);
4610
4611     QQuickItem *contentItem = listview->contentItem();
4612     QTRY_VERIFY(contentItem != 0);
4613
4614     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4615
4616     QQuickVisualItemModel *model = window->rootObject()->findChild<QQuickVisualItemModel*>("itemModel");
4617     QTRY_VERIFY(model != 0);
4618
4619     QTRY_VERIFY(model->count() == 3);
4620     QTRY_COMPARE(listview->currentIndex(), 0);
4621
4622     // initial position at first item, right edge aligned
4623     QCOMPARE(listview->contentX(), -640.);
4624
4625     QQuickItem *item = findItem<QQuickItem>(contentItem, "item1");
4626     QTRY_VERIFY(item);
4627     QTRY_COMPARE(item->x(), -100.0);
4628     QCOMPARE(item->height(), listview->height());
4629
4630     QQuickText *text = findItem<QQuickText>(contentItem, "text1");
4631     QTRY_VERIFY(text);
4632     QTRY_COMPARE(text->text(), QLatin1String("index: 0"));
4633
4634     listview->setCurrentIndex(2);
4635
4636     item = findItem<QQuickItem>(contentItem, "item3");
4637     QTRY_VERIFY(item);
4638     QTRY_COMPARE(item->x(), -540.0);
4639
4640     text = findItem<QQuickText>(contentItem, "text3");
4641     QTRY_VERIFY(text);
4642     QTRY_COMPARE(text->text(), QLatin1String("index: 2"));
4643
4644     QCOMPARE(listview->contentX(), -640.);
4645
4646     // Ensure resizing maintains position relative to right edge
4647     qobject_cast<QQuickItem*>(window->rootObject())->setWidth(600);
4648     QTRY_COMPARE(listview->contentX(), -600.);
4649
4650     delete window;
4651 }
4652
4653 void tst_QQuickListView::test_mirroring()
4654 {
4655     QQuickView *windowA = createView();
4656     windowA->setSource(testFileUrl("rightToLeft.qml"));
4657     QQuickListView *listviewA = findItem<QQuickListView>(windowA->rootObject(), "view");
4658     QTRY_VERIFY(listviewA != 0);
4659
4660     QQuickView *windowB = createView();
4661     windowB->setSource(testFileUrl("rightToLeft.qml"));
4662     QQuickListView *listviewB = findItem<QQuickListView>(windowB->rootObject(), "view");
4663     QTRY_VERIFY(listviewA != 0);
4664     qApp->processEvents();
4665
4666     QList<QString> objectNames;
4667     objectNames << "item1" << "item2"; // << "item3"
4668
4669     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
4670     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
4671     QCOMPARE(listviewA->layoutDirection(), listviewA->effectiveLayoutDirection());
4672
4673     // LTR != RTL
4674     foreach (const QString objectName, objectNames)
4675         QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
4676
4677     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
4678     listviewB->setProperty("layoutDirection", Qt::LeftToRight);
4679
4680     // LTR == LTR
4681     foreach (const QString objectName, objectNames)
4682         QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
4683
4684     QVERIFY(listviewB->layoutDirection() == listviewB->effectiveLayoutDirection());
4685     QQuickItemPrivate::get(listviewB)->setLayoutMirror(true);
4686     QVERIFY(listviewB->layoutDirection() != listviewB->effectiveLayoutDirection());
4687
4688     // LTR != LTR+mirror
4689     foreach (const QString objectName, objectNames)
4690         QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
4691
4692     listviewA->setProperty("layoutDirection", Qt::RightToLeft);
4693
4694     // RTL == LTR+mirror
4695     foreach (const QString objectName, objectNames)
4696         QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
4697
4698     listviewB->setProperty("layoutDirection", Qt::RightToLeft);
4699
4700     // RTL != RTL+mirror
4701     foreach (const QString objectName, objectNames)
4702         QVERIFY(findItem<QQuickItem>(listviewA, objectName)->x() != findItem<QQuickItem>(listviewB, objectName)->x());
4703
4704     listviewA->setProperty("layoutDirection", Qt::LeftToRight);
4705
4706     // LTR == RTL+mirror
4707     foreach (const QString objectName, objectNames)
4708         QCOMPARE(findItem<QQuickItem>(listviewA, objectName)->x(), findItem<QQuickItem>(listviewB, objectName)->x());
4709
4710     delete windowA;
4711     delete windowB;
4712 }
4713
4714 void tst_QQuickListView::margins()
4715 {
4716     QQuickView *window = createView();
4717
4718     QaimModel model;
4719     for (int i = 0; i < 50; i++)
4720         model.addItem("Item" + QString::number(i), "");
4721
4722     QQmlContext *ctxt = window->rootContext();
4723     ctxt->setContextProperty("testModel", &model);
4724
4725     window->setSource(testFileUrl("margins.qml"));
4726     window->show();
4727     qApp->processEvents();
4728
4729     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4730     QTRY_VERIFY(listview != 0);
4731     QQuickItem *contentItem = listview->contentItem();
4732     QTRY_VERIFY(contentItem != 0);
4733     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4734
4735     QCOMPARE(listview->contentY(), -30.);
4736     QCOMPARE(listview->originY(), 0.);
4737
4738     // check end bound
4739     listview->positionViewAtEnd();
4740     qreal pos = listview->contentY();
4741     listview->setContentY(pos + 80);
4742     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4743     listview->returnToBounds();
4744     QTRY_COMPARE(listview->contentY(), pos + 50);
4745
4746     // remove item before visible and check that top margin is maintained
4747     // and originY is updated
4748     listview->setContentY(100);
4749     model.removeItem(1);
4750     QTRY_COMPARE(listview->count(), model.count());
4751     listview->setContentY(-50);
4752     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4753     listview->returnToBounds();
4754     QCOMPARE(listview->originY(), 20.);
4755     QTRY_COMPARE(listview->contentY(), -10.);
4756
4757     // reduce top margin
4758     listview->setTopMargin(20);
4759     QCOMPARE(listview->originY(), 20.);
4760     QTRY_COMPARE(listview->contentY(), 0.);
4761
4762     // check end bound
4763     listview->positionViewAtEnd();
4764     pos = listview->contentY();
4765     listview->setContentY(pos + 80);
4766     listview->returnToBounds();
4767     QTRY_COMPARE(listview->contentY(), pos + 50);
4768
4769     // reduce bottom margin
4770     pos = listview->contentY();
4771     listview->setBottomMargin(40);
4772     QCOMPARE(listview->originY(), 20.);
4773     QTRY_COMPARE(listview->contentY(), pos-10);
4774
4775     delete window;
4776 }
4777
4778 // QTBUG-24028
4779 void tst_QQuickListView::marginsResize()
4780 {
4781     QFETCH(QQuickListView::Orientation, orientation);
4782     QFETCH(Qt::LayoutDirection, layoutDirection);
4783     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4784     QFETCH(qreal, start);
4785     QFETCH(qreal, end);
4786
4787     QPoint flickStart(20, 20);
4788     QPoint flickEnd(20, 20);
4789     if (orientation == QQuickListView::Vertical)
4790         flickStart.ry() += (verticalLayoutDirection == QQuickItemView::TopToBottom) ? 180 : -180;
4791     else
4792         flickStart.rx() += (layoutDirection == Qt::LeftToRight) ? 180 : -180;
4793
4794     QQuickView *window = getView();
4795
4796     window->setSource(testFileUrl("margins2.qml"));
4797     window->show();
4798     qApp->processEvents();
4799
4800     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "listview");
4801     QTRY_VERIFY(listview != 0);
4802
4803     listview->setOrientation(orientation);
4804     listview->setLayoutDirection(layoutDirection);
4805     listview->setVerticalLayoutDirection(verticalLayoutDirection);
4806     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4807
4808     // view is resized after componentCompleted - top margin should still be visible
4809     if (orientation == QQuickListView::Vertical)
4810         QCOMPARE(listview->contentY(), start);
4811     else
4812         QCOMPARE(listview->contentX(), start);
4813
4814     // move to last index and ensure bottom margin is visible.
4815     listview->setCurrentIndex(19);
4816     if (orientation == QQuickListView::Vertical)
4817         QTRY_COMPARE(listview->contentY(), end);
4818     else
4819         QTRY_COMPARE(listview->contentX(), end);
4820
4821     // flick past the end and check content pos still settles on correct extents
4822     flick(window, flickStart, flickEnd, 180);
4823     QTRY_VERIFY(listview->isMoving() == false);
4824     if (orientation == QQuickListView::Vertical)
4825         QTRY_COMPARE(listview->contentY(), end);
4826     else
4827         QTRY_COMPARE(listview->contentX(), end);
4828
4829     // back to top - top margin should be visible.
4830     listview->setCurrentIndex(0);
4831     if (orientation == QQuickListView::Vertical)
4832         QTRY_COMPARE(listview->contentY(), start);
4833     else
4834         QTRY_COMPARE(listview->contentX(), start);
4835
4836     // flick past the beginning and check content pos still settles on correct extents
4837     flick(window, flickEnd, flickStart, 180);
4838     QTRY_VERIFY(listview->isMoving() == false);
4839     if (orientation == QQuickListView::Vertical)
4840         QTRY_COMPARE(listview->contentY(), start);
4841     else
4842         QTRY_COMPARE(listview->contentX(), start);
4843
4844     releaseView(window);
4845 }
4846
4847 void tst_QQuickListView::marginsResize_data()
4848 {
4849     QTest::addColumn<QQuickListView::Orientation>("orientation");
4850     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
4851     QTest::addColumn<QQuickListView::VerticalLayoutDirection>("verticalLayoutDirection");
4852     QTest::addColumn<qreal>("start");
4853     QTest::addColumn<qreal>("end");
4854
4855     // in Right to Left mode, leftMargin still means leftMargin - it doesn't reverse to mean rightMargin
4856
4857     QTest::newRow("vertical")
4858             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom
4859             << -40.0 << 1020.0;
4860
4861     QTest::newRow("vertical, BottomToTop")
4862             << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop
4863             << -180.0 << -1240.0;
4864
4865     QTest::newRow("horizontal")
4866             << QQuickListView::Horizontal<< Qt::LeftToRight << QQuickItemView::TopToBottom
4867             << -40.0 << 1020.0;
4868
4869     QTest::newRow("horizontal, rtl")
4870             << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom
4871             << -180.0 << -1240.0;
4872 }
4873
4874 void tst_QQuickListView::snapToItem_data()
4875 {
4876     QTest::addColumn<QQuickListView::Orientation>("orientation");
4877     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
4878     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
4879     QTest::addColumn<int>("highlightRangeMode");
4880     QTest::addColumn<QPoint>("flickStart");
4881     QTest::addColumn<QPoint>("flickEnd");
4882     QTest::addColumn<qreal>("snapAlignment");
4883     QTest::addColumn<qreal>("endExtent");
4884     QTest::addColumn<qreal>("startExtent");
4885
4886     QTest::newRow("vertical, top to bottom")
4887         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
4888         << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 560.0 << 0.0;
4889
4890     QTest::newRow("vertical, bottom to top")
4891         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::NoHighlightRange)
4892         << QPoint(20, 20) << QPoint(20, 200) << -60.0 << -560.0 - 240.0 << -240.0;
4893
4894     QTest::newRow("horizontal, left to right")
4895         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
4896         << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 560.0 << 0.0;
4897
4898     QTest::newRow("horizontal, right to left")
4899         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
4900         << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -560.0 - 240.0 << -240.0;
4901
4902     QTest::newRow("vertical, top to bottom, enforce range")
4903         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
4904         << QPoint(20, 200) << QPoint(20, 20) << 60.0 << 700.0 << -20.0;
4905
4906     QTest::newRow("vertical, bottom to top, enforce range")
4907         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::StrictlyEnforceRange)
4908         << QPoint(20, 20) << QPoint(20, 200) << -60.0 << -560.0 - 240.0 - 140.0 << -220.0;
4909
4910     QTest::newRow("horizontal, left to right, enforce range")
4911         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
4912         << QPoint(200, 20) << QPoint(20, 20) << 60.0 << 700.0 << -20.0;
4913
4914     QTest::newRow("horizontal, right to left, enforce range")
4915         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
4916         << QPoint(20, 20) << QPoint(200, 20) << -60.0 << -560.0 - 240.0 - 140.0 << -220.0;
4917 }
4918
4919 void tst_QQuickListView::snapToItem()
4920 {
4921     QFETCH(QQuickListView::Orientation, orientation);
4922     QFETCH(Qt::LayoutDirection, layoutDirection);
4923     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
4924     QFETCH(int, highlightRangeMode);
4925     QFETCH(QPoint, flickStart);
4926     QFETCH(QPoint, flickEnd);
4927     QFETCH(qreal, snapAlignment);
4928     QFETCH(qreal, endExtent);
4929     QFETCH(qreal, startExtent);
4930
4931     QQuickView *window = getView();
4932
4933     window->setSource(testFileUrl("snapToItem.qml"));
4934     window->show();
4935     qApp->processEvents();
4936
4937     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
4938     QTRY_VERIFY(listview != 0);
4939
4940     listview->setOrientation(orientation);
4941     listview->setLayoutDirection(layoutDirection);
4942     listview->setVerticalLayoutDirection(verticalLayoutDirection);
4943     listview->setHighlightRangeMode(QQuickItemView::HighlightRangeMode(highlightRangeMode));
4944     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
4945
4946     QQuickItem *contentItem = listview->contentItem();
4947     QTRY_VERIFY(contentItem != 0);
4948
4949     // confirm that a flick hits an item boundary
4950     flick(window, flickStart, flickEnd, 180);
4951     QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
4952     if (orientation == QQuickListView::Vertical)
4953         QCOMPARE(qreal(fmod(listview->contentY(),80.0)), snapAlignment);
4954     else
4955         QCOMPARE(qreal(fmod(listview->contentX(),80.0)), snapAlignment);
4956
4957     // flick to end
4958     do {
4959         flick(window, flickStart, flickEnd, 180);
4960         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
4961     } while (orientation == QQuickListView::Vertical
4962            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYEnd() : !listview->isAtYBeginning()
4963            : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
4964
4965     if (orientation == QQuickListView::Vertical)
4966         QCOMPARE(listview->contentY(), endExtent);
4967     else
4968         QCOMPARE(listview->contentX(), endExtent);
4969
4970     // flick to start
4971     do {
4972         flick(window, flickEnd, flickStart, 180);
4973         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
4974     } while (orientation == QQuickListView::Vertical
4975            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYBeginning() : !listview->isAtYEnd()
4976            : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
4977
4978     if (orientation == QQuickListView::Vertical)
4979         QCOMPARE(listview->contentY(), startExtent);
4980     else
4981         QCOMPARE(listview->contentX(), startExtent);
4982
4983     releaseView(window);
4984 }
4985
4986 void tst_QQuickListView::qAbstractItemModel_package_items()
4987 {
4988     items<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
4989 }
4990
4991 void tst_QQuickListView::qAbstractItemModel_items()
4992 {
4993     items<QaimModel>(testFileUrl("listviewtest.qml"), false);
4994 }
4995
4996 void tst_QQuickListView::qAbstractItemModel_package_changed()
4997 {
4998     changed<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
4999 }
5000
5001 void tst_QQuickListView::qAbstractItemModel_changed()
5002 {
5003     changed<QaimModel>(testFileUrl("listviewtest.qml"), false);
5004 }
5005
5006 void tst_QQuickListView::qAbstractItemModel_package_inserted()
5007 {
5008     inserted<QaimModel>(testFileUrl("listviewtest-package.qml"));
5009 }
5010
5011 void tst_QQuickListView::qAbstractItemModel_inserted()
5012 {
5013     inserted<QaimModel>(testFileUrl("listviewtest.qml"));
5014 }
5015
5016 void tst_QQuickListView::qAbstractItemModel_inserted_more()
5017 {
5018     inserted_more<QaimModel>();
5019 }
5020
5021 void tst_QQuickListView::qAbstractItemModel_inserted_more_data()
5022 {
5023     inserted_more_data();
5024 }
5025
5026 void tst_QQuickListView::qAbstractItemModel_inserted_more_bottomToTop()
5027 {
5028     inserted_more<QaimModel>(QQuickItemView::BottomToTop);
5029 }
5030
5031 void tst_QQuickListView::qAbstractItemModel_inserted_more_bottomToTop_data()
5032 {
5033     inserted_more_data();
5034 }
5035
5036 void tst_QQuickListView::qAbstractItemModel_package_removed()
5037 {
5038     removed<QaimModel>(testFileUrl("listviewtest-package.qml"), false);
5039     removed<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
5040 }
5041
5042 void tst_QQuickListView::qAbstractItemModel_removed()
5043 {
5044     removed<QaimModel>(testFileUrl("listviewtest.qml"), false);
5045     removed<QaimModel>(testFileUrl("listviewtest.qml"), true);
5046 }
5047
5048 void tst_QQuickListView::qAbstractItemModel_removed_more()
5049 {
5050     removed_more<QaimModel>(testFileUrl("listviewtest.qml"));
5051 }
5052
5053 void tst_QQuickListView::qAbstractItemModel_removed_more_data()
5054 {
5055     removed_more_data();
5056 }
5057
5058 void tst_QQuickListView::qAbstractItemModel_removed_more_bottomToTop()
5059 {
5060     removed_more<QaimModel>(testFileUrl("listviewtest.qml"), QQuickItemView::BottomToTop);
5061 }
5062
5063 void tst_QQuickListView::qAbstractItemModel_removed_more_bottomToTop_data()
5064 {
5065     removed_more_data();
5066 }
5067
5068 void tst_QQuickListView::qAbstractItemModel_package_moved()
5069 {
5070     moved<QaimModel>(testFileUrl("listviewtest-package.qml"));
5071 }
5072
5073 void tst_QQuickListView::qAbstractItemModel_package_moved_data()
5074 {
5075     moved_data();
5076 }
5077
5078 void tst_QQuickListView::qAbstractItemModel_moved()
5079 {
5080     moved<QaimModel>(testFileUrl("listviewtest.qml"));
5081 }
5082
5083 void tst_QQuickListView::qAbstractItemModel_moved_data()
5084 {
5085     moved_data();
5086 }
5087
5088 void tst_QQuickListView::qAbstractItemModel_moved_bottomToTop()
5089 {
5090     moved<QaimModel>(testFileUrl("listviewtest-package.qml"), QQuickItemView::BottomToTop);
5091 }
5092
5093 void tst_QQuickListView::qAbstractItemModel_moved_bottomToTop_data()
5094 {
5095     moved_data();
5096 }
5097
5098 void tst_QQuickListView::qAbstractItemModel_package_clear()
5099 {
5100     clear<QaimModel>(testFileUrl("listviewtest-package.qml"));
5101 }
5102
5103 void tst_QQuickListView::qAbstractItemModel_clear()
5104 {
5105     clear<QaimModel>(testFileUrl("listviewtest.qml"));
5106 }
5107
5108 void tst_QQuickListView::qAbstractItemModel_clear_bottomToTop()
5109 {
5110     clear<QaimModel>(testFileUrl("listviewtest.qml"), QQuickItemView::BottomToTop);
5111 }
5112
5113 void tst_QQuickListView::qAbstractItemModel_package_sections()
5114 {
5115     sections<QaimModel>(testFileUrl("listview-sections-package.qml"));
5116 }
5117
5118 void tst_QQuickListView::qAbstractItemModel_sections()
5119 {
5120     sections<QaimModel>(testFileUrl("listview-sections.qml"));
5121 }
5122
5123 void tst_QQuickListView::creationContext()
5124 {
5125     QQuickView window;
5126     window.setGeometry(0,0,240,320);
5127     window.setSource(testFileUrl("creationContext.qml"));
5128     qApp->processEvents();
5129
5130     QQuickItem *rootItem = qobject_cast<QQuickItem *>(window.rootObject());
5131     QVERIFY(rootItem);
5132     QVERIFY(rootItem->property("count").toInt() > 0);
5133
5134     QQuickItem *item;
5135     QVERIFY(item = findItem<QQuickItem>(rootItem, "listItem"));
5136     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5137     QVERIFY(item = rootItem->findChild<QQuickItem *>("header"));
5138     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5139     QVERIFY(item = rootItem->findChild<QQuickItem *>("footer"));
5140     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5141     QVERIFY(item = rootItem->findChild<QQuickItem *>("section"));
5142     QCOMPARE(item->property("text").toString(), QString("Hello!"));
5143 }
5144
5145 void tst_QQuickListView::QTBUG_21742()
5146 {
5147     QQuickView window;
5148     window.setGeometry(0,0,200,200);
5149     window.setSource(testFileUrl("qtbug-21742.qml"));
5150     qApp->processEvents();
5151
5152     QQuickItem *rootItem = qobject_cast<QQuickItem *>(window.rootObject());
5153     QVERIFY(rootItem);
5154     QCOMPARE(rootItem->property("count").toInt(), 1);
5155 }
5156
5157 void tst_QQuickListView::asynchronous()
5158 {
5159     QQuickView *window = createView();
5160     window->show();
5161     QQmlIncubationController controller;
5162     window->engine()->setIncubationController(&controller);
5163
5164     window->setSource(testFileUrl("asyncloader.qml"));
5165
5166     QQuickItem *rootObject = qobject_cast<QQuickItem*>(window->rootObject());
5167     QVERIFY(rootObject);
5168
5169     QQuickListView *listview = 0;
5170     while (!listview) {
5171         bool b = false;
5172         controller.incubateWhile(&b);
5173         listview = rootObject->findChild<QQuickListView*>("view");
5174     }
5175
5176     // items will be created one at a time
5177     for (int i = 0; i < 8; ++i) {
5178         QVERIFY(findItem<QQuickItem>(listview, "wrapper", i) == 0);
5179         QQuickItem *item = 0;
5180         while (!item) {
5181             bool b = false;
5182             controller.incubateWhile(&b);
5183             item = findItem<QQuickItem>(listview, "wrapper", i);
5184         }
5185     }
5186
5187     {
5188         bool b = true;
5189         controller.incubateWhile(&b);
5190     }
5191
5192     // verify positioning
5193     QQuickItem *contentItem = listview->contentItem();
5194     for (int i = 0; i < 8; ++i) {
5195         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5196         QTRY_COMPARE(item->y(), i*50.0);
5197     }
5198
5199     delete window;
5200 }
5201
5202 void tst_QQuickListView::snapOneItem_data()
5203 {
5204     QTest::addColumn<QQuickListView::Orientation>("orientation");
5205     QTest::addColumn<Qt::LayoutDirection>("layoutDirection");
5206     QTest::addColumn<QQuickItemView::VerticalLayoutDirection>("verticalLayoutDirection");
5207     QTest::addColumn<int>("highlightRangeMode");
5208     QTest::addColumn<QPoint>("flickStart");
5209     QTest::addColumn<QPoint>("flickEnd");
5210     QTest::addColumn<qreal>("snapAlignment");
5211     QTest::addColumn<qreal>("endExtent");
5212     QTest::addColumn<qreal>("startExtent");
5213
5214     QTest::newRow("vertical, top to bottom")
5215         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5216         << QPoint(20, 200) << QPoint(20, 20) << 180.0 << 560.0 << 0.0;
5217
5218     QTest::newRow("vertical, bottom to top")
5219         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::NoHighlightRange)
5220         << QPoint(20, 20) << QPoint(20, 200) << -420.0 << -560.0 - 240.0 << -240.0;
5221
5222     QTest::newRow("horizontal, left to right")
5223         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5224         << QPoint(200, 20) << QPoint(20, 20) << 180.0 << 560.0 << 0.0;
5225
5226     QTest::newRow("horizontal, right to left")
5227         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::NoHighlightRange)
5228         << QPoint(20, 20) << QPoint(200, 20) << -420.0 << -560.0 - 240.0 << -240.0;
5229
5230     QTest::newRow("vertical, top to bottom, enforce range")
5231         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5232         << QPoint(20, 200) << QPoint(20, 20) << 180.0 << 580.0 << -20.0;
5233
5234     QTest::newRow("vertical, bottom to top, enforce range")
5235         << QQuickListView::Vertical << Qt::LeftToRight << QQuickItemView::BottomToTop << int(QQuickItemView::StrictlyEnforceRange)
5236         << QPoint(20, 20) << QPoint(20, 200) << -420.0 << -580.0 - 240.0 << -220.0;
5237
5238     QTest::newRow("horizontal, left to right, enforce range")
5239         << QQuickListView::Horizontal << Qt::LeftToRight << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5240         << QPoint(200, 20) << QPoint(20, 20) << 180.0 << 580.0 << -20.0;
5241
5242     QTest::newRow("horizontal, right to left, enforce range")
5243         << QQuickListView::Horizontal << Qt::RightToLeft << QQuickItemView::TopToBottom << int(QQuickItemView::StrictlyEnforceRange)
5244         << QPoint(20, 20) << QPoint(200, 20) << -420.0 << -580.0 - 240.0 << -220.0;
5245 }
5246
5247 void tst_QQuickListView::snapOneItem()
5248 {
5249     QFETCH(QQuickListView::Orientation, orientation);
5250     QFETCH(Qt::LayoutDirection, layoutDirection);
5251     QFETCH(QQuickItemView::VerticalLayoutDirection, verticalLayoutDirection);
5252     QFETCH(int, highlightRangeMode);
5253     QFETCH(QPoint, flickStart);
5254     QFETCH(QPoint, flickEnd);
5255     QFETCH(qreal, snapAlignment);
5256     QFETCH(qreal, endExtent);
5257     QFETCH(qreal, startExtent);
5258
5259     QQuickView *window = getView();
5260
5261     window->setSource(testFileUrl("snapOneItem.qml"));
5262     window->show();
5263     qApp->processEvents();
5264
5265     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
5266     QTRY_VERIFY(listview != 0);
5267
5268     listview->setOrientation(orientation);
5269     listview->setLayoutDirection(layoutDirection);
5270     listview->setVerticalLayoutDirection(verticalLayoutDirection);
5271     listview->setHighlightRangeMode(QQuickItemView::HighlightRangeMode(highlightRangeMode));
5272     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5273
5274     QQuickItem *contentItem = listview->contentItem();
5275     QTRY_VERIFY(contentItem != 0);
5276
5277     QSignalSpy currentIndexSpy(listview, SIGNAL(currentIndexChanged()));
5278
5279     // confirm that a flick hits the next item boundary
5280     flick(window, flickStart, flickEnd, 180);
5281     QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5282     if (orientation == QQuickListView::Vertical)
5283         QCOMPARE(listview->contentY(), snapAlignment);
5284     else
5285         QCOMPARE(listview->contentX(), snapAlignment);
5286
5287     if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
5288         QCOMPARE(listview->currentIndex(), 1);
5289         QCOMPARE(currentIndexSpy.count(), 1);
5290     }
5291
5292     // flick to end
5293     do {
5294         flick(window, flickStart, flickEnd, 180);
5295         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5296     } while (orientation == QQuickListView::Vertical
5297            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYEnd() : !listview->isAtYBeginning()
5298            : layoutDirection == Qt::LeftToRight ? !listview->isAtXEnd() : !listview->isAtXBeginning());
5299
5300     if (orientation == QQuickListView::Vertical)
5301         QCOMPARE(listview->contentY(), endExtent);
5302     else
5303         QCOMPARE(listview->contentX(), endExtent);
5304
5305     if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
5306         QCOMPARE(listview->currentIndex(), 3);
5307         QCOMPARE(currentIndexSpy.count(), 3);
5308     }
5309
5310     // flick to start
5311     do {
5312         flick(window, flickEnd, flickStart, 180);
5313         QTRY_VERIFY(listview->isMoving() == false); // wait until it stops
5314     } while (orientation == QQuickListView::Vertical
5315            ? verticalLayoutDirection == QQuickItemView::TopToBottom ? !listview->isAtYBeginning() : !listview->isAtYEnd()
5316            : layoutDirection == Qt::LeftToRight ? !listview->isAtXBeginning() : !listview->isAtXEnd());
5317
5318     if (orientation == QQuickListView::Vertical)
5319         QCOMPARE(listview->contentY(), startExtent);
5320     else
5321         QCOMPARE(listview->contentX(), startExtent);
5322
5323     if (QQuickItemView::HighlightRangeMode(highlightRangeMode) == QQuickItemView::StrictlyEnforceRange) {
5324         QCOMPARE(listview->currentIndex(), 0);
5325         QCOMPARE(currentIndexSpy.count(), 6);
5326     }
5327
5328     releaseView(window);
5329 }
5330
5331 void tst_QQuickListView::unrequestedVisibility()
5332 {
5333     QaimModel model;
5334     for (int i = 0; i < 30; i++)
5335         model.addItem("Item" + QString::number(i), QString::number(i));
5336
5337     QQuickView *window = new QQuickView(0);
5338     window->setGeometry(0,0,240,320);
5339
5340     QQmlContext *ctxt = window->rootContext();
5341     ctxt->setContextProperty("testModel", &model);
5342     ctxt->setContextProperty("testWrap", QVariant(false));
5343
5344     window->setSource(testFileUrl("unrequestedItems.qml"));
5345     window->show();
5346     qApp->processEvents();
5347
5348     QQuickListView *leftview = findItem<QQuickListView>(window->rootObject(), "leftList");
5349     QTRY_VERIFY(leftview != 0);
5350
5351     QQuickListView *rightview = findItem<QQuickListView>(window->rootObject(), "rightList");
5352     QTRY_VERIFY(rightview != 0);
5353
5354     QQuickItem *leftContent = leftview->contentItem();
5355     QTRY_VERIFY(leftContent != 0);
5356
5357     QQuickItem *rightContent = rightview->contentItem();
5358     QTRY_VERIFY(rightContent != 0);
5359
5360     rightview->setCurrentIndex(20);
5361
5362     QTRY_COMPARE(leftview->contentY(), 0.0);
5363     QTRY_COMPARE(rightview->contentY(), 100.0);
5364
5365     QQuickItem *item;
5366
5367     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5368     QCOMPARE(delegateVisible(item), true);
5369     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5370     QCOMPARE(delegateVisible(item), false);
5371
5372     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 19));
5373     QCOMPARE(delegateVisible(item), false);
5374     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 19));
5375     QCOMPARE(delegateVisible(item), true);
5376
5377     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 16));
5378     QCOMPARE(delegateVisible(item), true);
5379     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 17));
5380     QCOMPARE(delegateVisible(item), false);
5381     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 3));
5382     QCOMPARE(delegateVisible(item), false);
5383     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 4));
5384     QCOMPARE(delegateVisible(item), true);
5385
5386     rightview->setCurrentIndex(0);
5387
5388     QTRY_COMPARE(leftview->contentY(), 0.0);
5389     QTRY_COMPARE(rightview->contentY(), 0.0);
5390
5391     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5392     QCOMPARE(delegateVisible(item), true);
5393     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5394     QTRY_COMPARE(delegateVisible(item), true);
5395
5396     QVERIFY(!findItem<QQuickItem>(leftContent, "wrapper", 19));
5397     QVERIFY(!findItem<QQuickItem>(rightContent, "wrapper", 19));
5398
5399     leftview->setCurrentIndex(20);
5400
5401     QTRY_COMPARE(leftview->contentY(), 100.0);
5402     QTRY_COMPARE(rightview->contentY(), 0.0);
5403
5404     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5405     QTRY_COMPARE(delegateVisible(item), false);
5406     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5407     QCOMPARE(delegateVisible(item), true);
5408
5409     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 19));
5410     QCOMPARE(delegateVisible(item), true);
5411     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 19));
5412     QCOMPARE(delegateVisible(item), false);
5413
5414     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 3));
5415     QCOMPARE(delegateVisible(item), false);
5416     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5417     QCOMPARE(delegateVisible(item), true);
5418     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5419     QCOMPARE(delegateVisible(item), true);
5420     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5421     QCOMPARE(delegateVisible(item), false);
5422
5423     model.moveItems(19, 1, 1);
5424     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5425
5426     QTRY_VERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 1));
5427     QCOMPARE(delegateVisible(item), false);
5428     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 1));
5429     QCOMPARE(delegateVisible(item), true);
5430
5431     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 19));
5432     QCOMPARE(delegateVisible(item), true);
5433     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 19));
5434     QCOMPARE(delegateVisible(item), false);
5435
5436     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5437     QCOMPARE(delegateVisible(item), false);
5438     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5439     QCOMPARE(delegateVisible(item), true);
5440     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5441     QCOMPARE(delegateVisible(item), true);
5442     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5443     QCOMPARE(delegateVisible(item), false);
5444
5445     model.moveItems(3, 4, 1);
5446     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5447
5448     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5449     QCOMPARE(delegateVisible(item), false);
5450     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5451     QCOMPARE(delegateVisible(item), true);
5452     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5453     QCOMPARE(delegateVisible(item), true);
5454     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5455     QCOMPARE(delegateVisible(item), false);
5456
5457     model.moveItems(4, 3, 1);
5458     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5459
5460     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5461     QCOMPARE(delegateVisible(item), false);
5462     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5463     QCOMPARE(delegateVisible(item), true);
5464     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5465     QCOMPARE(delegateVisible(item), true);
5466     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5467     QCOMPARE(delegateVisible(item), false);
5468
5469     model.moveItems(16, 17, 1);
5470     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5471
5472     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5473     QCOMPARE(delegateVisible(item), false);
5474     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5475     QCOMPARE(delegateVisible(item), true);
5476     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5477     QCOMPARE(delegateVisible(item), true);
5478     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5479     QCOMPARE(delegateVisible(item), false);
5480
5481     model.moveItems(17, 16, 1);
5482     QTRY_COMPARE(QQuickItemPrivate::get(leftview)->polishScheduled, false);
5483
5484     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 4));
5485     QCOMPARE(delegateVisible(item), false);
5486     QVERIFY(item = findItem<QQuickItem>(leftContent, "wrapper", 5));
5487     QCOMPARE(delegateVisible(item), true);
5488     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 16));
5489     QCOMPARE(delegateVisible(item), true);
5490     QVERIFY(item = findItem<QQuickItem>(rightContent, "wrapper", 17));
5491     QCOMPARE(delegateVisible(item), false);
5492
5493     delete window;
5494 }
5495
5496 void tst_QQuickListView::populateTransitions()
5497 {
5498     QFETCH(bool, staticallyPopulate);
5499     QFETCH(bool, dynamicallyPopulate);
5500     QFETCH(bool, usePopulateTransition);
5501
5502     QPointF transitionFrom(-50, -50);
5503     QPointF transitionVia(100, 100);
5504     QaimModel model_transitionFrom;
5505     QaimModel model_transitionVia;
5506
5507     QaimModel model;
5508     if (staticallyPopulate) {
5509         for (int i = 0; i < 30; i++)
5510             model.addItem("item" + QString::number(i), "");
5511     }
5512
5513     QQuickView *window = getView();
5514     window->rootContext()->setContextProperty("testModel", &model);
5515     window->rootContext()->setContextProperty("testObject", new TestObject(window->rootContext()));
5516     window->rootContext()->setContextProperty("usePopulateTransition", usePopulateTransition);
5517     window->rootContext()->setContextProperty("dynamicallyPopulate", dynamicallyPopulate);
5518     window->rootContext()->setContextProperty("transitionFrom", transitionFrom);
5519     window->rootContext()->setContextProperty("transitionVia", transitionVia);
5520     window->rootContext()->setContextProperty("model_transitionFrom", &model_transitionFrom);
5521     window->rootContext()->setContextProperty("model_transitionVia", &model_transitionVia);
5522     window->setSource(testFileUrl("populateTransitions.qml"));
5523     window->show();
5524     QVERIFY(QTest::qWaitForWindowExposed(window));
5525
5526     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
5527     QVERIFY(listview);
5528     QQuickItem *contentItem = listview->contentItem();
5529     QVERIFY(contentItem);
5530
5531     if (staticallyPopulate && usePopulateTransition) {
5532         QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 16);
5533         QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
5534     } else if (dynamicallyPopulate) {
5535         QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
5536         QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 16);
5537     } else {
5538         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5539         QCOMPARE(listview->property("countPopulateTransitions").toInt(), 0);
5540         QCOMPARE(listview->property("countAddTransitions").toInt(), 0);
5541     }
5542
5543     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5544     for (int i=0; i < model.count() && i < itemCount; ++i) {
5545         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5546         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5547         QTRY_COMPARE(item->x(), 0.0);
5548         QTRY_COMPARE(item->y(), i*20.0);
5549         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5550         QVERIFY(name != 0);
5551         QTRY_COMPARE(name->text(), model.name(i));
5552     }
5553
5554     listview->setProperty("countPopulateTransitions", 0);
5555     listview->setProperty("countAddTransitions", 0);
5556
5557     // add an item and check this is done with add transition, not populate
5558     model.insertItem(0, "another item", "");
5559     QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 1);
5560     QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), 0);
5561
5562     // clear the model
5563     window->rootContext()->setContextProperty("testModel", QVariant());
5564     QTRY_COMPARE(listview->count(), 0);
5565     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
5566     listview->setProperty("countPopulateTransitions", 0);
5567     listview->setProperty("countAddTransitions", 0);
5568
5569     // set to a valid model and check populate transition is run a second time
5570     model.clear();
5571     for (int i = 0; i < 30; i++)
5572         model.addItem("item" + QString::number(i), "");
5573     window->rootContext()->setContextProperty("testModel", &model);
5574     QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 16 : 0);
5575     QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
5576
5577     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5578     for (int i=0; i < model.count() && i < itemCount; ++i) {
5579         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5580         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5581         QTRY_COMPARE(item->x(), 0.0);
5582         QTRY_COMPARE(item->y(), i*20.0);
5583         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5584         QVERIFY(name != 0);
5585         QTRY_COMPARE(name->text(), model.name(i));
5586     }
5587
5588     // reset model and check populate transition is run again
5589     listview->setProperty("countPopulateTransitions", 0);
5590     listview->setProperty("countAddTransitions", 0);
5591     model.reset();
5592     QTRY_COMPARE(listview->property("countPopulateTransitions").toInt(), usePopulateTransition ? 16 : 0);
5593     QTRY_COMPARE(listview->property("countAddTransitions").toInt(), 0);
5594
5595     itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5596     for (int i=0; i < model.count() && i < itemCount; ++i) {
5597         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5598         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5599         QTRY_COMPARE(item->x(), 0.0);
5600         QTRY_COMPARE(item->y(), i*20.0);
5601         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5602         QVERIFY(name != 0);
5603         QTRY_COMPARE(name->text(), model.name(i));
5604     }
5605
5606     releaseView(window);
5607 }
5608
5609 void tst_QQuickListView::populateTransitions_data()
5610 {
5611     QTest::addColumn<bool>("staticallyPopulate");
5612     QTest::addColumn<bool>("dynamicallyPopulate");
5613     QTest::addColumn<bool>("usePopulateTransition");
5614
5615     QTest::newRow("static") << true << false << true;
5616     QTest::newRow("static, no populate") << true << false << false;
5617
5618     QTest::newRow("dynamic") << false << true << true;
5619     QTest::newRow("dynamic, no populate") << false << true << false;
5620
5621     QTest::newRow("empty to start with") << false << false << true;
5622     QTest::newRow("empty to start with, no populate") << false << false << false;
5623 }
5624
5625 void tst_QQuickListView::addTransitions()
5626 {
5627     QFETCH(int, initialItemCount);
5628     QFETCH(bool, shouldAnimateTargets);
5629     QFETCH(qreal, contentY);
5630     QFETCH(int, insertionIndex);
5631     QFETCH(int, insertionCount);
5632     QFETCH(ListRange, expectedDisplacedIndexes);
5633
5634     // added items should start here
5635     QPointF targetItems_transitionFrom(-50, -50);
5636
5637     // displaced items should pass through this point
5638     QPointF displacedItems_transitionVia(100, 100);
5639
5640     QaimModel model;
5641     for (int i = 0; i < initialItemCount; i++)
5642         model.addItem("Original item" + QString::number(i), "");
5643     QaimModel model_targetItems_transitionFrom;
5644     QaimModel model_displacedItems_transitionVia;
5645
5646     QQuickView *window = getView();
5647     QQmlContext *ctxt = window->rootContext();
5648     TestObject *testObject = new TestObject;
5649     ctxt->setContextProperty("testModel", &model);
5650     ctxt->setContextProperty("model_targetItems_transitionFrom", &model_targetItems_transitionFrom);
5651     ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
5652     ctxt->setContextProperty("targetItems_transitionFrom", targetItems_transitionFrom);
5653     ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
5654     ctxt->setContextProperty("testObject", testObject);
5655     window->setSource(testFileUrl("addTransitions.qml"));
5656     window->show();
5657     QVERIFY(QTest::qWaitForWindowExposed(window));
5658
5659     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
5660     QTRY_VERIFY(listview != 0);
5661     QQuickItem *contentItem = listview->contentItem();
5662     QVERIFY(contentItem != 0);
5663     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5664
5665     if (contentY != 0) {
5666         listview->setContentY(contentY);
5667         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5668     }
5669
5670     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
5671
5672     // only target items that will become visible should be animated
5673     QList<QPair<QString, QString> > newData;
5674     QList<QPair<QString, QString> > expectedTargetData;
5675     QList<int> targetIndexes;
5676     if (shouldAnimateTargets) {
5677         for (int i=insertionIndex; i<insertionIndex+insertionCount; i++) {
5678             newData << qMakePair(QString("New item %1").arg(i), QString(""));
5679
5680             if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {  // only grab visible items
5681                 expectedTargetData << newData.last();
5682                 targetIndexes << i;
5683             }
5684         }
5685         QVERIFY(expectedTargetData.count() > 0);
5686     }
5687
5688     // start animation
5689     if (!newData.isEmpty()) {
5690         model.insertItems(insertionIndex, newData);
5691         QTRY_COMPARE(model.count(), listview->count());
5692     }
5693
5694     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
5695
5696     if (shouldAnimateTargets) {
5697         QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
5698         QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
5699                      expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
5700
5701         // check the target and displaced items were animated
5702         model_targetItems_transitionFrom.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
5703         model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
5704
5705         // check attached properties
5706         matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
5707         matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
5708         matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
5709         if (expectedDisplacedIndexes.isValid()) {
5710             // adjust expectedDisplacedIndexes to their final values after the move
5711             QList<int> displacedIndexes = adjustIndexesForAddDisplaced(expectedDisplacedIndexes.indexes, insertionIndex, insertionCount);
5712             matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
5713             matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
5714             matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
5715         }
5716
5717     } else {
5718         QTRY_COMPARE(model_targetItems_transitionFrom.count(), 0);
5719         QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
5720     }
5721
5722     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
5723     int firstVisibleIndex = -1;
5724     int itemCount = items.count();
5725     for (int i=0; i<items.count(); i++) {
5726         if (items[i]->y() >= contentY) {
5727             QQmlExpression e(qmlContext(items[i]), items[i], "index");
5728             firstVisibleIndex = e.evaluate().toInt();
5729             break;
5730         }
5731     }
5732     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
5733
5734     // verify all items moved to the correct final positions
5735     for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
5736         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5737         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5738         QTRY_COMPARE(item->y(), i*20.0);
5739         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
5740         QVERIFY(name != 0);
5741         QTRY_COMPARE(name->text(), model.name(i));
5742     }
5743
5744     releaseView(window);
5745     delete testObject;
5746 }
5747
5748 void tst_QQuickListView::addTransitions_data()
5749 {
5750     QTest::addColumn<int>("initialItemCount");
5751     QTest::addColumn<qreal>("contentY");
5752     QTest::addColumn<bool>("shouldAnimateTargets");
5753     QTest::addColumn<int>("insertionIndex");
5754     QTest::addColumn<int>("insertionCount");
5755     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
5756
5757     // if inserting before visible index, items should not appear or animate in, even if there are > 1 new items
5758     QTest::newRow("insert 1, just before start")
5759             << 30 << 20.0 << false
5760             << 0 << 1 << ListRange();
5761     QTest::newRow("insert 1, way before start")
5762             << 30 << 20.0 << false
5763             << 0 << 1 << ListRange();
5764     QTest::newRow("insert multiple, just before start")
5765             << 30 << 100.0 << false
5766             << 0 << 3 << ListRange();
5767     QTest::newRow("insert multiple, way before start")
5768             << 30 << 100.0 << false
5769             << 0 << 3 << ListRange();
5770
5771     QTest::newRow("insert 1 at start")
5772             << 30 << 0.0 << true
5773             << 0 << 1 << ListRange(0, 15);
5774     QTest::newRow("insert multiple at start")
5775             << 30 << 0.0 << true
5776             << 0 << 3 << ListRange(0, 15);
5777     QTest::newRow("insert 1 at start, content y not 0")
5778             << 30 << 40.0 << true  // first visible is index 2, so translate the displaced indexes by 2
5779             << 2 << 1 << ListRange(0 + 2, 15 + 2);
5780     QTest::newRow("insert multiple at start, content y not 0")
5781             << 30 << 40.0 << true    // first visible is index 2
5782             << 2 << 3 << ListRange(0 + 2, 15 + 2);
5783
5784     QTest::newRow("insert 1 at start, to empty list")
5785             << 0 << 0.0 << true
5786             << 0 << 1 << ListRange();
5787     QTest::newRow("insert multiple at start, to empty list")
5788             << 0 << 0.0 << true
5789             << 0 << 3 << ListRange();
5790
5791     QTest::newRow("insert 1 at middle")
5792             << 30 << 0.0 << true
5793             << 5 << 1 << ListRange(5, 15);
5794     QTest::newRow("insert multiple at middle")
5795             << 30 << 0.0 << true
5796             << 5 << 3 << ListRange(5, 15);
5797
5798     QTest::newRow("insert 1 at bottom")
5799             << 30 << 0.0 << true
5800             << 15 << 1 << ListRange(15, 15);
5801     QTest::newRow("insert multiple at bottom")
5802             << 30 << 0.0 << true
5803             << 15 << 3 << ListRange(15, 15);
5804     QTest::newRow("insert 1 at bottom, content y not 0")
5805             << 30 << 20.0 * 3 << true
5806             << 15 + 3 << 1 << ListRange(15 + 3, 15 + 3);
5807     QTest::newRow("insert multiple at bottom, content y not 0")
5808             << 30 << 20.0 * 3 << true
5809             << 15 + 3 << 3 << ListRange(15 + 3, 15 + 3);
5810
5811     // items added after the last visible will not be animated in, since they
5812     // do not appear in the final view
5813     QTest::newRow("insert 1 after end")
5814             << 30 << 0.0 << false
5815             << 17 << 1 << ListRange();
5816     QTest::newRow("insert multiple after end")
5817             << 30 << 0.0 << false
5818             << 17 << 3 << ListRange();
5819 }
5820
5821 void tst_QQuickListView::moveTransitions()
5822 {
5823     QFETCH(int, initialItemCount);
5824     QFETCH(qreal, contentY);
5825     QFETCH(qreal, itemsOffsetAfterMove);
5826     QFETCH(int, moveFrom);
5827     QFETCH(int, moveTo);
5828     QFETCH(int, moveCount);
5829     QFETCH(ListRange, expectedDisplacedIndexes);
5830
5831     // target and displaced items should pass through these points
5832     QPointF targetItems_transitionVia(-50, 50);
5833     QPointF displacedItems_transitionVia(100, 100);
5834
5835     QaimModel model;
5836     for (int i = 0; i < initialItemCount; i++)
5837         model.addItem("Original item" + QString::number(i), "");
5838     QaimModel model_targetItems_transitionVia;
5839     QaimModel model_displacedItems_transitionVia;
5840
5841     QQuickView *window = getView();
5842     QQmlContext *ctxt = window->rootContext();
5843     TestObject *testObject = new TestObject;
5844     ctxt->setContextProperty("testModel", &model);
5845     ctxt->setContextProperty("model_targetItems_transitionVia", &model_targetItems_transitionVia);
5846     ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
5847     ctxt->setContextProperty("targetItems_transitionVia", targetItems_transitionVia);
5848     ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
5849     ctxt->setContextProperty("testObject", testObject);
5850     window->setSource(testFileUrl("moveTransitions.qml"));
5851     window->show();
5852     QVERIFY(QTest::qWaitForWindowExposed(window));
5853
5854     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
5855     QTRY_VERIFY(listview != 0);
5856     QQuickItem *contentItem = listview->contentItem();
5857     QVERIFY(contentItem != 0);
5858     QQuickText *name;
5859
5860     if (contentY != 0) {
5861         listview->setContentY(contentY);
5862         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
5863     }
5864
5865     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
5866
5867     // Items moving to *or* from visible positions should be animated.
5868     // Otherwise, they should not be animated.
5869     QList<QPair<QString, QString> > expectedTargetData;
5870     QList<int> targetIndexes;
5871     for (int i=moveFrom; i<moveFrom+moveCount; i++) {
5872         int toIndex = moveTo + (i - moveFrom);
5873         if (i <= (contentY + listview->height()) / 20
5874                 || toIndex < (contentY + listview->height()) / 20) {
5875             expectedTargetData << qMakePair(model.name(i), model.number(i));
5876             targetIndexes << i;
5877         }
5878     }
5879     // ViewTransition.index provides the indices that items are moving to, not from
5880     targetIndexes = adjustIndexesForMove(targetIndexes, moveFrom, moveTo, moveCount);
5881
5882     // start animation
5883     model.moveItems(moveFrom, moveTo, moveCount);
5884
5885     QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
5886     QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
5887                  expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
5888
5889     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
5890
5891     // check the target and displaced items were animated
5892     model_targetItems_transitionVia.matchAgainst(expectedTargetData, "wasn't animated from target 'from' pos", "shouldn't have been animated from target 'from' pos");
5893     model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
5894
5895     // check attached properties
5896     matchItemsAndIndexes(listview->property("targetTrans_items").toMap(), model, targetIndexes);
5897     matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
5898     matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
5899     if (expectedDisplacedIndexes.isValid()) {
5900         // adjust expectedDisplacedIndexes to their final values after the move
5901         QList<int> displacedIndexes = adjustIndexesForMove(expectedDisplacedIndexes.indexes, moveFrom, moveTo, moveCount);
5902         matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
5903         matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
5904         matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
5905     }
5906
5907     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
5908     int firstVisibleIndex = -1;
5909     for (int i=0; i<items.count(); i++) {
5910         if (items[i]->y() >= contentY) {
5911             QQmlExpression e(qmlContext(items[i]), items[i], "index");
5912             firstVisibleIndex = e.evaluate().toInt();
5913             break;
5914         }
5915     }
5916     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
5917
5918     // verify all items moved to the correct final positions
5919     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
5920     for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
5921         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
5922         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
5923         QTRY_COMPARE(item->y(), i*20.0 + itemsOffsetAfterMove);
5924         name = findItem<QQuickText>(contentItem, "textName", i);
5925         QVERIFY(name != 0);
5926         QTRY_COMPARE(name->text(), model.name(i));
5927     }
5928
5929     releaseView(window);
5930     delete testObject;
5931 }
5932
5933 void tst_QQuickListView::moveTransitions_data()
5934 {
5935     QTest::addColumn<int>("initialItemCount");
5936     QTest::addColumn<qreal>("contentY");
5937     QTest::addColumn<qreal>("itemsOffsetAfterMove");
5938     QTest::addColumn<int>("moveFrom");
5939     QTest::addColumn<int>("moveTo");
5940     QTest::addColumn<int>("moveCount");
5941     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
5942
5943     // when removing from above the visible, all items shift down depending on how many
5944     // items have been removed from above the visible
5945     QTest::newRow("move from above view, outside visible items, move 1") << 30 << 4*20.0 << 20.0
5946             << 1 << 10 << 1 << ListRange(11, 15+4);
5947     QTest::newRow("move from above view, outside visible items, move 1 (first item)") << 30 << 4*20.0 << 20.0
5948             << 0 << 10 << 1 << ListRange(11, 15+4);
5949     QTest::newRow("move from above view, outside visible items, move multiple") << 30 << 4*20.0 << 2*20.0
5950             << 1 << 10 << 2 << ListRange(12, 15+4);
5951     QTest::newRow("move from above view, outside visible items, move multiple (first item)") << 30 << 4*20.0 << 3*20.0
5952             << 0 << 10 << 3 << ListRange(13, 15+4);
5953     QTest::newRow("move from above view, mix of visible/non-visible") << 30 << 4*20.0 << 3*20.0
5954             << 1 << 10 << 5 << ListRange(6, 14) + ListRange(15, 15+4);
5955     QTest::newRow("move from above view, mix of visible/non-visible (move first)") << 30 << 4*20.0 << 4*20.0
5956             << 0 << 10 << 5 << ListRange(5, 14) + ListRange(15, 15+4);
5957
5958     QTest::newRow("move within view, move 1 down") << 30 << 0.0 << 0.0
5959             << 1 << 10 << 1 << ListRange(2, 10);
5960     QTest::newRow("move within view, move 1 down, move first item") << 30 << 0.0 << 0.0
5961             << 0 << 10 << 1 << ListRange(1, 10);
5962     QTest::newRow("move within view, move 1 down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
5963             << 0+4 << 10+4 << 1 << ListRange(1+4, 10+4);
5964     QTest::newRow("move within view, move 1 down, to last item") << 30 << 0.0 << 0.0
5965             << 10 << 15 << 1 << ListRange(11, 15);
5966     QTest::newRow("move within view, move first->last") << 30 << 0.0 << 0.0
5967             << 0 << 15 << 1 << ListRange(1, 15);
5968
5969     QTest::newRow("move within view, move multiple down") << 30 << 0.0 << 0.0
5970             << 1 << 10 << 3 << ListRange(4, 12);
5971     QTest::newRow("move within view, move multiple down, move first item") << 30 << 0.0 << 0.0
5972             << 0 << 10 << 3 << ListRange(3, 12);
5973     QTest::newRow("move within view, move multiple down, move first item, contentY not 0") << 30 << 4*20.0 << 0.0
5974             << 0+4 << 10+4 << 3 << ListRange(3+4, 12+4);
5975     QTest::newRow("move within view, move multiple down, displace last item") << 30 << 0.0 << 0.0
5976             << 5 << 13 << 3 << ListRange(8, 15);
5977     QTest::newRow("move within view, move multiple down, move first->last") << 30 << 0.0 << 0.0
5978             << 0 << 13 << 3 << ListRange(3, 15);
5979
5980     QTest::newRow("move within view, move 1 up") << 30 << 0.0 << 0.0
5981             << 10 << 1 << 1 << ListRange(1, 9);
5982     QTest::newRow("move within view, move 1 up, move to first index") << 30 << 0.0 << 0.0
5983             << 10 << 0 << 1 << ListRange(0, 9);
5984     QTest::newRow("move within view, move 1 up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
5985             << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
5986     QTest::newRow("move within view, move 1 up, move to first index, contentY not on item border") << 30 << 4*20.0 - 10 << 0.0
5987             << 10+4 << 0+4 << 1 << ListRange(0+4, 9+4);
5988     QTest::newRow("move within view, move 1 up, move last item") << 30 << 0.0 << 0.0
5989             << 15 << 10 << 1 << ListRange(10, 14);
5990     QTest::newRow("move within view, move 1 up, move last->first") << 30 << 0.0 << 0.0
5991             << 15 << 0 << 1 << ListRange(0, 14);
5992
5993     QTest::newRow("move within view, move multiple up") << 30 << 0.0 << 0.0
5994             << 10 << 1 << 3 << ListRange(1, 9);
5995     QTest::newRow("move within view, move multiple up, move to first index") << 30 << 0.0 << 0.0
5996             << 10 << 0 << 3 << ListRange(0, 9);
5997     QTest::newRow("move within view, move multiple up, move to first index, contentY not 0") << 30 << 4*20.0 << 0.0
5998             << 10+4 << 0+4 << 3 << ListRange(0+4, 9+4);
5999     QTest::newRow("move within view, move multiple up, move last item") << 30 << 0.0 << 0.0
6000             << 13 << 5 << 3 << ListRange(5, 12);
6001     QTest::newRow("move within view, move multiple up, move last->first") << 30 << 0.0 << 0.0
6002             << 13 << 0 << 3 << ListRange(0, 12);
6003
6004     QTest::newRow("move from below view, move 1 up, move to top") << 30 << 0.0 << 0.0
6005             << 20 << 0 << 1 << ListRange(0, 15);
6006     QTest::newRow("move from below view, move 1 up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
6007             << 25 << 4 << 1 << ListRange(0+4, 15+4);
6008     QTest::newRow("move from below view, move multiple up, move to top") << 30 << 0.0 << 0.0
6009             << 20 << 0 << 3 << ListRange(0, 15);
6010     QTest::newRow("move from below view, move multiple up, move to top, contentY not 0") << 30 << 4*20.0 << 0.0
6011             << 25 << 4 << 3 << ListRange(0+4, 15+4);
6012
6013     QTest::newRow("move from below view, move 1 up, move to bottom") << 30 << 0.0 << 0.0
6014             << 20 << 15 << 1 << ListRange(15, 15);
6015     QTest::newRow("move from below view, move 1 up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
6016             << 25 << 15+4 << 1 << ListRange(15+4, 15+4);
6017     QTest::newRow("move from below view, move multiple up, move to to bottom") << 30 << 0.0 << 0.0
6018             << 20 << 15 << 3 << ListRange(15, 15);
6019     QTest::newRow("move from below view, move multiple up, move to bottom, contentY not 0") << 30 << 4*20.0 << 0.0
6020             << 25 << 15+4 << 3 << ListRange(15+4, 15+4);
6021 }
6022
6023 void tst_QQuickListView::removeTransitions()
6024 {
6025     QFETCH(int, initialItemCount);
6026     QFETCH(bool, shouldAnimateTargets);
6027     QFETCH(qreal, contentY);
6028     QFETCH(int, removalIndex);
6029     QFETCH(int, removalCount);
6030     QFETCH(ListRange, expectedDisplacedIndexes);
6031
6032     // added items should end here
6033     QPointF targetItems_transitionTo(-50, -50);
6034
6035     // displaced items should pass through this points
6036     QPointF displacedItems_transitionVia(100, 100);
6037
6038     QaimModel model;
6039     for (int i = 0; i < initialItemCount; i++)
6040         model.addItem("Original item" + QString::number(i), "");
6041     QaimModel model_targetItems_transitionTo;
6042     QaimModel model_displacedItems_transitionVia;
6043
6044     QQuickView *window = getView();
6045     QQmlContext *ctxt = window->rootContext();
6046     TestObject *testObject = new TestObject;
6047     ctxt->setContextProperty("testModel", &model);
6048     ctxt->setContextProperty("model_targetItems_transitionTo", &model_targetItems_transitionTo);
6049     ctxt->setContextProperty("model_displacedItems_transitionVia", &model_displacedItems_transitionVia);
6050     ctxt->setContextProperty("targetItems_transitionTo", targetItems_transitionTo);
6051     ctxt->setContextProperty("displacedItems_transitionVia", displacedItems_transitionVia);
6052     ctxt->setContextProperty("testObject", testObject);
6053     window->setSource(testFileUrl("removeTransitions.qml"));
6054     window->show();
6055     QVERIFY(QTest::qWaitForWindowExposed(window));
6056
6057     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
6058     QTRY_VERIFY(listview != 0);
6059     QQuickItem *contentItem = listview->contentItem();
6060     QVERIFY(contentItem != 0);
6061     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6062
6063     if (contentY != 0) {
6064         listview->setContentY(contentY);
6065         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6066     }
6067
6068     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
6069
6070     // only target items that are visible should be animated
6071     QList<QPair<QString, QString> > expectedTargetData;
6072     QList<int> targetIndexes;
6073     if (shouldAnimateTargets) {
6074         for (int i=removalIndex; i<removalIndex+removalCount; i++) {
6075             if (i >= contentY / 20 && i < (contentY + listview->height()) / 20) {
6076                 expectedTargetData << qMakePair(model.name(i), model.number(i));
6077                 targetIndexes << i;
6078             }
6079         }
6080         QVERIFY(expectedTargetData.count() > 0);
6081     }
6082
6083     // calculate targetItems and expectedTargets before model changes
6084     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
6085     QVariantMap expectedTargets;
6086     for (int i=0; i<targetIndexes.count(); i++)
6087         expectedTargets[model.name(targetIndexes[i])] = targetIndexes[i];
6088
6089     // start animation
6090     model.removeItems(removalIndex, removalCount);
6091     QTRY_COMPARE(model.count(), listview->count());
6092
6093     if (shouldAnimateTargets) {
6094         QTRY_COMPARE(listview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
6095         QTRY_COMPARE(listview->property("displaceTransitionsDone").toInt(),
6096                      expectedDisplacedIndexes.isValid() ? expectedDisplacedIndexes.count() : 0);
6097
6098         // check the target and displaced items were animated
6099         model_targetItems_transitionTo.matchAgainst(expectedTargetData, "wasn't animated to target 'to' pos", "shouldn't have been animated to target 'to' pos");
6100         model_displacedItems_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with displaced anim", "shouldn't have been animated with displaced anim");
6101
6102         // check attached properties
6103         QCOMPARE(listview->property("targetTrans_items").toMap(), expectedTargets);
6104         matchIndexLists(listview->property("targetTrans_targetIndexes").toList(), targetIndexes);
6105         matchItemLists(listview->property("targetTrans_targetItems").toList(), targetItems);
6106         if (expectedDisplacedIndexes.isValid()) {
6107             // adjust expectedDisplacedIndexes to their final values after the move
6108             QList<int> displacedIndexes = adjustIndexesForRemoveDisplaced(expectedDisplacedIndexes.indexes, removalIndex, removalCount);
6109             matchItemsAndIndexes(listview->property("displacedTrans_items").toMap(), model, displacedIndexes);
6110             matchIndexLists(listview->property("displacedTrans_targetIndexes").toList(), targetIndexes);
6111             matchItemLists(listview->property("displacedTrans_targetItems").toList(), targetItems);
6112         }
6113     } else {
6114         QTRY_COMPARE(model_targetItems_transitionTo.count(), 0);
6115         QTRY_COMPARE(model_displacedItems_transitionVia.count(), 0);
6116     }
6117
6118     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6119     int firstVisibleIndex = -1;
6120     int itemCount = items.count();
6121
6122     for (int i=0; i<items.count(); i++) {
6123         QQmlExpression e(qmlContext(items[i]), items[i], "index");
6124         int index = e.evaluate().toInt();
6125         if (firstVisibleIndex < 0 && items[i]->y() >= contentY)
6126             firstVisibleIndex = index;
6127         if (index < 0)
6128             itemCount--;    // exclude deleted items
6129     }
6130     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
6131
6132     // verify all items moved to the correct final positions
6133     for (int i=firstVisibleIndex; i < model.count() && i < itemCount; ++i) {
6134         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6135         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6136         QCOMPARE(item->x(), 0.0);
6137         QCOMPARE(item->y(), contentY + (i-firstVisibleIndex) * 20.0);
6138         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6139         QVERIFY(name != 0);
6140         QTRY_COMPARE(name->text(), model.name(i));
6141     }
6142
6143     releaseView(window);
6144     delete testObject;
6145 }
6146
6147 void tst_QQuickListView::removeTransitions_data()
6148 {
6149     QTest::addColumn<int>("initialItemCount");
6150     QTest::addColumn<qreal>("contentY");
6151     QTest::addColumn<bool>("shouldAnimateTargets");
6152     QTest::addColumn<int>("removalIndex");
6153     QTest::addColumn<int>("removalCount");
6154     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
6155
6156     // All items that are visible following the remove operation should be animated.
6157     // Remove targets that are outside of the view should not be animated.
6158
6159     QTest::newRow("remove 1 before start")
6160             << 30 << 20.0 * 3 << false
6161             << 2 << 1 << ListRange();
6162     QTest::newRow("remove multiple, all before start")
6163             << 30 << 20.0 * 3 << false
6164             << 0 << 3 << ListRange();
6165     QTest::newRow("remove mix of before and after start")
6166             << 30 << 20.0 * 3 << true
6167             << 2 << 3 << ListRange(5, 20);  // 5-20 are visible after the remove
6168
6169     QTest::newRow("remove 1 from start")
6170             << 30 << 0.0 << true
6171             << 0 << 1 << ListRange(1, 16);  // 1-16 are visible after the remove
6172     QTest::newRow("remove multiple from start")
6173             << 30 << 0.0 << true
6174             << 0 << 3 << ListRange(3, 18);  // 3-18 are visible after the remove
6175     QTest::newRow("remove 1 from start, content y not 0")
6176             << 30 << 20.0 * 2 << true  // first visible is index 2, so translate the displaced indexes by 2
6177             << 2 << 1 << ListRange(1 + 2, 16 + 2);
6178     QTest::newRow("remove multiple from start, content y not 0")
6179             << 30 << 20.0 * 2 << true    // first visible is index 2
6180             << 2 << 3 << ListRange(3 + 2, 18 + 2);
6181
6182     QTest::newRow("remove 1 from middle")
6183             << 30 << 0.0 << true
6184             << 5 << 1 << ListRange(6, 16);
6185     QTest::newRow("remove multiple from middle")
6186             << 30 << 0.0 << true
6187             << 5 << 3 << ListRange(8, 18);
6188
6189
6190     QTest::newRow("remove 1 from bottom")
6191             << 30 << 0.0 << true
6192             << 15 << 1 << ListRange(16, 16);
6193
6194     // remove 15, 16, 17
6195     // 15 will animate as the target item, 16 & 17 won't be animated since they are outside
6196     // the view, and 18 will be animated as the displaced item to replace the last item
6197     QTest::newRow("remove multiple from bottom")
6198             << 30 << 0.0 << true
6199             << 15 << 3 << ListRange(18, 18);
6200
6201     QTest::newRow("remove 1 from bottom, content y not 0")
6202             << 30 << 20.0 * 2 << true
6203             << 15 + 2 << 1 << ListRange(16 + 2, 16 + 2);
6204     QTest::newRow("remove multiple from bottom, content y not 0")
6205             << 30 << 20.0 * 2 << true
6206             << 15 + 2 << 3 << ListRange(18 + 2, 18 + 2);
6207
6208
6209     QTest::newRow("remove 1 after end")
6210             << 30 << 0.0 << false
6211             << 17 << 1 << ListRange();
6212     QTest::newRow("remove multiple after end")
6213             << 30 << 0.0 << false
6214             << 17 << 3 << ListRange();
6215 }
6216
6217 void tst_QQuickListView::displacedTransitions()
6218 {
6219     QFETCH(bool, useDisplaced);
6220     QFETCH(bool, displacedEnabled);
6221     QFETCH(bool, useAddDisplaced);
6222     QFETCH(bool, addDisplacedEnabled);
6223     QFETCH(bool, useMoveDisplaced);
6224     QFETCH(bool, moveDisplacedEnabled);
6225     QFETCH(bool, useRemoveDisplaced);
6226     QFETCH(bool, removeDisplacedEnabled);
6227     QFETCH(ListChange, change);
6228     QFETCH(ListRange, expectedDisplacedIndexes);
6229
6230     QaimModel model;
6231     for (int i = 0; i < 30; i++)
6232         model.addItem("Original item" + QString::number(i), "");
6233     QaimModel model_displaced_transitionVia;
6234     QaimModel model_addDisplaced_transitionVia;
6235     QaimModel model_moveDisplaced_transitionVia;
6236     QaimModel model_removeDisplaced_transitionVia;
6237
6238     QPointF displaced_transitionVia(-50, -100);
6239     QPointF addDisplaced_transitionVia(-150, 100);
6240     QPointF moveDisplaced_transitionVia(50, -100);
6241     QPointF removeDisplaced_transitionVia(150, 100);
6242
6243     QQuickView *window = getView();
6244     QQmlContext *ctxt = window->rootContext();
6245     TestObject *testObject = new TestObject(window);
6246     ctxt->setContextProperty("testModel", &model);
6247     ctxt->setContextProperty("testObject", testObject);
6248     ctxt->setContextProperty("model_displaced_transitionVia", &model_displaced_transitionVia);
6249     ctxt->setContextProperty("model_addDisplaced_transitionVia", &model_addDisplaced_transitionVia);
6250     ctxt->setContextProperty("model_moveDisplaced_transitionVia", &model_moveDisplaced_transitionVia);
6251     ctxt->setContextProperty("model_removeDisplaced_transitionVia", &model_removeDisplaced_transitionVia);
6252     ctxt->setContextProperty("displaced_transitionVia", displaced_transitionVia);
6253     ctxt->setContextProperty("addDisplaced_transitionVia", addDisplaced_transitionVia);
6254     ctxt->setContextProperty("moveDisplaced_transitionVia", moveDisplaced_transitionVia);
6255     ctxt->setContextProperty("removeDisplaced_transitionVia", removeDisplaced_transitionVia);
6256     ctxt->setContextProperty("useDisplaced", useDisplaced);
6257     ctxt->setContextProperty("displacedEnabled", displacedEnabled);
6258     ctxt->setContextProperty("useAddDisplaced", useAddDisplaced);
6259     ctxt->setContextProperty("addDisplacedEnabled", addDisplacedEnabled);
6260     ctxt->setContextProperty("useMoveDisplaced", useMoveDisplaced);
6261     ctxt->setContextProperty("moveDisplacedEnabled", moveDisplacedEnabled);
6262     ctxt->setContextProperty("useRemoveDisplaced", useRemoveDisplaced);
6263     ctxt->setContextProperty("removeDisplacedEnabled", removeDisplacedEnabled);
6264     window->setSource(testFileUrl("displacedTransitions.qml"));
6265     window->show();
6266     qApp->processEvents();
6267
6268     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
6269     QTRY_VERIFY(listview != 0);
6270     QQuickItem *contentItem = listview->contentItem();
6271     QVERIFY(contentItem != 0);
6272     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6273
6274     QList<QPair<QString,QString> > expectedDisplacedValues = expectedDisplacedIndexes.getModelDataValues(model);
6275     listview->setProperty("displaceTransitionsDone", false);
6276
6277     switch (change.type) {
6278         case ListChange::Inserted:
6279         {
6280             QList<QPair<QString, QString> > targetItemData;
6281             for (int i=change.index; i<change.index + change.count; ++i)
6282                 targetItemData << qMakePair(QString("new item %1").arg(i), QString::number(i));
6283             model.insertItems(change.index, targetItemData);
6284             QTRY_COMPARE(model.count(), listview->count());
6285             break;
6286         }
6287         case ListChange::Removed:
6288             model.removeItems(change.index, change.count);
6289             QTRY_COMPARE(model.count(), listview->count());
6290             break;
6291         case ListChange::Moved:
6292             model.moveItems(change.index, change.to, change.count);
6293             QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6294             break;
6295         case ListChange::SetCurrent:
6296         case ListChange::SetContentY:
6297         case ListChange::Polish:
6298             break;
6299     }
6300
6301     QVariantList resultTargetIndexes = listview->property("displacedTargetIndexes").toList();
6302     QVariantList resultTargetItems = listview->property("displacedTargetItems").toList();
6303
6304     if ((useDisplaced && displacedEnabled)
6305             || (useAddDisplaced && addDisplacedEnabled)
6306             || (useMoveDisplaced && moveDisplacedEnabled)
6307             || (useRemoveDisplaced && removeDisplacedEnabled)) {
6308         QTRY_VERIFY(listview->property("displaceTransitionsDone").toBool());
6309
6310         // check the correct number of target items and indexes were received
6311         QCOMPARE(resultTargetIndexes.count(), expectedDisplacedIndexes.count());
6312         for (int i=0; i<resultTargetIndexes.count(); i++)
6313             QCOMPARE(resultTargetIndexes[i].value<QList<int> >().count(), change.count);
6314         QCOMPARE(resultTargetItems.count(), expectedDisplacedIndexes.count());
6315         for (int i=0; i<resultTargetItems.count(); i++)
6316             QCOMPARE(resultTargetItems[i].toList().count(), change.count);
6317     } else {
6318         QCOMPARE(resultTargetIndexes.count(), 0);
6319         QCOMPARE(resultTargetItems.count(), 0);
6320     }
6321
6322     if (change.type == ListChange::Inserted && useAddDisplaced && addDisplacedEnabled)
6323         model_addDisplaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with add displaced", "shouldn't have been animated with add displaced");
6324     else
6325         QCOMPARE(model_addDisplaced_transitionVia.count(), 0);
6326     if (change.type == ListChange::Moved && useMoveDisplaced && moveDisplacedEnabled)
6327         model_moveDisplaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with move displaced", "shouldn't have been animated with move displaced");
6328     else
6329         QCOMPARE(model_moveDisplaced_transitionVia.count(), 0);
6330     if (change.type == ListChange::Removed && useRemoveDisplaced && removeDisplacedEnabled)
6331         model_removeDisplaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with remove displaced", "shouldn't have been animated with remove displaced");
6332     else
6333         QCOMPARE(model_removeDisplaced_transitionVia.count(), 0);
6334
6335     if (useDisplaced && displacedEnabled
6336             && ( (change.type == ListChange::Inserted && (!useAddDisplaced || !addDisplacedEnabled))
6337                  || (change.type == ListChange::Moved && (!useMoveDisplaced || !moveDisplacedEnabled))
6338                  || (change.type == ListChange::Removed && (!useRemoveDisplaced || !removeDisplacedEnabled))) ) {
6339         model_displaced_transitionVia.matchAgainst(expectedDisplacedValues, "wasn't animated with generic displaced", "shouldn't have been animated with generic displaced");
6340     } else {
6341         QCOMPARE(model_displaced_transitionVia.count(), 0);
6342     }
6343
6344     // verify all items moved to the correct final positions
6345     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6346     for (int i=0; i < model.count() && i < items.count(); ++i) {
6347         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6348         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6349         QCOMPARE(item->x(), 0.0);
6350         QCOMPARE(item->y(), i * 20.0);
6351         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6352         QVERIFY(name != 0);
6353         QTRY_COMPARE(name->text(), model.name(i));
6354     }
6355
6356     releaseView(window);
6357 }
6358
6359 void tst_QQuickListView::displacedTransitions_data()
6360 {
6361     QTest::addColumn<bool>("useDisplaced");
6362     QTest::addColumn<bool>("displacedEnabled");
6363     QTest::addColumn<bool>("useAddDisplaced");
6364     QTest::addColumn<bool>("addDisplacedEnabled");
6365     QTest::addColumn<bool>("useMoveDisplaced");
6366     QTest::addColumn<bool>("moveDisplacedEnabled");
6367     QTest::addColumn<bool>("useRemoveDisplaced");
6368     QTest::addColumn<bool>("removeDisplacedEnabled");
6369     QTest::addColumn<ListChange>("change");
6370     QTest::addColumn<ListRange>("expectedDisplacedIndexes");
6371
6372     QTest::newRow("no displaced transitions at all")
6373             << false << false
6374             << false << false
6375             << false << false
6376             << false << false
6377             << ListChange::insert(0, 1) << ListRange(0, 15);
6378
6379     QTest::newRow("just displaced")
6380             << true << true
6381             << false << false
6382             << false << false
6383             << false << false
6384             << ListChange::insert(0, 1) << ListRange(0, 15);
6385
6386     QTest::newRow("just displaced (not enabled)")
6387             << true << false
6388             << false << false
6389             << false << false
6390             << false << false
6391             << ListChange::insert(0, 1) << ListRange(0, 15);
6392
6393     QTest::newRow("displaced + addDisplaced")
6394             << true << true
6395             << true << true
6396             << false << false
6397             << false << false
6398             << ListChange::insert(0, 1) << ListRange(0, 15);
6399
6400     QTest::newRow("displaced + addDisplaced (not enabled)")
6401             << true << true
6402             << true << false
6403             << false << false
6404             << false << false
6405             << ListChange::insert(0, 1) << ListRange(0, 15);
6406
6407     QTest::newRow("displaced + moveDisplaced")
6408             << true << true
6409             << false << false
6410             << true << true
6411             << false << false
6412             << ListChange::move(0, 10, 1) << ListRange(1, 10);
6413
6414     QTest::newRow("displaced + moveDisplaced (not enabled)")
6415             << true << true
6416             << false << false
6417             << true << false
6418             << false << false
6419             << ListChange::move(0, 10, 1) << ListRange(1, 10);
6420
6421     QTest::newRow("displaced + removeDisplaced")
6422             << true << true
6423             << false << false
6424             << false << false
6425             << true << true
6426             << ListChange::remove(0, 1) << ListRange(1, 16);
6427
6428     QTest::newRow("displaced + removeDisplaced (not enabled)")
6429             << true << true
6430             << false << false
6431             << false << false
6432             << true << false
6433             << ListChange::remove(0, 1) << ListRange(1, 16);
6434
6435
6436     QTest::newRow("displaced + add, should use generic displaced for a remove")
6437             << true << true
6438             << true << true
6439             << false << false
6440             << true << false
6441             << ListChange::remove(0, 1) << ListRange(1, 16);
6442 }
6443
6444 void tst_QQuickListView::multipleTransitions()
6445 {
6446     // Tests that if you interrupt a transition in progress with another action that
6447     // cancels the previous transition, the resulting items are still placed correctly.
6448
6449     QFETCH(int, initialCount);
6450     QFETCH(qreal, contentY);
6451     QFETCH(QList<ListChange>, changes);
6452     QFETCH(bool, enableAddTransitions);
6453     QFETCH(bool, enableMoveTransitions);
6454     QFETCH(bool, enableRemoveTransitions);
6455     QFETCH(bool, rippleAddDisplaced);
6456
6457     QPointF addTargets_transitionFrom(-50, -50);
6458     QPointF addDisplaced_transitionFrom(-50, 50);
6459     QPointF moveTargets_transitionFrom(50, -50);
6460     QPointF moveDisplaced_transitionFrom(50, 50);
6461     QPointF removeTargets_transitionTo(-100, 300);
6462     QPointF removeDisplaced_transitionFrom(100, 300);
6463
6464     QaimModel model;
6465     for (int i = 0; i < initialCount; i++)
6466         model.addItem("Original item" + QString::number(i), "");
6467
6468     QQuickView *window = getView();
6469     QQmlContext *ctxt = window->rootContext();
6470     TestObject *testObject = new TestObject;
6471     ctxt->setContextProperty("testModel", &model);
6472     ctxt->setContextProperty("testObject", testObject);
6473     ctxt->setContextProperty("addTargets_transitionFrom", addTargets_transitionFrom);
6474     ctxt->setContextProperty("addDisplaced_transitionFrom", addDisplaced_transitionFrom);
6475     ctxt->setContextProperty("moveTargets_transitionFrom", moveTargets_transitionFrom);
6476     ctxt->setContextProperty("moveDisplaced_transitionFrom", moveDisplaced_transitionFrom);
6477     ctxt->setContextProperty("removeTargets_transitionTo", removeTargets_transitionTo);
6478     ctxt->setContextProperty("removeDisplaced_transitionFrom", removeDisplaced_transitionFrom);
6479     ctxt->setContextProperty("enableAddTransitions", enableAddTransitions);
6480     ctxt->setContextProperty("enableMoveTransitions", enableMoveTransitions);
6481     ctxt->setContextProperty("enableRemoveTransitions", enableRemoveTransitions);
6482     ctxt->setContextProperty("rippleAddDisplaced", rippleAddDisplaced);
6483     window->setSource(testFileUrl("multipleTransitions.qml"));
6484     window->show();
6485     QVERIFY(QTest::qWaitForWindowExposed(window));
6486
6487     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
6488     QTRY_VERIFY(listview != 0);
6489     QQuickItem *contentItem = listview->contentItem();
6490     QVERIFY(contentItem != 0);
6491     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6492
6493     if (contentY != 0) {
6494         listview->setContentY(contentY);
6495         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6496     }
6497
6498     int timeBetweenActions = window->rootObject()->property("timeBetweenActions").toInt();
6499
6500     for (int i=0; i<changes.count(); i++) {
6501         switch (changes[i].type) {
6502             case ListChange::Inserted:
6503             {
6504                 QList<QPair<QString, QString> > targetItems;
6505                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
6506                     targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
6507                 model.insertItems(changes[i].index, targetItems);
6508                 QTRY_COMPARE(model.count(), listview->count());
6509                 if (i == changes.count() - 1) {
6510                     QTRY_VERIFY(!listview->property("runningAddTargets").toBool());
6511                     QTRY_VERIFY(!listview->property("runningAddDisplaced").toBool());
6512                 } else {
6513                     QTest::qWait(timeBetweenActions);
6514                 }
6515                 break;
6516             }
6517             case ListChange::Removed:
6518                 model.removeItems(changes[i].index, changes[i].count);
6519                 QTRY_COMPARE(model.count(), listview->count());
6520                 if (i == changes.count() - 1) {
6521                     QTRY_VERIFY(!listview->property("runningRemoveTargets").toBool());
6522                     QTRY_VERIFY(!listview->property("runningRemoveDisplaced").toBool());
6523                 } else {
6524                     QTest::qWait(timeBetweenActions);
6525                 }
6526                 break;
6527             case ListChange::Moved:
6528                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
6529                 QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6530                 if (i == changes.count() - 1) {
6531                     QTRY_VERIFY(!listview->property("runningMoveTargets").toBool());
6532                     QTRY_VERIFY(!listview->property("runningMoveDisplaced").toBool());
6533                 } else {
6534                     QTest::qWait(timeBetweenActions);
6535                 }
6536                 break;
6537             case ListChange::SetCurrent:
6538                 listview->setCurrentIndex(changes[i].index);
6539                 break;
6540             case ListChange::SetContentY:
6541                 listview->setContentY(changes[i].pos);
6542                 QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6543                 break;
6544             case ListChange::Polish:
6545                 break;
6546         }
6547     }
6548     QCOMPARE(listview->count(), model.count());
6549
6550     // verify all items moved to the correct final positions
6551     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6552     for (int i=0; i < model.count() && i < items.count(); ++i) {
6553         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6554         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6555         QTRY_COMPARE(item->x(), 0.0);
6556         QTRY_COMPARE(item->y(), i*20.0);
6557         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6558         QVERIFY(name != 0);
6559         QTRY_COMPARE(name->text(), model.name(i));
6560     }
6561
6562     releaseView(window);
6563     delete testObject;
6564 }
6565
6566 void tst_QQuickListView::multipleTransitions_data()
6567 {
6568     QTest::addColumn<int>("initialCount");
6569     QTest::addColumn<qreal>("contentY");
6570     QTest::addColumn<QList<ListChange> >("changes");
6571     QTest::addColumn<bool>("enableAddTransitions");
6572     QTest::addColumn<bool>("enableMoveTransitions");
6573     QTest::addColumn<bool>("enableRemoveTransitions");
6574     QTest::addColumn<bool>("rippleAddDisplaced");
6575
6576     // the added item and displaced items should move to final dest correctly
6577     QTest::newRow("add item, then move it immediately") << 10 << 0.0 << (QList<ListChange>()
6578             << ListChange::insert(0, 1)
6579             << ListChange::move(0, 3, 1)
6580             )
6581             << true << true << true << false;
6582
6583     // items affected by the add should change from move to add transition
6584     QTest::newRow("move, then insert item before the moved item") << 20 << 0.0 << (QList<ListChange>()
6585             << ListChange::move(1, 10, 3)
6586             << ListChange::insert(0, 1)
6587             )
6588             << true << true << true << false;
6589
6590     // items should be placed correctly if you trigger a transition then refill for that index
6591     QTest::newRow("add at 0, flick down, flick back to top and add at 0 again") << 20 << 0.0 << (QList<ListChange>()
6592             << ListChange::insert(0, 1)
6593             << ListChange::setContentY(80.0)
6594             << ListChange::setContentY(0.0)
6595             << ListChange::insert(0, 1)
6596             )
6597             << true << true << true << false;
6598
6599     QTest::newRow("insert then remove same index, with ripple effect on add displaced") << 20 << 0.0 << (QList<ListChange>()
6600             << ListChange::insert(1, 1)
6601             << ListChange::remove(1, 1)
6602             )
6603             << true << true << true << true;
6604
6605     // if item is removed while undergoing a displaced transition, all other items should end up at their correct positions,
6606     // even if a remove-displace transition is not present to re-animate them
6607     QTest::newRow("insert then remove, with remove disabled") << 20 << 0.0 << (QList<ListChange>()
6608             << ListChange::insert(0, 1)
6609             << ListChange::remove(2, 1)
6610             )
6611             << true << true << false << false;
6612
6613     // if last item is not flush with the edge of the view, it should still be refilled in correctly after a
6614     // remove has changed the position of where it will move to
6615     QTest::newRow("insert twice then remove, with remove disabled") << 20 << 0.0 << (QList<ListChange>()
6616             << ListChange::setContentY(-10.0)
6617             << ListChange::insert(0, 1)
6618             << ListChange::insert(0, 1)
6619             << ListChange::remove(2, 1)
6620             )
6621             << true << true << false << false;
6622 }
6623
6624 void tst_QQuickListView::multipleDisplaced()
6625 {
6626     // multiple move() operations should only restart displace transitions for items that
6627     // moved from previously set positions, and not those that have moved from their current
6628     // item positions (which may e.g. still be changing from easing bounces in the last transition)
6629
6630     QaimModel model;
6631     for (int i = 0; i < 30; i++)
6632         model.addItem("Original item" + QString::number(i), "");
6633
6634     QQuickView *window = getView();
6635     QQmlContext *ctxt = window->rootContext();
6636     ctxt->setContextProperty("testModel", &model);
6637     ctxt->setContextProperty("testObject", new TestObject(window));
6638     window->setSource(testFileUrl("multipleDisplaced.qml"));
6639     window->show();
6640     QVERIFY(QTest::qWaitForWindowExposed(window));
6641
6642     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
6643     QTRY_VERIFY(listview != 0);
6644     QQuickItem *contentItem = listview->contentItem();
6645     QVERIFY(contentItem != 0);
6646     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6647
6648     model.moveItems(12, 8, 1);
6649     QTest::qWait(window->rootObject()->property("duration").toInt() / 2);
6650     model.moveItems(8, 3, 1);
6651     QTRY_VERIFY(listview->property("displaceTransitionsDone").toBool());
6652
6653     QVariantMap transitionsStarted = listview->property("displaceTransitionsStarted").toMap();
6654     foreach (const QString &name, transitionsStarted.keys()) {
6655         QVERIFY2(transitionsStarted[name] == 1,
6656                  QTest::toString(QString("%1 was displaced %2 times").arg(name).arg(transitionsStarted[name].toInt())));
6657     }
6658
6659     // verify all items moved to the correct final positions
6660     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
6661     for (int i=0; i < model.count() && i < items.count(); ++i) {
6662         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6663         QVERIFY2(item, QTest::toString(QString("Item %1 not found").arg(i)));
6664         QTRY_COMPARE(item->x(), 0.0);
6665         QTRY_COMPARE(item->y(), i*20.0);
6666         QQuickText *name = findItem<QQuickText>(contentItem, "textName", i);
6667         QVERIFY(name != 0);
6668         QTRY_COMPARE(name->text(), model.name(i));
6669     }
6670
6671     releaseView(window);
6672 }
6673
6674 QList<int> tst_QQuickListView::toIntList(const QVariantList &list)
6675 {
6676     QList<int> ret;
6677     bool ok = true;
6678     for (int i=0; i<list.count(); i++) {
6679         ret << list[i].toInt(&ok);
6680         if (!ok)
6681             qWarning() << "tst_QQuickListView::toIntList(): not a number:" << list[i];
6682     }
6683
6684     return ret;
6685 }
6686
6687 void tst_QQuickListView::matchIndexLists(const QVariantList &indexLists, const QList<int> &expectedIndexes)
6688 {
6689     for (int i=0; i<indexLists.count(); i++) {
6690         QSet<int> current = indexLists[i].value<QList<int> >().toSet();
6691         if (current != expectedIndexes.toSet())
6692             qDebug() << "Cannot match actual targets" << current << "with expected" << expectedIndexes;
6693         QCOMPARE(current, expectedIndexes.toSet());
6694     }
6695 }
6696
6697 void tst_QQuickListView::matchItemsAndIndexes(const QVariantMap &items, const QaimModel &model, const QList<int> &expectedIndexes)
6698 {
6699     for (QVariantMap::const_iterator it = items.begin(); it != items.end(); ++it) {
6700         QVERIFY(it.value().type() == QVariant::Int);
6701         QString name = it.key();
6702         int itemIndex = it.value().toInt();
6703         QVERIFY2(expectedIndexes.contains(itemIndex), QTest::toString(QString("Index %1 not found in expectedIndexes").arg(itemIndex)));
6704         if (model.name(itemIndex) != name)
6705             qDebug() << itemIndex;
6706         QCOMPARE(model.name(itemIndex), name);
6707     }
6708     QCOMPARE(items.count(), expectedIndexes.count());
6709 }
6710
6711 void tst_QQuickListView::matchItemLists(const QVariantList &itemLists, const QList<QQuickItem *> &expectedItems)
6712 {
6713     for (int i=0; i<itemLists.count(); i++) {
6714         QVERIFY(itemLists[i].type() == QVariant::List);
6715         QVariantList current = itemLists[i].toList();
6716         for (int j=0; j<current.count(); j++) {
6717             QQuickItem *o = qobject_cast<QQuickItem*>(current[j].value<QObject*>());
6718             QVERIFY2(o, QTest::toString(QString("Invalid actual item at %1").arg(j)));
6719             QVERIFY2(expectedItems.contains(o), QTest::toString(QString("Cannot match item %1").arg(j)));
6720         }
6721         QCOMPARE(current.count(), expectedItems.count());
6722     }
6723 }
6724
6725 void tst_QQuickListView::flickBeyondBounds()
6726 {
6727     QQuickView *window = createView();
6728
6729     window->setSource(testFileUrl("flickBeyondBoundsBug.qml"));
6730     window->show();
6731     qApp->processEvents();
6732
6733     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
6734     QTRY_VERIFY(listview != 0);
6735
6736     QQuickItem *contentItem = listview->contentItem();
6737     QTRY_VERIFY(contentItem != 0);
6738     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6739
6740     // Flick view up beyond bounds
6741     flick(window, QPoint(10, 10), QPoint(10, -1000), 180);
6742     QTRY_VERIFY(findItems<QQuickItem>(contentItem, "wrapper").count() == 0);
6743
6744     // We're really testing that we don't get stuck in a loop,
6745     // but also confirm items positioned correctly.
6746     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 2);
6747     for (int i = 0; i < 2; ++i) {
6748         QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", i);
6749         if (!item) qWarning() << "Item" << i << "not found";
6750         QTRY_VERIFY(item);
6751         QTRY_VERIFY(item->y() == i*45);
6752     }
6753
6754     delete window;
6755 }
6756
6757 void tst_QQuickListView::destroyItemOnCreation()
6758 {
6759     QaimModel model;
6760     QQuickView *window = createView();
6761     window->rootContext()->setContextProperty("testModel", &model);
6762
6763     window->setSource(testFileUrl("destroyItemOnCreation.qml"));
6764     window->show();
6765     qApp->processEvents();
6766
6767     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
6768     QVERIFY(listview != 0);
6769
6770     QQuickItem *contentItem = listview->contentItem();
6771     QVERIFY(contentItem != 0);
6772     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6773
6774     QCOMPARE(window->rootObject()->property("createdIndex").toInt(), -1);
6775     model.addItem("new item", "");
6776     QTRY_COMPARE(window->rootObject()->property("createdIndex").toInt(), 0);
6777
6778     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
6779     QCOMPARE(model.count(), 0);
6780
6781     delete window;
6782 }
6783
6784 void tst_QQuickListView::parentBinding()
6785 {
6786     QQuickView *window = createView();
6787
6788     m_errorCount = 0;
6789     QtMessageHandler old = qInstallMessageHandler(errorMsgHandler);
6790
6791     window->setSource(testFileUrl("parentBinding.qml"));
6792     window->show();
6793     QTest::qWaitForWindowExposed(window);
6794
6795     QQuickListView *listview = qobject_cast<QQuickListView*>(window->rootObject());
6796     QVERIFY(listview != 0);
6797
6798     QQuickItem *contentItem = listview->contentItem();
6799     QVERIFY(contentItem != 0);
6800     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
6801
6802     QQuickItem *item = findItem<QQuickItem>(contentItem, "wrapper", 0);
6803     QVERIFY(item);
6804     QCOMPARE(item->width(), listview->width());
6805     QCOMPARE(item->height(), listview->height()/12);
6806
6807     // there should be no transient binding error
6808     QVERIFY(!m_errorCount);
6809
6810     qInstallMessageHandler(old);
6811
6812     delete window;
6813 }
6814
6815 QTEST_MAIN(tst_QQuickListView)
6816
6817 #include "tst_qquicklistview.moc"
6818
6819