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