From 53e317468626284fe26877659dff551ce3bc0e55 Mon Sep 17 00:00:00 2001
From: Alan Alpert <aalpert@blackberry.com>
Date: Thu, 11 Apr 2013 16:57:19 -0700
Subject: [PATCH] No longer apply pending changes when accessing ItemView
 properties

Applying changes in the getters can lead to binding loops, and is
currently inconsistently applied. Removing the applyPendingChanges calls
from remaining getters, and adding a forceLayout() function for cases
where the immediate-apply behavior is needed.

Task-number:  QTBUG-30555
Parts-of-patch-by: Albert Astals Cid
Change-Id: I64632601e02f2a53060296ab7739577a749d916f
Reviewed-by: Andrew den Exter <andrew.den.exter@qinetic.com.au>
Reviewed-by: Albert Astals Cid <albert.astals@canonical.com>
---
 src/quick/items/qquickgridview.cpp            |  16 +++
 src/quick/items/qquickitemsmodule.cpp         |   3 +
 src/quick/items/qquickitemview.cpp            |  13 +--
 src/quick/items/qquickitemview_p.h            |   1 +
 src/quick/items/qquicklistview.cpp            |  15 +++
 tests/auto/qmltest/listview/tst_listview.qml  |   3 +-
 .../qquickgridview/tst_qquickgridview.cpp     |  37 ++++--
 .../qquicklistview/data/delayedChanges.qml    |  39 +++++++
 .../quick/qquicklistview/data/emptymodel.qml  |   4 +-
 .../qquicklistview/tst_qquicklistview.cpp     | 106 +++++++++++++-----
 .../tst_qquickvisualdatamodel.cpp             |   7 ++
 11 files changed, 198 insertions(+), 46 deletions(-)
 create mode 100644 tests/auto/quick/qquicklistview/data/delayedChanges.qml

diff --git a/src/quick/items/qquickgridview.cpp b/src/quick/items/qquickgridview.cpp
index e40d21b498..f4e34da318 100644
--- a/src/quick/items/qquickgridview.cpp
+++ b/src/quick/items/qquickgridview.cpp
@@ -2564,6 +2564,22 @@ bool QQuickGridViewPrivate::needsRefillForAddedOrRemovedIndex(int modelIndex) co
     \b Note: methods should only be called after the Component has completed.
 */
 
+
+/*!
+    \qmlmethod QtQuick2::GridView::forceLayout()
+
+    Responding to changes in the model is usually batched to happen only once
+    per frame. This means that inside script blocks it is possible for the
+    underlying model to have changed, but the GridView has not caught up yet.
+
+    This method forces the GridView to immediately respond to any outstanding
+    changes in the model.
+
+    \since 5.1
+
+    \b Note: methods should only be called after the Component has completed.
+*/
+
 QQuickGridViewAttached *QQuickGridView::qmlAttachedProperties(QObject *obj)
 {
     return new QQuickGridViewAttached(obj);
diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp
index 741583a95d..f5bcf3596f 100644
--- a/src/quick/items/qquickitemsmodule.cpp
+++ b/src/quick/items/qquickitemsmodule.cpp
@@ -230,6 +230,9 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor)
 
     qmlRegisterType<QQuickItem, 1>(uri, 2, 1,"Item");
     qmlRegisterType<QQuickGrid, 1>(uri, 2, 1, "Grid");
+    qmlRegisterUncreatableType<QQuickItemView, 1>(uri, 2, 1, "ItemView", QQuickItemView::tr("ItemView is an abstract base class"));
+    qmlRegisterType<QQuickListView, 1>(uri, 2, 1, "ListView");
+    qmlRegisterType<QQuickGridView, 1>(uri, 2, 1, "GridView");
     qmlRegisterType<QQuickTextEdit, 1>(uri, 2, 1, "TextEdit");
 }
 
diff --git a/src/quick/items/qquickitemview.cpp b/src/quick/items/qquickitemview.cpp
index 03e16f2ebf..d774091ef5 100644
--- a/src/quick/items/qquickitemview.cpp
+++ b/src/quick/items/qquickitemview.cpp
@@ -249,7 +249,6 @@ QQuickItemView::~QQuickItemView()
 QQuickItem *QQuickItemView::currentItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->currentItem ? d->currentItem->item : 0;
 }
 
@@ -379,14 +378,12 @@ int QQuickItemView::count() const
     Q_D(const QQuickItemView);
     if (!d->model)
         return 0;
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->model->count();
 }
 
 int QQuickItemView::currentIndex() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->currentIndex;
 }
 
@@ -496,7 +493,6 @@ QQmlComponent *QQuickItemView::header() const
 QQuickItem *QQuickItemView::headerItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->header ? d->header->item : 0;
 }
 
@@ -532,7 +528,6 @@ QQmlComponent *QQuickItemView::footer() const
 QQuickItem *QQuickItemView::footerItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->footer ? d->footer->item : 0;
 }
 
@@ -559,7 +554,6 @@ void QQuickItemView::setFooter(QQmlComponent *footerComponent)
 QQmlComponent *QQuickItemView::highlight() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->highlightComponent;
 }
 
@@ -579,7 +573,6 @@ void QQuickItemView::setHighlight(QQmlComponent *highlightComponent)
 QQuickItem *QQuickItemView::highlightItem() const
 {
     Q_D(const QQuickItemView);
-    const_cast<QQuickItemViewPrivate*>(d)->applyPendingChanges();
     return d->highlight ? d->highlight->item : 0;
 }
 
@@ -965,6 +958,12 @@ QQuickItem *QQuickItemView::itemAt(qreal x, qreal y) const
     return 0;
 }
 
+void QQuickItemView::forceLayout()
+{
+    Q_D(QQuickItemView);
+    d->applyPendingChanges();
+}
+
 void QQuickItemViewPrivate::applyPendingChanges()
 {
     Q_Q(QQuickItemView);
diff --git a/src/quick/items/qquickitemview_p.h b/src/quick/items/qquickitemview_p.h
index b0f910680a..d7812bcdad 100644
--- a/src/quick/items/qquickitemview_p.h
+++ b/src/quick/items/qquickitemview_p.h
@@ -202,6 +202,7 @@ public:
     Q_INVOKABLE QQuickItem *itemAt(qreal x, qreal y) const;
     Q_INVOKABLE void positionViewAtBeginning();
     Q_INVOKABLE void positionViewAtEnd();
+    Q_REVISION(1) Q_INVOKABLE void forceLayout();
 
     virtual void setContentX(qreal pos);
     virtual void setContentY(qreal pos);
diff --git a/src/quick/items/qquicklistview.cpp b/src/quick/items/qquicklistview.cpp
index 53dc715469..b99fba4e4c 100644
--- a/src/quick/items/qquicklistview.cpp
+++ b/src/quick/items/qquicklistview.cpp
@@ -3108,6 +3108,21 @@ void QQuickListViewPrivate::translateAndTransitionItemsAfter(int afterModelIndex
     \b Note: methods should only be called after the Component has completed.
 */
 
+/*!
+    \qmlmethod QtQuick2::ListView::forceLayout()
+
+    Responding to changes in the model is usually batched to happen only once
+    per frame. This means that inside script blocks it is possible for the
+    underlying model to have changed, but the ListView has not caught up yet.
+
+    This method forces the ListView to immediately respond to any outstanding
+    changes in the model.
+
+    \since 5.1
+
+    \b Note: methods should only be called after the Component has completed.
+*/
+
 QQuickListViewAttached *QQuickListView::qmlAttachedProperties(QObject *obj)
 {
     return new QQuickListViewAttached(obj);
diff --git a/tests/auto/qmltest/listview/tst_listview.qml b/tests/auto/qmltest/listview/tst_listview.qml
index cbace624c3..52384fe242 100644
--- a/tests/auto/qmltest/listview/tst_listview.qml
+++ b/tests/auto/qmltest/listview/tst_listview.qml
@@ -38,7 +38,7 @@
 **
 ****************************************************************************/
 
-import QtQuick 2.0
+import QtQuick 2.1
 import QtTest 1.0
 
 Item {
@@ -168,6 +168,7 @@ Item {
             modelalter.currentIndex = 1;
             compare(modelalter.currentItem.text, "AlterModelElement1")
             altermodel.clear()
+            modelalter.forceLayout()
             tryCompare(modelalter.count, 0)
             compare(modelalter.currentItem, null)
         }
diff --git a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp
index 0c9788ab8e..d0ffba9435 100644
--- a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp
+++ b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp
@@ -532,6 +532,7 @@ void tst_QQuickGridView::inserted_defaultLayout(QQuickGridView::Flow flow,
     for (int i=0; i<insertCount; i++)
         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.insertItems(insertIndex, newData);
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->property("count").toInt(), model.count());
 
     // check visibleItems.first() is in correct position
@@ -728,6 +729,7 @@ void tst_QQuickGridView::insertBeforeVisible()
     for (int i=0; i<insertCount; i++)
         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.insertItems(insertIndex, newData);
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->property("count").toInt(), model.count());
 
     // now, moving to the top of the view should position the inserted items correctly
@@ -958,6 +960,7 @@ void tst_QQuickGridView::removed_defaultLayout(QQuickGridView::Flow flow,
         QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
 
     model.removeItems(removeIndex, removeCount);
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->property("count").toInt(), model.count());
 
     QString firstName;
@@ -1245,6 +1248,7 @@ void tst_QQuickGridView::clear()
     QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
 
     model.clear();
+    gridview->forceLayout();
 
     QVERIFY(gridview->count() == 0);
     QVERIFY(gridview->currentItem() == 0);
@@ -1254,6 +1258,7 @@ void tst_QQuickGridView::clear()
 
     // confirm sanity when adding an item to cleared list
     model.addItem("New", "1");
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->count(), 1);
     QVERIFY(gridview->currentItem() != 0);
     QVERIFY(gridview->currentIndex() == 0);
@@ -3483,6 +3488,7 @@ void tst_QQuickGridView::extents()
     QCOMPARE(gridview->originY(), origin_empty.y());
     for (int i=0; i<30; i++)
         model.addItem("Item" + QString::number(i), "");
+    gridview->forceLayout();
     QTRY_COMPARE(gridview->count(), model.count());
     QCOMPARE(gridview->originX(), origin_nonEmpty.x());
     QCOMPARE(gridview->originY(), origin_nonEmpty.y());
@@ -3942,9 +3948,9 @@ void tst_QQuickGridView::onAdd()
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
 
-    QObject *object = window->rootObject();
-    object->setProperty("width", window->width());
-    object->setProperty("height", window->height());
+    QQuickGridView *gridview = qobject_cast<QQuickGridView*>(window->rootObject());
+    gridview->setProperty("width", window->width());
+    gridview->setProperty("height", window->height());
     qApp->processEvents();
 
     QList<QPair<QString, QString> > items;
@@ -3952,10 +3958,11 @@ void tst_QQuickGridView::onAdd()
         items << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.addItems(items);
 
-    QTRY_COMPARE(model.count(), qobject_cast<QQuickGridView*>(window->rootObject())->count());
+    gridview->forceLayout();
+    QTRY_COMPARE(model.count(), gridview->count());
     qApp->processEvents();
 
-    QVariantList result = object->property("addedDelegates").toList();
+    QVariantList result = gridview->property("addedDelegates").toList();
     QTRY_COMPARE(result.count(), items.count());
     for (int i=0; i<items.count(); i++)
         QCOMPARE(result[i].toString(), items[i].first);
@@ -3999,11 +4006,12 @@ void tst_QQuickGridView::onRemove()
     ctxt->setContextProperty("delegateWidth", delegateWidth);
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
-    QObject *object = window->rootObject();
+    QQuickGridView *gridview = qobject_cast<QQuickGridView*>(window->rootObject());
 
     model.removeItems(indexToRemove, removeCount);
-    QTRY_COMPARE(model.count(), qobject_cast<QQuickGridView*>(window->rootObject())->count());
-    QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
+    gridview->forceLayout();
+    QTRY_COMPARE(model.count(), gridview->count());
+    QCOMPARE(gridview->property("removedDelegateCount"), QVariant(removeCount));
 
     releaseView(window);
 }
@@ -4151,6 +4159,7 @@ void tst_QQuickGridView::margins()
         gridview->setContentX(-400);
         QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
         model.removeItems(0, 4);
+        gridview->forceLayout();
         QTRY_COMPARE(model.count(), gridview->count());
         gridview->setContentX(-240+50);
         gridview->returnToBounds();
@@ -4458,6 +4467,7 @@ void tst_QQuickGridView::unaligned()
 
     // removing
     model.removeItems(7, 10);
+    gridview->forceLayout();
     QTRY_COMPARE(model.count(), gridview->count());
     for (int i = 0; i < 18; ++i) {
         QQuickItem *item = 0;
@@ -4663,6 +4673,7 @@ void tst_QQuickGridView::addTransitions()
     // start animation
     if (!newData.isEmpty()) {
         model.insertItems(insertionIndex, newData);
+        gridview->forceLayout();
         QTRY_COMPARE(model.count(), gridview->count());
     }
 
@@ -4866,6 +4877,7 @@ void tst_QQuickGridView::moveTransitions()
 
     // start animation
     model.moveItems(moveFrom, moveTo, moveCount);
+    gridview->forceLayout();
 
     QTRY_COMPARE(gridview->property("targetTransitionsDone").toInt(), expectedTargetData.count());
     QTRY_COMPARE(gridview->property("displaceTransitionsDone").toInt(),
@@ -5114,6 +5126,7 @@ void tst_QQuickGridView::removeTransitions()
 
     // start animation
     model.removeItems(removalIndex, removalCount);
+    gridview->forceLayout();
     QTRY_COMPARE(model.count(), gridview->count());
 
     if (shouldAnimateTargets || expectedDisplacedIndexes.isValid()) {
@@ -5327,6 +5340,7 @@ void tst_QQuickGridView::displacedTransitions()
         case ListChange::Polish:
             break;
     }
+    gridview->forceLayout();
 
     QVariantList resultTargetIndexes = gridview->property("displacedTargetIndexes").toList();
     QVariantList resultTargetItems = gridview->property("displacedTargetItems").toList();
@@ -5534,6 +5548,7 @@ void tst_QQuickGridView::multipleTransitions()
                 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
                     targetItems << qMakePair(QString("new item %1").arg(j), QString::number(j));
                 model.insertItems(changes[i].index, targetItems);
+                gridview->forceLayout();
                 QTRY_COMPARE(model.count(), gridview->count());
                 if (i == changes.count() - 1) {
                     QTRY_VERIFY(!gridview->property("runningAddTargets").toBool());
@@ -5545,6 +5560,7 @@ void tst_QQuickGridView::multipleTransitions()
             }
             case ListChange::Removed:
                 model.removeItems(changes[i].index, changes[i].count);
+                gridview->forceLayout();
                 QTRY_COMPARE(model.count(), gridview->count());
                 if (i == changes.count() - 1) {
                     QTRY_VERIFY(!gridview->property("runningRemoveTargets").toBool());
@@ -5555,6 +5571,7 @@ void tst_QQuickGridView::multipleTransitions()
                 break;
             case ListChange::Moved:
                 model.moveItems(changes[i].index, changes[i].to, changes[i].count);
+                gridview->forceLayout();
                 QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
                 if (i == changes.count() - 1) {
                     QTRY_VERIFY(!gridview->property("runningMoveTargets").toBool());
@@ -5566,16 +5583,17 @@ void tst_QQuickGridView::multipleTransitions()
             case ListChange::SetCurrent:
                 gridview->setCurrentIndex(changes[i].index);
                 QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                gridview->forceLayout();
                 break;
             case ListChange::SetContentY:
                 gridview->setContentY(changes[i].pos);
                 QTRY_COMPARE(QQuickItemPrivate::get(gridview)->polishScheduled, false);
+                gridview->forceLayout();
                 break;
             case ListChange::Polish:
                 break;
         }
     }
-    QCOMPARE(gridview->count(), model.count());
 
     QList<QQuickItem*> items = findItems<QQuickItem>(contentItem, "wrapper");
     int firstVisibleIndex = -1;
@@ -5586,6 +5604,7 @@ void tst_QQuickGridView::multipleTransitions()
             break;
         }
     }
+    QTRY_COMPARE(gridview->count(), model.count());
     QVERIFY2(firstVisibleIndex >= 0, QTest::toString(firstVisibleIndex));
 
     // verify all items moved to the correct final positions
diff --git a/tests/auto/quick/qquicklistview/data/delayedChanges.qml b/tests/auto/quick/qquicklistview/data/delayedChanges.qml
new file mode 100644
index 0000000000..590af39c02
--- /dev/null
+++ b/tests/auto/quick/qquicklistview/data/delayedChanges.qml
@@ -0,0 +1,39 @@
+import QtQuick 2.1
+
+Item {
+    width: 400
+    height: 400
+    function takeTwo()
+    {
+        listView.model.remove(0);
+        listView.model.remove(0);
+    }
+    function takeTwo_sync()
+    {
+        listView.model.remove(0);
+        listView.forceLayout();
+        listView.model.remove(0);
+        listView.forceLayout();
+    }
+
+    ListView {
+        id: listView
+        height: parent.height
+        width: 400
+        model: ListModel {
+            ListElement { name: "A" }
+            ListElement { name: "B" }
+            ListElement { name: "C" }
+            ListElement { name: "D" }
+            ListElement { name: "E" }
+            ListElement { name: "F" }
+            ListElement { name: "G" }
+            ListElement { name: "H" }
+            ListElement { name: "I" }
+            ListElement { name: "J" }
+        }
+        delegate: Text {
+            text: index + listView.count
+        }
+    }
+}
diff --git a/tests/auto/quick/qquicklistview/data/emptymodel.qml b/tests/auto/quick/qquicklistview/data/emptymodel.qml
index 16bcd3f9ae..3feec691cf 100644
--- a/tests/auto/quick/qquicklistview/data/emptymodel.qml
+++ b/tests/auto/quick/qquicklistview/data/emptymodel.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.0
+import QtQuick 2.1
 Rectangle {
     ListModel {
         id: model
@@ -12,11 +12,13 @@ Rectangle {
     }
     function remove() {
         model.remove(0)
+        list.forceLayout()
         isCurrentItemNull = list.currentItem === null //check no seg fault
     }
 
     function add() {
         model.append({name: "hello"})
+        list.forceLayout()
         isCurrentItemNull = list.currentItem === null
     }
     property bool isCurrentItemNull
diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
index f8c7de6635..bec61eaccb 100644
--- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
+++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp
@@ -208,10 +208,11 @@ private slots:
     void parentBinding();
     void defaultHighlightMoveDuration();
     void accessEmptyCurrentItem_QTBUG_30227();
+    void delayedChanges_QTBUG_30555();
 
 private:
-    template <class T> void items(const QUrl &source, bool forceLayout);
-    template <class T> void changed(const QUrl &source, bool forceLayout);
+    template <class T> void items(const QUrl &source);
+    template <class T> void changed(const QUrl &source);
     template <class T> void inserted(const QUrl &source);
     template <class T> void inserted_more(QQuickItemView::VerticalLayoutDirection verticalLayoutDirection = QQuickItemView::TopToBottom);
     template <class T> void removed(const QUrl &source, bool animated);
@@ -329,7 +330,7 @@ void tst_QQuickListView::cleanupTestCase()
 }
 
 template <class T>
-void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
+void tst_QQuickListView::items(const QUrl &source)
 {
     QQuickView *window = createView();
 
@@ -349,6 +350,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
 
     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
@@ -359,6 +361,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
     QTRY_VERIFY(listview->highlightItem() != 0);
     QTRY_COMPARE(listview->count(), model.count());
     QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
+    listview->forceLayout();
     QTRY_COMPARE(contentItem->childItems().count(), model.count()+1); // assumes all are visible, +1 for the (default) highlight item
 
     // current item should be first item
@@ -398,8 +401,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
     ctxt->setContextProperty("testModel", &model2);
 
     // Force a layout, necessary if ListView is completed before VisualDataModel.
-    if (forceLayout)
-        QCOMPARE(listview->property("count").toInt(), 0);
+    listview->forceLayout();
 
     int itemCount = findItems<QQuickItem>(contentItem, "wrapper").count();
     QTRY_VERIFY(itemCount == 0);
@@ -413,7 +415,7 @@ void tst_QQuickListView::items(const QUrl &source, bool forceLayout)
 
 
 template <class T>
-void tst_QQuickListView::changed(const QUrl &source, bool forceLayout)
+void tst_QQuickListView::changed(const QUrl &source)
 {
     QQuickView *window = createView();
 
@@ -431,15 +433,15 @@ void tst_QQuickListView::changed(const QUrl &source, bool forceLayout)
     window->setSource(source);
     qApp->processEvents();
 
-    QQuickFlickable *listview = findItem<QQuickFlickable>(window->rootObject(), "list");
+    QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
     // Force a layout, necessary if ListView is completed before VisualDataModel.
-    if (forceLayout)
-        QCOMPARE(listview->property("count").toInt(), model.count());
+    listview->forceLayout();
 
     model.modifyItem(1, "Will", "9876");
     QQuickText *name = findItem<QQuickText>(contentItem, "textName", 1);
@@ -579,22 +581,24 @@ void tst_QQuickListView::inserted_more(QQuickItemView::VerticalLayoutDirection v
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
-    bool waitForPolish = (contentY != 0);
     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
         listview->setVerticalLayoutDirection(verticalLayoutDirection);
         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
         contentY = -listview->height() - contentY;
     }
     listview->setContentY(contentY);
-    if (waitForPolish)
-        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
 
     QList<QPair<QString, QString> > newData;
     for (int i=0; i<insertCount; i++)
         newData << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.insertItems(insertIndex, newData);
+
+    //Wait for polish (updates list to the model changes)
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
     QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
+
     // check visibleItems.first() is in correct position
     QQuickItem *item0 = findItem<QQuickItem>(contentItem, "wrapper", 0);
     QVERIFY(item0);
@@ -1019,17 +1023,17 @@ void tst_QQuickListView::removed_more(const QUrl &source, QQuickItemView::Vertic
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
-    bool waitForPolish = (contentY != 0);
     if (verticalLayoutDirection == QQuickItemView::BottomToTop) {
         listview->setVerticalLayoutDirection(verticalLayoutDirection);
         QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
         contentY = -listview->height() - contentY;
     }
     listview->setContentY(contentY);
-    if (waitForPolish)
-        QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
 
     model.removeItems(removeIndex, removeCount);
+    //Wait for polish (updates list to the model changes)
+    QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
+
     QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
     // check visibleItems.first() is in correct position
@@ -1219,6 +1223,7 @@ void tst_QQuickListView::clear(const QUrl &source, QQuickItemView::VerticalLayou
 
     // confirm sanity when adding an item to cleared list
     model.addItem("New", "1");
+    listview->forceLayout();
     QTRY_VERIFY(listview->count() == 1);
     QVERIFY(listview->currentItem() != 0);
     QVERIFY(listview->currentIndex() == 0);
@@ -1945,6 +1950,7 @@ void tst_QQuickListView::sections(const QUrl &source)
 
     // Remove section boundary
     model.removeItem(5);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     // New section header created
@@ -1953,6 +1959,7 @@ void tst_QQuickListView::sections(const QUrl &source)
     QTRY_COMPARE(item->height(), 40.0);
 
     model.insertItem(3, "New Item", "0");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     // Section header moved
@@ -1966,6 +1973,7 @@ void tst_QQuickListView::sections(const QUrl &source)
 
     // insert item which will become a section header
     model.insertItem(6, "Replace header", "1");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     item = findItem<QQuickItem>(contentItem, "wrapper", 6);
@@ -2061,6 +2069,7 @@ void tst_QQuickListView::sectionsDelegate()
 
     // remove section boundary
     model.removeItem(5);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     for (int i = 0; i < 3; ++i) {
         QQuickItem *item = findItem<QQuickItem>(contentItem,
@@ -2288,6 +2297,7 @@ void tst_QQuickListView::sectionsPositioning()
     listview->setContentY(120);
     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
     model.removeItem(5);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     for (int i = 1; i < 3; ++i) {
         QQuickItem *item = findVisibleChild(contentItem,
@@ -2485,7 +2495,8 @@ void tst_QQuickListView::currentIndex_delayedItemCreation()
     QTRY_VERIFY(contentItem != 0);
 
     QSignalSpy spy(listview, SIGNAL(currentItemChanged()));
-    QCOMPARE(listview->currentIndex(), 0);
+    //QCOMPARE(listview->currentIndex(), 0);
+    listview->forceLayout();
     QTRY_COMPARE(spy.count(), 1);
 
     releaseView(window);
@@ -2536,6 +2547,7 @@ void tst_QQuickListView::currentIndex()
     for (int i = 0; i < 30; i++)
         model.addItem("Item" + QString::number(i), QString::number(i));
     ctxt->setContextProperty("testModel", &model);
+    listview->forceLayout();
 
     QCOMPARE(listview->currentIndex(), 0);
     QCOMPARE(listview->currentItem(), findItem<QQuickItem>(contentItem, "wrapper", 0));
@@ -2679,6 +2691,7 @@ void tst_QQuickListView::keyNavigation()
         QTRY_COMPARE(listview->currentIndex(), i+1);
     }
     QTest::keyRelease(window, forwardsKey);
+    listview->forceLayout();
     QTRY_COMPARE(listview->currentIndex(), model.count()-1);
     QTRY_COMPARE(listview->contentX(), contentPosAtLastItem.x());
     QTRY_COMPARE(listview->contentY(), contentPosAtLastItem.y());
@@ -2689,6 +2702,7 @@ void tst_QQuickListView::keyNavigation()
         QTRY_COMPARE(listview->currentIndex(), i-1);
     }
     QTest::keyRelease(window, backwardsKey);
+    listview->forceLayout();
     QTRY_COMPARE(listview->currentIndex(), 0);
     QTRY_COMPARE(listview->contentX(), contentPosAtFirstItem.x());
     QTRY_COMPARE(listview->contentY(), contentPosAtFirstItem.y());
@@ -3123,6 +3137,7 @@ void tst_QQuickListView::resetModel()
     strings << "four" << "five" << "six" << "seven";
     model.setStringList(strings);
 
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.rowCount());
 
     for (int i = 0; i < model.rowCount(); ++i) {
@@ -3470,6 +3485,7 @@ void tst_QQuickListView::header()
     QCOMPARE(item->position(), firstDelegatePos);
 
     model.clear();
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     QCOMPARE(header->position(), initialHeaderPos); // header should stay where it is
     if (orientation == QQuickListView::Vertical)
@@ -3865,6 +3881,7 @@ void tst_QQuickListView::extents()
     QCOMPARE(listview->originY(), origin_empty.y());
     for (int i=0; i<30; i++)
         model.addItem("Item" + QString::number(i), "");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     QCOMPARE(listview->originX(), origin_nonEmpty.x());
     QCOMPARE(listview->originY(), origin_nonEmpty.y());
@@ -4262,6 +4279,7 @@ void tst_QQuickListView::resizeFirstDelegate()
 
     for (int i = 0; i < 10; i++)
         model.addItem("Item" + QString::number(i), "");
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
 
     item = findItem<QQuickItem>(contentItem, "wrapper", 1);
@@ -4461,6 +4479,7 @@ void tst_QQuickListView::indexAt_itemAt()
 void tst_QQuickListView::incrementalModel()
 {
     QQuickView *window = createView();
+    QSKIP("QTBUG-30716");
 
     IncrementalModel model;
     QQmlContext *ctxt = window->rootContext();
@@ -4471,14 +4490,17 @@ void tst_QQuickListView::incrementalModel()
 
     QQuickListView *listview = findItem<QQuickListView>(window->rootObject(), "list");
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QQuickItem *contentItem = listview->contentItem();
     QTRY_VERIFY(contentItem != 0);
 
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), 20);
 
     listview->positionViewAtIndex(10, QQuickListView::Beginning);
 
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), 25);
 
     delete window;
@@ -4503,18 +4525,19 @@ void tst_QQuickListView::onAdd()
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
 
-    QObject *object = window->rootObject();
-    object->setProperty("width", window->width());
-    object->setProperty("height", window->height());
+    QQuickListView* listview = qobject_cast<QQuickListView*>(window->rootObject());
+    listview->setProperty("width", window->width());
+    listview->setProperty("height", window->height());
     qApp->processEvents();
 
     QList<QPair<QString, QString> > items;
     for (int i=0; i<itemsToAdd; i++)
         items << qMakePair(QString("value %1").arg(i), QString::number(i));
     model.addItems(items);
-    QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
+    listview->forceLayout();
+    QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
-    QVariantList result = object->property("addedDelegates").toList();
+    QVariantList result = listview->property("addedDelegates").toList();
     QCOMPARE(result.count(), items.count());
     for (int i=0; i<items.count(); i++)
         QCOMPARE(result[i].toString(), items[i].first);
@@ -4557,12 +4580,13 @@ void tst_QQuickListView::onRemove()
     ctxt->setContextProperty("delegateHeight", delegateHeight);
     window->setSource(testFileUrl("attachedSignals.qml"));
 
-    QObject *object = window->rootObject();
+    QQuickListView *listview = qobject_cast<QQuickListView *>(window->rootObject());
 
     model.removeItems(indexToRemove, removeCount);
-    QTRY_COMPARE(window->rootObject()->property("count").toInt(), model.count());
+    listview->forceLayout();
+    QTRY_COMPARE(listview->property("count").toInt(), model.count());
 
-    QCOMPARE(object->property("removedDelegateCount"), QVariant(removeCount));
+    QCOMPARE(listview->property("removedDelegateCount"), QVariant(removeCount));
 
     releaseView(window);
 }
@@ -4741,6 +4765,7 @@ void tst_QQuickListView::margins()
     // and originY is updated
     listview->setContentY(100);
     model.removeItem(1);
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), model.count());
     listview->setContentY(-50);
     QTRY_COMPARE(QQuickItemPrivate::get(listview)->polishScheduled, false);
@@ -4979,22 +5004,22 @@ void tst_QQuickListView::snapToItem()
 
 void tst_QQuickListView::qAbstractItemModel_package_items()
 {
-    items<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
+    items<QaimModel>(testFileUrl("listviewtest-package.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_items()
 {
-    items<QaimModel>(testFileUrl("listviewtest.qml"), false);
+    items<QaimModel>(testFileUrl("listviewtest.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_package_changed()
 {
-    changed<QaimModel>(testFileUrl("listviewtest-package.qml"), true);
+    changed<QaimModel>(testFileUrl("listviewtest-package.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_changed()
 {
-    changed<QaimModel>(testFileUrl("listviewtest.qml"), false);
+    changed<QaimModel>(testFileUrl("listviewtest.qml"));
 }
 
 void tst_QQuickListView::qAbstractItemModel_package_inserted()
@@ -5555,6 +5580,7 @@ void tst_QQuickListView::populateTransitions()
 
     // clear the model
     window->rootContext()->setContextProperty("testModel", QVariant());
+    listview->forceLayout();
     QTRY_COMPARE(listview->count(), 0);
     QTRY_COMPARE(findItems<QQuickItem>(contentItem, "wrapper").count(), 0);
     listview->setProperty("countPopulateTransitions", 0);
@@ -5683,6 +5709,7 @@ void tst_QQuickListView::addTransitions()
     if (!newData.isEmpty()) {
         model.insertItems(insertionIndex, newData);
         QTRY_COMPARE(model.count(), listview->count());
+        listview->forceLayout();
     }
 
     QList<QQuickItem *> targetItems = findItems<QQuickItem>(contentItem, "wrapper", targetIndexes);
@@ -6291,6 +6318,7 @@ void tst_QQuickListView::displacedTransitions()
         case ListChange::Polish:
             break;
     }
+    listview->forceLayout();
 
     QVariantList resultTargetIndexes = listview->property("displacedTargetIndexes").toList();
     QVariantList resultTargetItems = listview->property("displacedTargetItems").toList();
@@ -6539,6 +6567,7 @@ void tst_QQuickListView::multipleTransitions()
                 break;
         }
     }
+    listview->forceLayout();
     QCOMPARE(listview->count(), model.count());
 
     // verify all items moved to the correct final positions
@@ -6822,6 +6851,7 @@ void tst_QQuickListView::accessEmptyCurrentItem_QTBUG_30227()
 
     QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
     QTRY_VERIFY(listview != 0);
+    listview->forceLayout();
 
     QMetaObject::invokeMethod(window->rootObject(), "remove");
     QVERIFY(window->rootObject()->property("isCurrentItemNull").toBool());
@@ -6830,6 +6860,26 @@ void tst_QQuickListView::accessEmptyCurrentItem_QTBUG_30227()
     QVERIFY(!window->rootObject()->property("isCurrentItemNull").toBool());
 }
 
+void tst_QQuickListView::delayedChanges_QTBUG_30555()
+{
+    QQuickView *window = createView();
+    window->setSource(testFileUrl("delayedChanges.qml"));
+
+    QQuickListView *listview = window->rootObject()->findChild<QQuickListView*>();
+    QTRY_VERIFY(listview != 0);
+
+    QCOMPARE(listview->count(), 10);
+
+    //Takes two just like in the bug report
+    QMetaObject::invokeMethod(window->rootObject(), "takeTwo");
+    QTRY_COMPARE(listview->count(), 8);
+
+    QMetaObject::invokeMethod(window->rootObject(), "takeTwo_sync");
+    QCOMPARE(listview->count(), 6);
+
+    delete window;
+}
+
 QTEST_MAIN(tst_QQuickListView)
 
 #include "tst_qquicklistview.moc"
diff --git a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp
index d16bf81d88..d374d71280 100644
--- a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp
+++ b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp
@@ -615,6 +615,7 @@ void tst_qquickvisualdatamodel::childChanged()
     vdm->setRootIndex(QVariant::fromValue(model.indexFromItem(model.item(1,0))));
     QCOMPARE(listview->count(), 1);
 
+    listview->forceLayout();
     QQuickText *name = findItem<QQuickText>(contentItem, "display", 0);
     QVERIFY(name);
     QCOMPARE(name->text(), QString("Row 2 Child Item"));
@@ -628,6 +629,7 @@ void tst_qquickvisualdatamodel::childChanged()
     model.item(1,0)->appendRow(new QStandardItem(QLatin1String("Row 2 Child Item 2")));
     QCOMPARE(listview->count(), 2);
 
+    listview->forceLayout();
     name = findItem<QQuickText>(contentItem, "display", 1);
     QVERIFY(name != 0);
     QCOMPARE(name->text(), QString("Row 2 Child Item 2"));
@@ -638,6 +640,8 @@ void tst_qquickvisualdatamodel::childChanged()
 
     vdm->setRootIndex(QVariant::fromValue(QModelIndex()));
     QCOMPARE(listview->count(), 3);
+
+    listview->forceLayout();
     name = findItem<QQuickText>(contentItem, "display", 0);
     QVERIFY(name);
     QCOMPARE(name->text(), QString("Row 1 Item"));
@@ -989,6 +993,8 @@ void tst_qquickvisualdatamodel::packagesDestroyed()
     QQuickItem *rightContent = rightview->contentItem();
     QTRY_VERIFY(rightContent != 0);
 
+    leftview->forceLayout();
+    rightview->forceLayout();
     QCOMPARE(leftview->currentIndex(), 0);
     QCOMPARE(rightview->currentIndex(), 0);
 
@@ -3548,6 +3554,7 @@ void tst_qquickvisualdatamodel::resolve()
     evaluate<void>(visualModel, setupExpression);
     QCOMPARE(evaluate<int>(listView, "count"), unresolvedCount);
 
+    listView->forceLayout();
     evaluate<void>(visualModel, resolveExpression);
 
     QCOMPARE(evaluate<int>(listView, "count"), inItems ? visualCount : modelCount);
-- 
GitLab