1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtTest/QSignalSpy>
31#include <QtQml/qqmlengine.h>
32#include <QtQuick/qquickview.h>
33#include <QtQml/qqmlcontext.h>
34#include <QtQml/qqmlexpression.h>
35#include <QtQml/qqmlincubator.h>
36#include <private/qquickrepeater_p.h>
37#include <QtQuick/private/qquicktext_p.h>
38#include <QtQmlModels/private/qqmllistmodel_p.h>
39#include <QtQmlModels/private/qqmlobjectmodel_p.h>
40#include <QtGui/qstandarditemmodel.h>
41
42#include "../../shared/util.h"
43#include "../shared/viewtestutil.h"
44#include "../shared/visualtestutil.h"
45
46using namespace QQuickViewTestUtil;
47using namespace QQuickVisualTestUtil;
48
49
50class tst_QQuickRepeater : public QQmlDataTest
51{
52 Q_OBJECT
53public:
54 tst_QQuickRepeater();
55
56private slots:
57 void numberModel();
58 void objectList_data();
59 void objectList();
60 void stringList();
61 void dataModel_adding();
62 void dataModel_removing();
63 void dataModel_changes();
64 void itemModel();
65 void resetModel();
66 void modelChanged();
67 void modelReset();
68 void modelCleared();
69 void properties();
70 void asynchronous();
71 void initParent();
72 void dynamicModelCrash();
73 void visualItemModelCrash();
74 void invalidContextCrash();
75 void jsArrayChange();
76 void clearRemovalOrder();
77 void destroyCount();
78 void stackingOrder();
79 void objectModel();
80 void QTBUG54859_asynchronousMove();
81 void package();
82 void ownership();
83 void requiredProperties();
84 void contextProperties();
85};
86
87class TestObject : public QObject
88{
89 Q_OBJECT
90
91 Q_PROPERTY(bool error READ error WRITE setError)
92 Q_PROPERTY(bool useModel READ useModel NOTIFY useModelChanged)
93
94public:
95 TestObject() : QObject(), mError(true), mUseModel(false) {}
96
97 bool error() const { return mError; }
98 void setError(bool err) { mError = err; }
99
100 bool useModel() const { return mUseModel; }
101 void setUseModel(bool use) { mUseModel = use; emit useModelChanged(); }
102
103signals:
104 void useModelChanged();
105
106private:
107 bool mError;
108 bool mUseModel;
109};
110
111tst_QQuickRepeater::tst_QQuickRepeater()
112{
113}
114
115void tst_QQuickRepeater::numberModel()
116{
117 QQuickView *window = createView();
118
119 QQmlContext *ctxt = window->rootContext();
120 ctxt->setContextProperty("testData", 5);
121 TestObject *testObject = new TestObject;
122 ctxt->setContextProperty("testObject", testObject);
123
124 window->setSource(testFileUrl(fileName: "intmodel.qml"));
125 qApp->processEvents();
126
127 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
128 QVERIFY(repeater != nullptr);
129 QCOMPARE(repeater->parentItem()->childItems().count(), 5+1);
130
131 QVERIFY(!repeater->itemAt(-1));
132 for (int i=0; i<repeater->count(); i++)
133 QCOMPARE(repeater->itemAt(i), repeater->parentItem()->childItems().at(i));
134 QVERIFY(!repeater->itemAt(repeater->count()));
135
136 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
137 QVERIFY(!testObject->error());
138
139 ctxt->setContextProperty("testData", std::numeric_limits<int>::max());
140 QCOMPARE(repeater->parentItem()->childItems().count(), 1);
141
142 ctxt->setContextProperty("testData", -1234);
143 QCOMPARE(repeater->parentItem()->childItems().count(), 1);
144
145 delete testObject;
146 delete window;
147}
148
149void tst_QQuickRepeater::objectList_data()
150{
151 QTest::addColumn<QUrl>(name: "filename");
152
153 QTest::newRow(dataTag: "normal") << testFileUrl(fileName: "objlist.qml");
154 QTest::newRow(dataTag: "required") << testFileUrl(fileName: "objlist_required.qml");
155}
156
157class MyObject : public QObject
158{
159 Q_OBJECT
160 Q_PROPERTY(int idx READ idx CONSTANT)
161public:
162 MyObject(int i) : QObject(), m_idx(i) {}
163
164 int idx() const { return m_idx; }
165
166 int m_idx;
167};
168
169void tst_QQuickRepeater::objectList()
170{
171 QFETCH(QUrl, filename);
172 QQuickView *window = createView();
173 QObjectList data;
174 for (int i=0; i<100; i++)
175 data << new MyObject(i);
176
177 QQmlContext *ctxt = window->rootContext();
178 ctxt->setContextProperty("testData", QVariant::fromValue(value: data));
179
180 window->setSource(filename);
181 qApp->processEvents();
182
183 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
184 QVERIFY(repeater != nullptr);
185 QCOMPARE(repeater->property("errors").toInt(), 0);//If this fails either they are out of order or can't find the object's data
186 QCOMPARE(repeater->property("instantiated").toInt(), 100);
187
188 QVERIFY(!repeater->itemAt(-1));
189 for (int i=0; i<data.count(); i++)
190 QCOMPARE(repeater->itemAt(i), repeater->parentItem()->childItems().at(i));
191 QVERIFY(!repeater->itemAt(data.count()));
192
193 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
194 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
195 ctxt->setContextProperty("testData", QVariant::fromValue(value: data));
196 QCOMPARE(addedSpy.count(), data.count());
197 QCOMPARE(removedSpy.count(), data.count());
198
199 qDeleteAll(c: data);
200 delete window;
201}
202
203/*
204The Repeater element creates children at its own position in its parent's
205stacking order. In this test we insert a repeater between two other Text
206elements to test this.
207*/
208void tst_QQuickRepeater::stringList()
209{
210 QQuickView *window = createView();
211
212 QStringList data;
213 data << "One";
214 data << "Two";
215 data << "Three";
216 data << "Four";
217
218 QQmlContext *ctxt = window->rootContext();
219 ctxt->setContextProperty("testData", data);
220
221 window->setSource(testFileUrl(fileName: "repeater1.qml"));
222 qApp->processEvents();
223
224 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
225 QVERIFY(repeater != nullptr);
226
227 QQuickItem *container = findItem<QQuickItem>(parent: window->rootObject(), objectName: "container");
228 QVERIFY(container != nullptr);
229
230 QCOMPARE(container->childItems().count(), data.count() + 3);
231
232 bool saw_repeater = false;
233 for (int i = 0; i < container->childItems().count(); ++i) {
234
235 if (i == 0) {
236 QQuickText *name = qobject_cast<QQuickText*>(object: container->childItems().at(i));
237 QVERIFY(name != nullptr);
238 QCOMPARE(name->text(), QLatin1String("Zero"));
239 } else if (i == container->childItems().count() - 2) {
240 // The repeater itself
241 QQuickRepeater *rep = qobject_cast<QQuickRepeater*>(object: container->childItems().at(i));
242 QCOMPARE(rep, repeater);
243 saw_repeater = true;
244 continue;
245 } else if (i == container->childItems().count() - 1) {
246 QQuickText *name = qobject_cast<QQuickText*>(object: container->childItems().at(i));
247 QVERIFY(name != nullptr);
248 QCOMPARE(name->text(), QLatin1String("Last"));
249 } else {
250 QQuickText *name = qobject_cast<QQuickText*>(object: container->childItems().at(i));
251 QVERIFY(name != nullptr);
252 QCOMPARE(name->text(), data.at(i-1));
253 }
254 }
255 QVERIFY(saw_repeater);
256
257 delete window;
258}
259
260void tst_QQuickRepeater::dataModel_adding()
261{
262 QQuickView *window = createView();
263 QQmlContext *ctxt = window->rootContext();
264 TestObject *testObject = new TestObject;
265 ctxt->setContextProperty("testObject", testObject);
266
267 QaimModel testModel;
268 ctxt->setContextProperty("testData", &testModel);
269 window->setSource(testFileUrl(fileName: "repeater2.qml"));
270 qApp->processEvents();
271
272 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
273 QVERIFY(repeater != nullptr);
274 QQuickItem *container = findItem<QQuickItem>(parent: window->rootObject(), objectName: "container");
275 QVERIFY(container != nullptr);
276
277 QVERIFY(!repeater->itemAt(0));
278
279 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
280 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
281
282 // add to empty model
283 testModel.addItem(name: "two", number: "2");
284 QCOMPARE(repeater->itemAt(0), container->childItems().at(0));
285 QCOMPARE(countSpy.count(), 1); countSpy.clear();
286 QCOMPARE(addedSpy.count(), 1);
287 QCOMPARE(addedSpy.at(0).at(0).toInt(), 0);
288 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(0));
289 addedSpy.clear();
290
291 // insert at start
292 testModel.insertItem(index: 0, name: "one", number: "1");
293 QCOMPARE(repeater->itemAt(0), container->childItems().at(0));
294 QCOMPARE(countSpy.count(), 1); countSpy.clear();
295 QCOMPARE(addedSpy.count(), 1);
296 QCOMPARE(addedSpy.at(0).at(0).toInt(), 0);
297 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(0));
298 addedSpy.clear();
299
300 // insert at end
301 testModel.insertItem(index: 2, name: "four", number: "4");
302 QCOMPARE(repeater->itemAt(2), container->childItems().at(2));
303 QCOMPARE(countSpy.count(), 1); countSpy.clear();
304 QCOMPARE(addedSpy.count(), 1);
305 QCOMPARE(addedSpy.at(0).at(0).toInt(), 2);
306 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(2));
307 addedSpy.clear();
308
309 // insert in middle
310 testModel.insertItem(index: 2, name: "three", number: "3");
311 QCOMPARE(repeater->itemAt(2), container->childItems().at(2));
312 QCOMPARE(countSpy.count(), 1); countSpy.clear();
313 QCOMPARE(addedSpy.count(), 1);
314 QCOMPARE(addedSpy.at(0).at(0).toInt(), 2);
315 QCOMPARE(addedSpy.at(0).at(1).value<QQuickItem*>(), container->childItems().at(2));
316 addedSpy.clear();
317
318 //insert in middle multiple
319 int childItemsSize = container->childItems().size();
320 QList<QPair<QString, QString> > multiData;
321 multiData << qMakePair(QStringLiteral("five"), QStringLiteral("5")) << qMakePair(QStringLiteral("six"), QStringLiteral("6")) << qMakePair(QStringLiteral("seven"), QStringLiteral("7"));
322 testModel.insertItems(index: 1, items: multiData);
323 QCOMPARE(countSpy.count(), 1);
324 QCOMPARE(addedSpy.count(), 3);
325 QCOMPARE(container->childItems().size(), childItemsSize + 3);
326 QCOMPARE(repeater->itemAt(2), container->childItems().at(2));
327 addedSpy.clear();
328 countSpy.clear();
329
330 delete testObject;
331 addedSpy.clear();
332 countSpy.clear();
333 delete window;
334}
335
336void tst_QQuickRepeater::dataModel_removing()
337{
338 QQuickView *window = createView();
339 QQmlContext *ctxt = window->rootContext();
340 TestObject *testObject = new TestObject;
341 ctxt->setContextProperty("testObject", testObject);
342
343 QaimModel testModel;
344 testModel.addItem(name: "one", number: "1");
345 testModel.addItem(name: "two", number: "2");
346 testModel.addItem(name: "three", number: "3");
347 testModel.addItem(name: "four", number: "4");
348 testModel.addItem(name: "five", number: "5");
349
350 ctxt->setContextProperty("testData", &testModel);
351 window->setSource(testFileUrl(fileName: "repeater2.qml"));
352 qApp->processEvents();
353
354 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
355 QVERIFY(repeater != nullptr);
356 QQuickItem *container = findItem<QQuickItem>(parent: window->rootObject(), objectName: "container");
357 QVERIFY(container != nullptr);
358 QCOMPARE(container->childItems().count(), repeater->count()+1);
359
360 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
361 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
362
363 // remove at start
364 QQuickItem *item = repeater->itemAt(index: 0);
365 QCOMPARE(item, container->childItems().at(0));
366
367 testModel.removeItem(index: 0);
368 QVERIFY(repeater->itemAt(0) != item);
369 QCOMPARE(countSpy.count(), 1); countSpy.clear();
370 QCOMPARE(removedSpy.count(), 1);
371 QCOMPARE(removedSpy.at(0).at(0).toInt(), 0);
372 QCOMPARE(removedSpy.at(0).at(1).value<QQuickItem*>(), item);
373 removedSpy.clear();
374
375 // remove at end
376 int lastIndex = testModel.count()-1;
377 item = repeater->itemAt(index: lastIndex);
378 QCOMPARE(item, container->childItems().at(lastIndex));
379
380 testModel.removeItem(index: lastIndex);
381 QVERIFY(repeater->itemAt(lastIndex) != item);
382 QCOMPARE(countSpy.count(), 1); countSpy.clear();
383 QCOMPARE(removedSpy.count(), 1);
384 QCOMPARE(removedSpy.at(0).at(0).toInt(), lastIndex);
385 QCOMPARE(removedSpy.at(0).at(1).value<QQuickItem*>(), item);
386 removedSpy.clear();
387
388 // remove from middle
389 item = repeater->itemAt(index: 1);
390 QCOMPARE(item, container->childItems().at(1));
391
392 testModel.removeItem(index: 1);
393 QVERIFY(repeater->itemAt(lastIndex) != item);
394 QCOMPARE(countSpy.count(), 1); countSpy.clear();
395 QCOMPARE(removedSpy.count(), 1);
396 QCOMPARE(removedSpy.at(0).at(0).toInt(), 1);
397 QCOMPARE(removedSpy.at(0).at(1).value<QQuickItem*>(), item);
398 removedSpy.clear();
399
400 delete testObject;
401 delete window;
402}
403
404void tst_QQuickRepeater::dataModel_changes()
405{
406 QQuickView *window = createView();
407 QQmlContext *ctxt = window->rootContext();
408 TestObject *testObject = new TestObject;
409 ctxt->setContextProperty("testObject", testObject);
410
411 QaimModel testModel;
412 testModel.addItem(name: "one", number: "1");
413 testModel.addItem(name: "two", number: "2");
414 testModel.addItem(name: "three", number: "3");
415
416 ctxt->setContextProperty("testData", &testModel);
417 window->setSource(testFileUrl(fileName: "repeater2.qml"));
418 qApp->processEvents();
419
420 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
421 QVERIFY(repeater != nullptr);
422 QQuickItem *container = findItem<QQuickItem>(parent: window->rootObject(), objectName: "container");
423 QVERIFY(container != nullptr);
424 QCOMPARE(container->childItems().count(), repeater->count()+1);
425
426 // Check that model changes are propagated
427 QQuickText *text = findItem<QQuickText>(parent: window->rootObject(), objectName: "myName", index: 1);
428 QVERIFY(text);
429 QCOMPARE(text->text(), QString("two"));
430
431 testModel.modifyItem(idx: 1, name: "Item two", number: "_2");
432 text = findItem<QQuickText>(parent: window->rootObject(), objectName: "myName", index: 1);
433 QVERIFY(text);
434 QCOMPARE(text->text(), QString("Item two"));
435
436 text = findItem<QQuickText>(parent: window->rootObject(), objectName: "myNumber", index: 1);
437 QVERIFY(text);
438 QCOMPARE(text->text(), QString("_2"));
439
440 delete testObject;
441 delete window;
442}
443
444void tst_QQuickRepeater::itemModel()
445{
446 QQuickView *window = createView();
447 QQmlContext *ctxt = window->rootContext();
448 TestObject *testObject = new TestObject;
449 ctxt->setContextProperty("testObject", testObject);
450
451 window->setSource(testFileUrl(fileName: "itemlist.qml"));
452 qApp->processEvents();
453
454 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
455 QVERIFY(repeater != nullptr);
456
457 QQuickItem *container = findItem<QQuickItem>(parent: window->rootObject(), objectName: "container");
458 QVERIFY(container != nullptr);
459
460 QCOMPARE(container->childItems().count(), 1);
461
462 testObject->setUseModel(true);
463 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
464 QVERIFY(!testObject->error());
465
466 QCOMPARE(container->childItems().count(), 4);
467 QCOMPARE(qobject_cast<QObject*>(container->childItems().at(0))->objectName(), QLatin1String("item1"));
468 QCOMPARE(qobject_cast<QObject*>(container->childItems().at(1))->objectName(), QLatin1String("item2"));
469 QCOMPARE(qobject_cast<QObject*>(container->childItems().at(2))->objectName(), QLatin1String("item3"));
470 QCOMPARE(container->childItems().at(3), repeater);
471
472 QMetaObject::invokeMethod(obj: window->rootObject(), member: "switchModel");
473 QCOMPARE(container->childItems().count(), 3);
474 QCOMPARE(qobject_cast<QObject*>(container->childItems().at(0))->objectName(), QLatin1String("item4"));
475 QCOMPARE(qobject_cast<QObject*>(container->childItems().at(1))->objectName(), QLatin1String("item5"));
476 QCOMPARE(container->childItems().at(2), repeater);
477
478 testObject->setUseModel(false);
479 QCOMPARE(container->childItems().count(), 1);
480
481 delete testObject;
482 delete window;
483}
484
485void tst_QQuickRepeater::resetModel()
486{
487 QQuickView *window = createView();
488
489 QStringList dataA;
490 for (int i=0; i<10; i++)
491 dataA << QString::number(i);
492
493 QQmlContext *ctxt = window->rootContext();
494 ctxt->setContextProperty("testData", dataA);
495 window->setSource(testFileUrl(fileName: "repeater1.qml"));
496 qApp->processEvents();
497 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: window->rootObject(), objectName: "repeater");
498 QVERIFY(repeater != nullptr);
499 QQuickItem *container = findItem<QQuickItem>(parent: window->rootObject(), objectName: "container");
500 QVERIFY(container != nullptr);
501
502 QCOMPARE(repeater->count(), dataA.count());
503 for (int i=0; i<repeater->count(); i++)
504 QCOMPARE(repeater->itemAt(i), container->childItems().at(i+1)); // +1 to skip first Text object
505
506 QSignalSpy modelChangedSpy(repeater, SIGNAL(modelChanged()));
507 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
508 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
509 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
510
511 QStringList dataB;
512 for (int i=0; i<20; i++)
513 dataB << QString::number(i);
514
515 // reset context property
516 ctxt->setContextProperty("testData", dataB);
517 QCOMPARE(repeater->count(), dataB.count());
518
519 QCOMPARE(modelChangedSpy.count(), 1);
520 QCOMPARE(countSpy.count(), 1);
521 QCOMPARE(removedSpy.count(), dataA.count());
522 QCOMPARE(addedSpy.count(), dataB.count());
523 for (int i=0; i<dataB.count(); i++) {
524 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
525 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
526 }
527 modelChangedSpy.clear();
528 countSpy.clear();
529 removedSpy.clear();
530 addedSpy.clear();
531
532 // reset via setModel()
533 repeater->setModel(dataA);
534 QCOMPARE(repeater->count(), dataA.count());
535
536 QCOMPARE(modelChangedSpy.count(), 1);
537 QCOMPARE(countSpy.count(), 1);
538 QCOMPARE(removedSpy.count(), dataB.count());
539 QCOMPARE(addedSpy.count(), dataA.count());
540 for (int i=0; i<dataA.count(); i++) {
541 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
542 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
543 }
544
545 modelChangedSpy.clear();
546 countSpy.clear();
547 removedSpy.clear();
548 addedSpy.clear();
549
550 delete window;
551}
552
553// QTBUG-17156
554void tst_QQuickRepeater::modelChanged()
555{
556 QQmlEngine engine;
557 QQmlComponent component(&engine, testFileUrl(fileName: "modelChanged.qml"));
558
559 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
560 QVERIFY(rootObject);
561 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
562 QVERIFY(repeater);
563
564 repeater->setModel(4);
565 QCOMPARE(repeater->count(), 4);
566 QCOMPARE(repeater->property("itemsCount").toInt(), 4);
567 QCOMPARE(repeater->property("itemsFound").toList().count(), 4);
568
569 repeater->setModel(10);
570 QCOMPARE(repeater->count(), 10);
571 QCOMPARE(repeater->property("itemsCount").toInt(), 10);
572 QCOMPARE(repeater->property("itemsFound").toList().count(), 10);
573
574 delete rootObject;
575}
576
577void tst_QQuickRepeater::modelReset()
578{
579 QaimModel model;
580
581 QQmlEngine engine;
582 QQmlContext *ctxt = engine.rootContext();
583 ctxt->setContextProperty("testData", &model);
584
585 QQmlComponent component(&engine, testFileUrl(fileName: "repeater2.qml"));
586 QScopedPointer<QObject> object(component.create());
587 QQuickItem *rootItem = qobject_cast<QQuickItem *>(object: object.data());
588 QVERIFY(rootItem);
589
590 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootItem, objectName: "repeater");
591 QVERIFY(repeater != nullptr);
592 QQuickItem *container = findItem<QQuickItem>(parent: rootItem, objectName: "container");
593 QVERIFY(container != nullptr);
594
595 QCOMPARE(repeater->count(), 0);
596
597 QSignalSpy countSpy(repeater, SIGNAL(countChanged()));
598 QSignalSpy addedSpy(repeater, SIGNAL(itemAdded(int,QQuickItem*)));
599 QSignalSpy removedSpy(repeater, SIGNAL(itemRemoved(int,QQuickItem*)));
600
601
602 QList<QPair<QString, QString> > items = QList<QPair<QString, QString> >()
603 << qMakePair(x: QString::fromLatin1(str: "one"), y: QString::fromLatin1(str: "1"))
604 << qMakePair(x: QString::fromLatin1(str: "two"), y: QString::fromLatin1(str: "2"))
605 << qMakePair(x: QString::fromLatin1(str: "three"), y: QString::fromLatin1(str: "3"));
606
607 model.resetItems(items);
608
609 QCOMPARE(countSpy.count(), 1);
610 QCOMPARE(removedSpy.count(), 0);
611 QCOMPARE(addedSpy.count(), items.count());
612 for (int i = 0; i< items.count(); i++) {
613 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
614 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
615 }
616
617 countSpy.clear();
618 addedSpy.clear();
619
620 model.reset();
621 QCOMPARE(countSpy.count(), 0);
622 QCOMPARE(removedSpy.count(), 3);
623 QCOMPARE(addedSpy.count(), 3);
624 for (int i = 0; i< items.count(); i++) {
625 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
626 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
627 }
628
629 addedSpy.clear();
630 removedSpy.clear();
631
632 items.append(t: qMakePair(x: QString::fromLatin1(str: "four"), y: QString::fromLatin1(str: "4")));
633 items.append(t: qMakePair(x: QString::fromLatin1(str: "five"), y: QString::fromLatin1(str: "5")));
634
635 model.resetItems(items);
636 QCOMPARE(countSpy.count(), 1);
637 QCOMPARE(removedSpy.count(), 3);
638 QCOMPARE(addedSpy.count(), 5);
639 for (int i = 0; i< items.count(); i++) {
640 QCOMPARE(addedSpy.at(i).at(0).toInt(), i);
641 QCOMPARE(addedSpy.at(i).at(1).value<QQuickItem*>(), repeater->itemAt(i));
642 }
643
644 countSpy.clear();
645 addedSpy.clear();
646 removedSpy.clear();
647
648 items.clear();
649 model.resetItems(items);
650 QCOMPARE(countSpy.count(), 1);
651 QCOMPARE(removedSpy.count(), 5);
652 QCOMPARE(addedSpy.count(), 0);
653}
654
655// QTBUG-46828
656void tst_QQuickRepeater::modelCleared()
657{
658 QQmlEngine engine;
659 QQmlComponent component(&engine, testFileUrl(fileName: "modelCleared.qml"));
660
661 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
662 QVERIFY(rootObject);
663
664 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
665 QVERIFY(repeater);
666
667 // verify no error messages when the model is cleared and the items are destroyed
668 QQmlTestMessageHandler messageHandler;
669 repeater->setModel(0);
670 QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
671
672 delete rootObject;
673}
674
675void tst_QQuickRepeater::properties()
676{
677 QQmlEngine engine;
678 QQmlComponent component(&engine, testFileUrl(fileName: "properties.qml"));
679
680 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
681 QVERIFY(rootObject);
682
683 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
684 QVERIFY(repeater);
685
686 QSignalSpy modelSpy(repeater, SIGNAL(modelChanged()));
687 repeater->setModel(3);
688 QCOMPARE(modelSpy.count(),1);
689 repeater->setModel(3);
690 QCOMPARE(modelSpy.count(),1);
691
692 QSignalSpy delegateSpy(repeater, SIGNAL(delegateChanged()));
693
694 QQmlComponent rectComponent(&engine);
695 rectComponent.setData("import QtQuick 2.0; Rectangle {}", baseUrl: QUrl::fromLocalFile(localfile: ""));
696
697 repeater->setDelegate(&rectComponent);
698 QCOMPARE(delegateSpy.count(),1);
699 repeater->setDelegate(&rectComponent);
700 QCOMPARE(delegateSpy.count(),1);
701
702 delete rootObject;
703}
704
705void tst_QQuickRepeater::asynchronous()
706{
707 QQuickView *window = createView();
708 window->show();
709 QQmlIncubationController controller;
710 window->engine()->setIncubationController(&controller);
711
712 window->setSource(testFileUrl(fileName: "asyncloader.qml"));
713
714 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: window->rootObject());
715 QVERIFY(rootObject);
716
717 QQuickItem *container = findItem<QQuickItem>(parent: rootObject, objectName: "container");
718 QVERIFY(!container);
719 while (!container) {
720 bool b = false;
721 controller.incubateWhile(flag: &b);
722 container = findItem<QQuickItem>(parent: rootObject, objectName: "container");
723 }
724
725 QQuickRepeater *repeater = nullptr;
726 while (!repeater) {
727 bool b = false;
728 controller.incubateWhile(flag: &b);
729 repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
730 }
731
732 // items will be created one at a time
733 // the order is incubator/model specific
734 for (int i = 9; i >= 0; --i) {
735 QString name("delegate");
736 name += QString::number(i);
737 QVERIFY(findItem<QQuickItem>(container, name) == nullptr);
738 QQuickItem *item = nullptr;
739 while (!item) {
740 bool b = false;
741 controller.incubateWhile(flag: &b);
742 item = findItem<QQuickItem>(parent: container, objectName: name);
743 }
744 }
745
746 {
747 bool b = true;
748 controller.incubateWhile(flag: &b);
749 }
750
751 // verify positioning
752 for (int i = 0; i < 10; ++i) {
753 QString name("delegate");
754 name += QString::number(i);
755 QQuickItem *item = findItem<QQuickItem>(parent: container, objectName: name);
756 QTRY_COMPARE(item->y(), i * 50.0);
757 }
758
759 delete window;
760}
761
762void tst_QQuickRepeater::initParent()
763{
764 QQmlEngine engine;
765 QQmlComponent component(&engine, testFileUrl(fileName: "initparent.qml"));
766
767 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
768 QVERIFY(rootObject);
769
770 QCOMPARE(qvariant_cast<QQuickItem*>(rootObject->property("parentItem")), rootObject);
771}
772
773void tst_QQuickRepeater::dynamicModelCrash()
774{
775 QQmlEngine engine;
776 QQmlComponent component(&engine, testFileUrl(fileName: "dynamicmodelcrash.qml"));
777
778 // Don't crash
779 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
780 QVERIFY(rootObject);
781
782 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "rep");
783 QVERIFY(repeater);
784 QVERIFY(qvariant_cast<QObject *>(repeater->model()) == 0);
785}
786
787void tst_QQuickRepeater::visualItemModelCrash()
788{
789 // This used to crash because the model would get
790 // deleted before the repeater, leading to double-deletion
791 // of the items.
792 QQuickView *window = createView();
793 window->setSource(testFileUrl(fileName: "visualitemmodel.qml"));
794 qApp->processEvents();
795 delete window;
796}
797
798class BadModel : public QAbstractListModel
799{
800public:
801 ~BadModel()
802 {
803 beginResetModel();
804 endResetModel();
805 }
806
807 QVariant data(const QModelIndex &, int) const { return QVariant(); }
808 int rowCount(const QModelIndex &) const { return 0; }
809};
810
811
812void tst_QQuickRepeater::invalidContextCrash()
813{
814 QQmlEngine engine;
815 QQmlComponent component(&engine, testFileUrl(fileName: "invalidContextCrash.qml"));
816
817 BadModel* model = new BadModel;
818 engine.rootContext()->setContextProperty("badModel", model);
819
820 QScopedPointer<QObject> root(component.create());
821 QCOMPARE(root->children().count(), 1);
822 QObject *repeater = root->children().first();
823
824 // Make sure the model comes first in the child list, so it will be
825 // deleted first and then the repeater. During deletion the QML context
826 // has been deleted already and is invalid.
827 model->setParent(root.data());
828 repeater->setParent(nullptr);
829 repeater->setParent(root.data());
830
831 QCOMPARE(root->children().count(), 2);
832 QCOMPARE(root->children().at(0), model);
833 QCOMPARE(root->children().at(1), repeater);
834
835 // Delete the root object, which will invalidate/delete the QML context
836 // and then delete the child QObjects, which may try to access the context.
837 root.reset(other: nullptr);
838}
839
840void tst_QQuickRepeater::jsArrayChange()
841{
842 QQmlEngine engine;
843 QQmlComponent component(&engine);
844 component.setData("import QtQuick 2.4; Repeater {}", baseUrl: QUrl());
845
846 QScopedPointer<QQuickRepeater> repeater(qobject_cast<QQuickRepeater *>(object: component.create()));
847 QVERIFY(!repeater.isNull());
848
849 QSignalSpy spy(repeater.data(), SIGNAL(modelChanged()));
850 QVERIFY(spy.isValid());
851
852 QJSValue array1 = engine.newArray(length: 3);
853 QJSValue array2 = engine.newArray(length: 3);
854 for (int i = 0; i < 3; ++i) {
855 array1.setProperty(arrayIndex: i, value: i);
856 array2.setProperty(arrayIndex: i, value: i);
857 }
858
859 repeater->setModel(QVariant::fromValue(value: array1));
860 QCOMPARE(spy.count(), 1);
861
862 // no change
863 repeater->setModel(QVariant::fromValue(value: array2));
864 QCOMPARE(spy.count(), 1);
865}
866
867void tst_QQuickRepeater::clearRemovalOrder()
868{
869 // Here, we're going to test that when the model is cleared, item removal
870 // signals are sent in a sensible order that gives us correct indices.
871 // (QTBUG-42243)
872 QQmlEngine engine;
873 QQmlComponent component(&engine, testFileUrl(fileName: "clearremovalorder.qml"));
874
875 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
876 QVERIFY(rootObject);
877
878 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
879 QVERIFY(repeater);
880 QCOMPARE(repeater->count(), 3);
881
882 QQmlListModel *model = rootObject->findChild<QQmlListModel*>(aName: "secondModel");
883 QVERIFY(model);
884 QCOMPARE(model->count(), 0);
885
886 // Now change the model
887 QSignalSpy removedSpy(repeater, &QQuickRepeater::itemRemoved);
888 repeater->setModel(QVariant::fromValue(value: model));
889
890 // we should have 0 items, and 3 removal signals.
891 QCOMPARE(repeater->count(), 0);
892 QCOMPARE(removedSpy.count(), 3);
893
894 // column 1 is for the items, we won't bother verifying these. just look at
895 // the indices and make sure they're sane.
896 QCOMPARE(removedSpy.at(0).at(0).toInt(), 2);
897 QCOMPARE(removedSpy.at(1).at(0).toInt(), 1);
898 QCOMPARE(removedSpy.at(2).at(0).toInt(), 0);
899
900 delete rootObject;
901}
902
903void tst_QQuickRepeater::destroyCount()
904{
905 QQmlEngine engine;
906 QQmlComponent component(&engine, testFileUrl(fileName: "destroycount.qml"));
907
908 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
909 QVERIFY(rootObject);
910
911 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
912 QVERIFY(repeater);
913
914 repeater->setProperty(name: "model", value: QVariant::fromValue<int>(value: 3));
915 QCOMPARE(repeater->property("componentCount").toInt(), 3);
916 repeater->setProperty(name: "model", value: QVariant::fromValue<int>(value: 0));
917 QCOMPARE(repeater->property("componentCount").toInt(), 0);
918 repeater->setProperty(name: "model", value: QVariant::fromValue<int>(value: 4));
919 QCOMPARE(repeater->property("componentCount").toInt(), 4);
920
921 QStringListModel model;
922 repeater->setProperty(name: "model", value: QVariant::fromValue<QStringListModel *>(value: &model));
923 QCOMPARE(repeater->property("componentCount").toInt(), 0);
924 QStringList list;
925 list << "1" << "2" << "3" << "4";
926 model.setStringList(list);
927 QCOMPARE(repeater->property("componentCount").toInt(), 4);
928 model.insertRows(row: 2,count: 1);
929 QModelIndex index = model.index(row: 2);
930 model.setData(index, value: QVariant::fromValue<QString>(QStringLiteral("foobar")));
931 QCOMPARE(repeater->property("componentCount").toInt(), 5);
932
933 model.removeRows(row: 2,count: 1);
934 QCOMPARE(model.rowCount(), 4);
935 QCOMPARE(repeater->property("componentCount").toInt(), 4);
936}
937
938void tst_QQuickRepeater::stackingOrder()
939{
940 QQmlEngine engine;
941 QQmlComponent component(&engine, testFileUrl(fileName: "stackingorder.qml"));
942
943 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: component.create());
944 QVERIFY(rootObject);
945
946 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: rootObject, objectName: "repeater");
947 QVERIFY(repeater);
948 int count = 1;
949 do {
950 bool stackingOrderOk = rootObject->property(name: "stackingOrderOk").toBool();
951 QVERIFY(stackingOrderOk);
952 repeater->setModel(QVariant(++count));
953 } while (count < 3);
954}
955
956static bool compareObjectModel(QQuickRepeater *repeater, QQmlObjectModel *model)
957{
958 if (repeater->count() != model->count())
959 return false;
960 for (int i = 0; i < repeater->count(); ++i) {
961 if (repeater->itemAt(index: i) != model->get(index: i))
962 return false;
963 }
964 return true;
965}
966
967void tst_QQuickRepeater::objectModel()
968{
969 QQmlEngine engine;
970 QQmlComponent component(&engine, testFileUrl(fileName: "objectmodel.qml"));
971
972 QQuickItem *positioner = qobject_cast<QQuickItem *>(object: component.create());
973 QVERIFY(positioner);
974
975 QQuickRepeater *repeater = findItem<QQuickRepeater>(parent: positioner, objectName: "repeater");
976 QVERIFY(repeater);
977
978 QQmlObjectModel *model = repeater->model().value<QQmlObjectModel *>();
979 QVERIFY(model);
980
981 QVERIFY(repeater->itemAt(0));
982 QVERIFY(repeater->itemAt(1));
983 QVERIFY(repeater->itemAt(2));
984 QCOMPARE(repeater->itemAt(0)->property("color").toString(), QColor("red").name());
985 QCOMPARE(repeater->itemAt(1)->property("color").toString(), QColor("green").name());
986 QCOMPARE(repeater->itemAt(2)->property("color").toString(), QColor("blue").name());
987
988 QQuickItem *item0 = new QQuickItem(positioner);
989 item0->setSize(QSizeF(20, 20));
990 model->append(object: item0);
991 QCOMPARE(model->count(), 4);
992 QVERIFY(compareObjectModel(repeater, model));
993
994 QQuickItem *item1 = new QQuickItem(positioner);
995 item1->setSize(QSizeF(20, 20));
996 model->insert(index: 0, object: item1);
997 QCOMPARE(model->count(), 5);
998 QVERIFY(compareObjectModel(repeater, model));
999
1000 model->move(from: 1, to: 2, n: 3);
1001 QVERIFY(compareObjectModel(repeater, model));
1002
1003 model->remove(index: 2, n: 2);
1004 QCOMPARE(model->count(), 3);
1005 QVERIFY(compareObjectModel(repeater, model));
1006
1007 model->clear();
1008 QCOMPARE(model->count(), 0);
1009 QCOMPARE(repeater->count(), 0);
1010
1011 delete positioner;
1012}
1013
1014class Ctrl : public QObject
1015{
1016 Q_OBJECT
1017public:
1018
1019 Q_INVOKABLE void wait()
1020 {
1021 QTest::qWait(ms: 200);
1022 }
1023};
1024
1025void tst_QQuickRepeater::QTBUG54859_asynchronousMove()
1026{
1027 Ctrl ctrl;
1028 QQuickView* view = createView();
1029 view->rootContext()->setContextProperty("ctrl", &ctrl);
1030 view->setSource(testFileUrl(fileName: "asynchronousMove.qml"));
1031 view->show();
1032 QQuickItem* item = view->rootObject();
1033
1034
1035 QTRY_COMPARE(item->property("finished"), QVariant(true));
1036}
1037
1038void tst_QQuickRepeater::package()
1039{
1040 QQmlEngine engine;
1041 QQmlComponent component(&engine, testFileUrl(fileName: "package.qml"));
1042
1043 QScopedPointer<QObject>o(component.create()); // don't crash!
1044 QVERIFY(o != nullptr);
1045
1046 {
1047 QQuickRepeater *repeater1 = qobject_cast<QQuickRepeater*>(object: qmlContext(o.data())->contextProperty("repeater1").value<QObject*>());
1048 QVERIFY(repeater1);
1049 QCOMPARE(repeater1->count(), 1);
1050 QCOMPARE(repeater1->itemAt(0)->objectName(), "firstItem");
1051 }
1052
1053 {
1054 QQuickRepeater *repeater2 = qobject_cast<QQuickRepeater*>(object: qmlContext(o.data())->contextProperty("repeater2").value<QObject*>());
1055 QVERIFY(repeater2);
1056 QCOMPARE(repeater2->count(), 1);
1057 QCOMPARE(repeater2->itemAt(0)->objectName(), "secondItem");
1058 }
1059
1060 {
1061 QQmlComponent component(&engine, testFileUrl(fileName: "package2.qml"));
1062 QScopedPointer<QObject> root(component.create());
1063 QVERIFY(root != nullptr);
1064 bool returnedValue = false;
1065 // calling setup should not crash
1066 QMetaObject::invokeMethod(obj: root.get(), member: "setup", Q_RETURN_ARG(bool, returnedValue));
1067 QVERIFY(returnedValue);
1068 }
1069}
1070
1071void tst_QQuickRepeater::ownership()
1072{
1073 QQmlEngine engine;
1074
1075 QQmlComponent component(&engine, testFileUrl(fileName: "ownership.qml"));
1076
1077 QScopedPointer<QAbstractItemModel> aim(new QStandardItemModel);
1078 QPointer<QAbstractItemModel> modelGuard(aim.data());
1079 QQmlEngine::setObjectOwnership(aim.data(), QQmlEngine::JavaScriptOwnership);
1080 {
1081 QJSValue wrapper = engine.newQObject(object: aim.data());
1082 }
1083
1084 QScopedPointer<QObject> repeater(component.create());
1085 QVERIFY(!repeater.isNull());
1086
1087 QVERIFY(!QQmlData::keepAliveDuringGarbageCollection(aim.data()));
1088
1089 repeater->setProperty(name: "model", value: QVariant::fromValue<QObject*>(value: aim.data()));
1090
1091 QVERIFY(!QQmlData::keepAliveDuringGarbageCollection(aim.data()));
1092
1093 engine.collectGarbage();
1094 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1095
1096 QVERIFY(modelGuard);
1097
1098 QScopedPointer<QQmlComponent> delegate(new QQmlComponent(&engine));
1099 delegate->setData(QByteArrayLiteral("import QtQuick 2.0\nItem{}"), baseUrl: dataDirectoryUrl().resolved(relative: QUrl("inline.qml")));
1100 QPointer<QQmlComponent> delegateGuard(delegate.data());
1101 QQmlEngine::setObjectOwnership(delegate.data(), QQmlEngine::JavaScriptOwnership);
1102 {
1103 QJSValue wrapper = engine.newQObject(object: delegate.data());
1104 }
1105
1106 QVERIFY(!QQmlData::keepAliveDuringGarbageCollection(delegate.data()));
1107
1108 repeater->setProperty(name: "delegate", value: QVariant::fromValue<QObject*>(value: delegate.data()));
1109
1110 QVERIFY(!QQmlData::keepAliveDuringGarbageCollection(delegate.data()));
1111
1112 engine.collectGarbage();
1113 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1114
1115 QVERIFY(delegateGuard);
1116
1117 repeater->setProperty(name: "model", value: QVariant());
1118 repeater->setProperty(name: "delegate", value: QVariant());
1119
1120 QVERIFY(delegateGuard);
1121 QVERIFY(modelGuard);
1122
1123 delegate.take();
1124 aim.take();
1125
1126 engine.collectGarbage();
1127 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1128
1129 QVERIFY(!delegateGuard);
1130 QVERIFY(!modelGuard);
1131}
1132
1133void tst_QQuickRepeater::requiredProperties()
1134{
1135 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "apples0");
1136 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "oranges1");
1137 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "pears2");
1138 QQmlEngine engine;
1139
1140 QQmlComponent component(&engine, testFileUrl(fileName: "requiredProperty.qml"));
1141 QScopedPointer<QObject> o {component.create()};
1142 QVERIFY(o);
1143}
1144
1145void tst_QQuickRepeater::contextProperties()
1146{
1147 QQmlEngine engine;
1148
1149 QQmlComponent component(&engine, testFileUrl(fileName: "contextProperty.qml"));
1150 QScopedPointer<QObject> o {component.create()};
1151 QVERIFY(o);
1152
1153 auto *root = qobject_cast<QQuickItem *>(object: o.get());
1154 QVERIFY(root);
1155
1156 QQueue<QQuickItem *> items;
1157 items.append(t: root);
1158
1159 while (!items.isEmpty()) {
1160 QQuickItem *item = items.dequeue();
1161 QQmlContextData *data = QQmlContextData::get(context: qmlContext(item));
1162 QVERIFY(!data->hasExtraObject);
1163 for (QQuickItem *child : item->childItems())
1164 items.enqueue(t: child);
1165 }
1166}
1167
1168QTEST_MAIN(tst_QQuickRepeater)
1169
1170#include "tst_qquickrepeater.moc"
1171

source code of qtdeclarative/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp