1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include <QtTest/QtTest>
43 #include <QtTest/QSignalSpy>
44 #include <private/qlistmodelinterface_p.h>
45 #include <QtQml/qqmlengine.h>
46 #include <QtQuick/qquickview.h>
47 #include <QtQml/qqmlcontext.h>
48 #include <QtQml/qqmlexpression.h>
49 #include <QtQml/qqmlincubator.h>
50 #include <private/qquickrepeater_p.h>
51 #include <QtQuick/private/qquicktext_p.h>
53 #include "../../shared/util.h"
54 #include "../shared/viewtestutil.h"
55 #include "../shared/visualtestutil.h"
57 using namespace QQuickViewTestUtil;
58 using namespace QQuickVisualTestUtil;
61 class tst_QQuickRepeater : public QQmlDataTest
71 void dataModel_adding();
72 void dataModel_removing();
73 void dataModel_changes();
80 void dynamicModelCrash();
83 class TestObject : public QObject
87 Q_PROPERTY(bool error READ error WRITE setError)
88 Q_PROPERTY(bool useModel READ useModel NOTIFY useModelChanged)
91 TestObject() : QObject(), mError(true), mUseModel(false) {}
93 bool error() const { return mError; }
94 void setError(bool err) { mError = err; }
96 bool useModel() const { return mUseModel; }
97 void setUseModel(bool use) { mUseModel = use; emit useModelChanged(); }
100 void useModelChanged();
107 tst_QQuickRepeater::tst_QQuickRepeater()
111 void tst_QQuickRepeater::numberModel()
113 QQuickView *canvas = createView();
115 QQmlContext *ctxt = canvas->rootContext();
116 ctxt->setContextProperty("testData", 5);
117 TestObject *testObject = new TestObject;
118 ctxt->setContextProperty("testObject", testObject);
120 canvas->setSource(testFileUrl("intmodel.qml"));
121 qApp->processEvents();
123 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
124 QVERIFY(repeater != 0);
125 QCOMPARE(repeater->parentItem()->childItems().count(), 5+1);
127 QVERIFY(!repeater->itemAt(-1));
128 for (int i=0; i<repeater->count(); i++)
129 QCOMPARE(repeater->itemAt(i), repeater->parentItem()->childItems().at(i));
130 QVERIFY(!repeater->itemAt(repeater->count()));
132 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
133 QVERIFY(testObject->error() == false);
139 class MyObject : public QObject
142 Q_PROPERTY(int idx READ idx CONSTANT)
144 MyObject(int i) : QObject(), m_idx(i) {}
146 int idx() const { return m_idx; }
151 void tst_QQuickRepeater::objectList()
153 QQuickView *canvas = createView();
155 for (int i=0; i<100; i++)
156 data << new MyObject(i);
158 QQmlContext *ctxt = canvas->rootContext();
159 ctxt->setContextProperty("testData", QVariant::fromValue(data));
161 canvas->setSource(testFileUrl("objlist.qml"));
162 qApp->processEvents();
164 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
165 QVERIFY(repeater != 0);
166 QCOMPARE(repeater->property("errors").toInt(), 0);//If this fails either they are out of order or can't find the object's data
167 QCOMPARE(repeater->property("instantiated").toInt(), 100);
169 QVERIFY(!repeater->itemAt(-1));
170 for (int i=0; i<data.count(); i++)
171 QCOMPARE(repeater->itemAt(i), repeater->parentItem()->childItems().at(i));
172 QVERIFY(!repeater->itemAt(data.count()));
174 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
175 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
176 ctxt->setContextProperty("testData", QVariant::fromValue(data));
177 QCOMPARE(addedSpy.count(), data.count());
178 QCOMPARE(removedSpy.count(), data.count());
185 The Repeater element creates children at its own position in its parent's
186 stacking order. In this test we insert a repeater between two other Text
187 elements to test this.
189 void tst_QQuickRepeater::stringList()
191 QQuickView *canvas = createView();
199 QQmlContext *ctxt = canvas->rootContext();
200 ctxt->setContextProperty("testData", data);
202 canvas->setSource(testFileUrl("repeater1.qml"));
203 qApp->processEvents();
205 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
206 QVERIFY(repeater != 0);
208 QQuickItem *container = findItem<QQuickItem>(canvas->rootObject(), "container");
209 QVERIFY(container != 0);
211 QCOMPARE(container->childItems().count(), data.count() + 3);
213 bool saw_repeater = false;
214 for (int i = 0; i < container->childItems().count(); ++i) {
217 QQuickText *name = qobject_cast<QQuickText*>(container->childItems().at(i));
219 QCOMPARE(name->text(), QLatin1String("Zero"));
220 } else if (i == container->childItems().count() - 2) {
221 // The repeater itself
222 QQuickRepeater *rep = qobject_cast<QQuickRepeater*>(container->childItems().at(i));
223 QCOMPARE(rep, repeater);
226 } else if (i == container->childItems().count() - 1) {
227 QQuickText *name = qobject_cast<QQuickText*>(container->childItems().at(i));
229 QCOMPARE(name->text(), QLatin1String("Last"));
231 QQuickText *name = qobject_cast<QQuickText*>(container->childItems().at(i));
233 QCOMPARE(name->text(), data.at(i-1));
236 QVERIFY(saw_repeater);
241 void tst_QQuickRepeater::dataModel_adding()
243 QQuickView *canvas = createView();
244 QQmlContext *ctxt = canvas->rootContext();
245 TestObject *testObject = new TestObject;
246 ctxt->setContextProperty("testObject", testObject);
249 ctxt->setContextProperty("testData", &testModel);
250 canvas->setSource(testFileUrl("repeater2.qml"));
251 qApp->processEvents();
253 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
254 QVERIFY(repeater != 0);
255 QQuickItem *container = findItem<QQuickItem>(canvas->rootObject(), "container");
256 QVERIFY(container != 0);
258 QVERIFY(!repeater->itemAt(0));
260 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
261 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
263 // add to empty model
264 testModel.addItem("two", "2");
265 QCOMPARE(repeater->itemAt(0), container->childItems().at(0));
266 QCOMPARE(countSpy.count(), 1); countSpy.clear();
267 QCOMPARE(addedSpy.count(), 1);
268 QCOMPARE(addedSpy.at(0).at(0).toInt(), 0);
269 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(0));
273 testModel.insertItem(0, "one", "1");
274 QCOMPARE(repeater->itemAt(0), container->childItems().at(0));
275 QCOMPARE(countSpy.count(), 1); countSpy.clear();
276 QCOMPARE(addedSpy.count(), 1);
277 QCOMPARE(addedSpy.at(0).at(0).toInt(), 0);
278 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(0));
282 testModel.insertItem(2, "four", "4");
283 QCOMPARE(repeater->itemAt(2), container->childItems().at(2));
284 QCOMPARE(countSpy.count(), 1); countSpy.clear();
285 QCOMPARE(addedSpy.count(), 1);
286 QCOMPARE(addedSpy.at(0).at(0).toInt(), 2);
287 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(2));
291 testModel.insertItem(2, "three", "3");
292 QCOMPARE(repeater->itemAt(2), container->childItems().at(2));
293 QCOMPARE(countSpy.count(), 1); countSpy.clear();
294 QCOMPARE(addedSpy.count(), 1);
295 QCOMPARE(addedSpy.at(0).at(0).toInt(), 2);
296 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(2));
305 void tst_QQuickRepeater::dataModel_removing()
307 QQuickView *canvas = createView();
308 QQmlContext *ctxt = canvas->rootContext();
309 TestObject *testObject = new TestObject;
310 ctxt->setContextProperty("testObject", testObject);
313 testModel.addItem("one", "1");
314 testModel.addItem("two", "2");
315 testModel.addItem("three", "3");
316 testModel.addItem("four", "4");
317 testModel.addItem("five", "5");
319 ctxt->setContextProperty("testData", &testModel);
320 canvas->setSource(testFileUrl("repeater2.qml"));
321 qApp->processEvents();
323 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
324 QVERIFY(repeater != 0);
325 QQuickItem *container = findItem<QQuickItem>(canvas->rootObject(), "container");
326 QVERIFY(container != 0);
327 QCOMPARE(container->childItems().count(), repeater->count()+1);
329 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
330 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
333 QQuickItem *item = repeater->itemAt(0);
334 QCOMPARE(item, container->childItems().at(0));
336 testModel.removeItem(0);
337 QVERIFY(repeater->itemAt(0) != item);
338 QCOMPARE(countSpy.count(), 1); countSpy.clear();
339 QCOMPARE(removedSpy.count(), 1);
340 QCOMPARE(removedSpy.at(0).at(0).toInt(), 0);
341 QCOMPARE(removedSpy.at(0).at(1).value<QQuickItem*>(), item);
345 int lastIndex = testModel.count()-1;
346 item = repeater->itemAt(lastIndex);
347 QCOMPARE(item, container->childItems().at(lastIndex));
349 testModel.removeItem(lastIndex);
350 QVERIFY(repeater->itemAt(lastIndex) != item);
351 QCOMPARE(countSpy.count(), 1); countSpy.clear();
352 QCOMPARE(removedSpy.count(), 1);
353 QCOMPARE(removedSpy.at(0).at(0).toInt(), lastIndex);
354 QCOMPARE(removedSpy.at(0).at(1).value<QQuickItem*>(), item);
357 // remove from middle
358 item = repeater->itemAt(1);
359 QCOMPARE(item, container->childItems().at(1));
361 testModel.removeItem(1);
362 QVERIFY(repeater->itemAt(lastIndex) != item);
363 QCOMPARE(countSpy.count(), 1); countSpy.clear();
364 QCOMPARE(removedSpy.count(), 1);
365 QCOMPARE(removedSpy.at(0).at(0).toInt(), 1);
366 QCOMPARE(removedSpy.at(0).at(1).value<QQuickItem*>(), item);
373 void tst_QQuickRepeater::dataModel_changes()
375 QQuickView *canvas = createView();
376 QQmlContext *ctxt = canvas->rootContext();
377 TestObject *testObject = new TestObject;
378 ctxt->setContextProperty("testObject", testObject);
381 testModel.addItem("one", "1");
382 testModel.addItem("two", "2");
383 testModel.addItem("three", "3");
385 ctxt->setContextProperty("testData", &testModel);
386 canvas->setSource(testFileUrl("repeater2.qml"));
387 qApp->processEvents();
389 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
390 QVERIFY(repeater != 0);
391 QQuickItem *container = findItem<QQuickItem>(canvas->rootObject(), "container");
392 QVERIFY(container != 0);
393 QCOMPARE(container->childItems().count(), repeater->count()+1);
395 // Check that model changes are propagated
396 QQuickText *text = findItem<QQuickText>(canvas->rootObject(), "myName", 1);
398 QCOMPARE(text->text(), QString("two"));
400 testModel.modifyItem(1, "Item two", "_2");
401 text = findItem<QQuickText>(canvas->rootObject(), "myName", 1);
403 QCOMPARE(text->text(), QString("Item two"));
405 text = findItem<QQuickText>(canvas->rootObject(), "myNumber", 1);
407 QCOMPARE(text->text(), QString("_2"));
413 void tst_QQuickRepeater::itemModel()
415 QQuickView *canvas = createView();
416 QQmlContext *ctxt = canvas->rootContext();
417 TestObject *testObject = new TestObject;
418 ctxt->setContextProperty("testObject", testObject);
420 canvas->setSource(testFileUrl("itemlist.qml"));
421 qApp->processEvents();
423 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
424 QVERIFY(repeater != 0);
426 QQuickItem *container = findItem<QQuickItem>(canvas->rootObject(), "container");
427 QVERIFY(container != 0);
429 QCOMPARE(container->childItems().count(), 1);
431 testObject->setUseModel(true);
432 QMetaObject::invokeMethod(canvas->rootObject(), "checkProperties");
433 QVERIFY(testObject->error() == false);
435 QCOMPARE(container->childItems().count(), 4);
436 QVERIFY(qobject_cast<QObject*>(container->childItems().at(0))->objectName() == "item1");
437 QVERIFY(qobject_cast<QObject*>(container->childItems().at(1))->objectName() == "item2");
438 QVERIFY(qobject_cast<QObject*>(container->childItems().at(2))->objectName() == "item3");
439 QVERIFY(container->childItems().at(3) == repeater);
441 QMetaObject::invokeMethod(canvas->rootObject(), "switchModel");
442 QCOMPARE(container->childItems().count(), 3);
443 QVERIFY(qobject_cast<QObject*>(container->childItems().at(0))->objectName() == "item4");
444 QVERIFY(qobject_cast<QObject*>(container->childItems().at(1))->objectName() == "item5");
445 QVERIFY(container->childItems().at(2) == repeater);
447 testObject->setUseModel(false);
448 QCOMPARE(container->childItems().count(), 1);
454 void tst_QQuickRepeater::resetModel()
456 QQuickView *canvas = createView();
459 for (int i=0; i<10; i++)
460 dataA << QString::number(i);
462 QQmlContext *ctxt = canvas->rootContext();
463 ctxt->setContextProperty("testData", dataA);
464 canvas->setSource(testFileUrl("repeater1.qml"));
465 qApp->processEvents();
466 QQuickRepeater *repeater = findItem<QQuickRepeater>(canvas->rootObject(), "repeater");
467 QVERIFY(repeater != 0);
468 QQuickItem *container = findItem<QQuickItem>(canvas->rootObject(), "container");
469 QVERIFY(container != 0);
471 QCOMPARE(repeater->count(), dataA.count());
472 for (int i=0; i<repeater->count(); i++)
473 QCOMPARE(repeater->itemAt(i), container->childItems().at(i+1)); // +1 to skip first Text object
475 QSignalSpy modelChangedSpy(repeater, SIGNAL(modelChanged()));
476 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
477 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
478 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
481 for (int i=0; i<20; i++)
482 dataB << QString::number(i);
484 // reset context property
485 ctxt->setContextProperty("testData", dataB);
486 QCOMPARE(repeater->count(), dataB.count());
488 QCOMPARE(modelChangedSpy.count(), 1);
489 QCOMPARE(countSpy.count(), 1);
490 QCOMPARE(removedSpy.count(), dataA.count());
491 QCOMPARE(addedSpy.count(), dataB.count());
492 for (int i=0; i<dataB.count(); i++) {
493 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
494 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
496 modelChangedSpy.clear();
501 // reset via setModel()
502 repeater->setModel(dataA);
503 QCOMPARE(repeater->count(), dataA.count());
505 QCOMPARE(modelChangedSpy.count(), 1);
506 QCOMPARE(countSpy.count(), 1);
507 QCOMPARE(removedSpy.count(), dataB.count());
508 QCOMPARE(addedSpy.count(), dataA.count());
509 for (int i=0; i<dataA.count(); i++) {
510 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
511 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
514 modelChangedSpy.clear();
523 void tst_QQuickRepeater::modelChanged()
526 QQmlComponent component(&engine, testFileUrl("modelChanged.qml"));
528 QQuickItem *rootObject = qobject_cast<QQuickItem*>(component.create());
530 QQuickRepeater *repeater = findItem<QQuickRepeater>(rootObject, "repeater");
533 repeater->setModel(4);
534 QCOMPARE(repeater->count(), 4);
535 QCOMPARE(repeater->property("itemsCount").toInt(), 4);
536 QCOMPARE(repeater->property("itemsFound").toList().count(), 4);
538 repeater->setModel(10);
539 QCOMPARE(repeater->count(), 10);
540 QCOMPARE(repeater->property("itemsCount").toInt(), 10);
541 QCOMPARE(repeater->property("itemsFound").toList().count(), 10);
546 void tst_QQuickRepeater::properties()
549 QQmlComponent component(&engine, testFileUrl("properties.qml"));
551 QQuickItem *rootObject = qobject_cast<QQuickItem*>(component.create());
554 QQuickRepeater *repeater = findItem<QQuickRepeater>(rootObject, "repeater");
557 QSignalSpy modelSpy(repeater, SIGNAL(modelChanged()));
558 repeater->setModel(3);
559 QCOMPARE(modelSpy.count(),1);
560 repeater->setModel(3);
561 QCOMPARE(modelSpy.count(),1);
563 QSignalSpy delegateSpy(repeater, SIGNAL(delegateChanged()));
565 QQmlComponent rectComponent(&engine);
566 rectComponent.setData("import QtQuick 2.0; Rectangle {}", QUrl::fromLocalFile(""));
568 repeater->setDelegate(&rectComponent);
569 QCOMPARE(delegateSpy.count(),1);
570 repeater->setDelegate(&rectComponent);
571 QCOMPARE(delegateSpy.count(),1);
576 void tst_QQuickRepeater::asynchronous()
578 QQuickView *canvas = createView();
580 QQmlIncubationController controller;
581 canvas->engine()->setIncubationController(&controller);
583 canvas->setSource(testFileUrl("asyncloader.qml"));
585 QQuickItem *rootObject = qobject_cast<QQuickItem*>(canvas->rootObject());
588 QQuickItem *container = findItem<QQuickItem>(rootObject, "container");
592 controller.incubateWhile(&b);
593 container = findItem<QQuickItem>(rootObject, "container");
596 QQuickRepeater *repeater = 0;
599 controller.incubateWhile(&b);
600 repeater = findItem<QQuickRepeater>(rootObject, "repeater");
603 // items will be created one at a time
604 for (int i = 0; i < 10; ++i) {
605 QString name("delegate");
606 name += QString::number(i);
607 QVERIFY(findItem<QQuickItem>(container, name) == 0);
608 QQuickItem *item = 0;
611 controller.incubateWhile(&b);
612 item = findItem<QQuickItem>(container, name);
618 controller.incubateWhile(&b);
621 // verify positioning
622 for (int i = 0; i < 10; ++i) {
623 QString name("delegate");
624 name += QString::number(i);
625 QQuickItem *item = findItem<QQuickItem>(container, name);
626 QTRY_COMPARE(item->y(), i * 50.0);
632 void tst_QQuickRepeater::initParent()
635 QQmlComponent component(&engine, testFileUrl("initparent.qml"));
637 QQuickItem *rootObject = qobject_cast<QQuickItem*>(component.create());
640 QCOMPARE(qvariant_cast<QQuickItem*>(rootObject->property("parentItem")), rootObject);
643 void tst_QQuickRepeater::dynamicModelCrash()
646 QQmlComponent component(&engine, testFileUrl("dynamicmodelcrash.qml"));
649 QQuickItem *rootObject = qobject_cast<QQuickItem*>(component.create());
652 QQuickRepeater *repeater = findItem<QQuickRepeater>(rootObject, "rep");
654 QVERIFY(qvariant_cast<QObject *>(repeater->model()) == 0);
657 QTEST_MAIN(tst_QQuickRepeater)
659 #include "tst_qquickrepeater.moc"