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 <QtQuick/qquickview.h>
31#include <QtQml/qqmlengine.h>
32#include <QtQml/qqmlcomponent.h>
33#include <QtQml/qqmlcontext.h>
34#include <QtQml/qqmlexpression.h>
35#include <QtQml/qqmlincubator.h>
36#include <QtQuick/private/qquickpathview_p.h>
37#include <QtQuick/private/qquickflickable_p.h>
38#include <QtQuick/private/qquickpath_p.h>
39#include <QtQuick/private/qquicktext_p.h>
40#include <QtQuick/private/qquickrectangle_p.h>
41#include <QtQuickTest/QtQuickTest>
42#include <QtQmlModels/private/qqmllistmodel_p.h>
43#include <QtQml/private/qqmlvaluetype_p.h>
44#include <QtGui/qstandarditemmodel.h>
45#include <QStringListModel>
46#include <QFile>
47
48#include "../../shared/util.h"
49#include "../shared/viewtestutil.h"
50#include "../shared/visualtestutil.h"
51
52#include <math.h>
53
54Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
55
56using namespace QQuickViewTestUtil;
57using namespace QQuickVisualTestUtil;
58
59Q_DECLARE_METATYPE(QQuickPathView::HighlightRangeMode)
60Q_DECLARE_METATYPE(QQuickPathView::PositionMode)
61
62static void initStandardTreeModel(QStandardItemModel *model)
63{
64 QStandardItem *item;
65 item = new QStandardItem(QLatin1String("Row 1 Item"));
66 model->insertRow(arow: 0, aitem: item);
67
68 item = new QStandardItem(QLatin1String("Row 2 Item"));
69 item->setCheckable(true);
70 model->insertRow(arow: 1, aitem: item);
71
72 QStandardItem *childItem = new QStandardItem(QLatin1String("Row 2 Child Item"));
73 item->setChild(arow: 0, aitem: childItem);
74
75 item = new QStandardItem(QLatin1String("Row 3 Item"));
76 item->setIcon(QIcon());
77 model->insertRow(arow: 2, aitem: item);
78}
79
80class tst_QQuickPathView : public QQmlDataTest
81{
82 Q_OBJECT
83public:
84 tst_QQuickPathView();
85
86private slots:
87 void initValues();
88 void items();
89 void dataModel();
90 void pathview2();
91 void pathview3();
92 void initialCurrentIndex();
93 void initialCurrentItem();
94 void insertModel_data();
95 void insertModel();
96 void removeModel_data();
97 void removeModel();
98 void moveModel_data();
99 void moveModel();
100 void consecutiveModelChanges_data();
101 void consecutiveModelChanges();
102 void path();
103 void pathMoved();
104 void offset_data();
105 void offset();
106 void setCurrentIndex();
107 void setCurrentIndexWrap();
108 void resetModel();
109 void propertyChanges();
110 void pathChanges();
111 void componentChanges();
112 void modelChanges();
113 void pathUpdateOnStartChanged();
114 void package();
115 void emptyModel();
116 void emptyPath();
117 void closed();
118 void pathUpdate();
119 void visualDataModel();
120 void undefinedPath();
121 void mouseDrag();
122 void nestedMouseAreaDrag();
123 void flickNClick();
124 void treeModel();
125 void changePreferredHighlight();
126 void missingPercent();
127 void creationContext();
128 void currentOffsetOnInsertion();
129 void asynchronous();
130 void cancelDrag();
131 void maximumFlickVelocity();
132 void snapToItem();
133 void snapToItem_data();
134 void snapOneItem();
135 void snapOneItem_data();
136 void positionViewAtIndex();
137 void positionViewAtIndex_data();
138 void indexAt_itemAt();
139 void indexAt_itemAt_data();
140 void cacheItemCount();
141 void changePathDuringRefill();
142 void nestedinFlickable();
143 void ungrabNestedinFlickable();
144 void flickableDelegate();
145 void jsArrayChange();
146 void qtbug37815();
147 void qtbug42716();
148 void qtbug53464();
149 void addCustomAttribute();
150 void movementDirection_data();
151 void movementDirection();
152 void removePath();
153 void objectModelMove();
154 void requiredPropertiesInDelegate();
155 void requiredPropertiesInDelegatePreventUnrelated();
156};
157
158class TestObject : public QObject
159{
160 Q_OBJECT
161
162 Q_PROPERTY(bool error READ error WRITE setError)
163 Q_PROPERTY(bool useModel READ useModel NOTIFY useModelChanged)
164 Q_PROPERTY(int pathItemCount READ pathItemCount NOTIFY pathItemCountChanged)
165
166public:
167 TestObject() : QObject(), mError(true), mUseModel(true), mPathItemCount(-1) {}
168
169 bool error() const { return mError; }
170 void setError(bool err) { mError = err; }
171
172 bool useModel() const { return mUseModel; }
173 void setUseModel(bool use) { mUseModel = use; emit useModelChanged(); }
174
175 int pathItemCount() const { return mPathItemCount; }
176 void setPathItemCount(int count) { mPathItemCount = count; emit pathItemCountChanged(); }
177
178signals:
179 void useModelChanged();
180 void pathItemCountChanged();
181
182private:
183 bool mError;
184 bool mUseModel;
185 int mPathItemCount;
186};
187
188tst_QQuickPathView::tst_QQuickPathView()
189{
190}
191
192void tst_QQuickPathView::initValues()
193{
194 QQmlEngine engine;
195 QQmlComponent c(&engine, testFileUrl(fileName: "pathview1.qml"));
196 QQuickPathView *obj = qobject_cast<QQuickPathView*>(object: c.create());
197
198 QVERIFY(obj != nullptr);
199 QVERIFY(!obj->path());
200 QVERIFY(!obj->delegate());
201 QCOMPARE(obj->model(), QVariant());
202 QCOMPARE(obj->currentIndex(), 0);
203 QCOMPARE(obj->offset(), 0.);
204 QCOMPARE(obj->preferredHighlightBegin(), 0.);
205 QCOMPARE(obj->dragMargin(), 0.);
206 QCOMPARE(obj->count(), 0);
207 QCOMPARE(obj->pathItemCount(), -1);
208
209 delete obj;
210}
211
212void tst_QQuickPathView::items()
213{
214 QScopedPointer<QQuickView> window(createView());
215
216 QaimModel model;
217 model.addItem(name: "Fred", number: "12345");
218 model.addItem(name: "John", number: "2345");
219 model.addItem(name: "Bob", number: "54321");
220 model.addItem(name: "Bill", number: "4321");
221
222 QQmlContext *ctxt = window->rootContext();
223 ctxt->setContextProperty("testModel", &model);
224
225 window->setSource(testFileUrl(fileName: "pathview0.qml"));
226 qApp->processEvents();
227
228 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
229 QVERIFY(pathview != nullptr);
230
231 QCOMPARE(pathview->count(), model.count());
232 QCOMPARE(window->rootObject()->property("count").toInt(), model.count());
233 QCOMPARE(pathview->childItems().count(), model.count()+1); // assumes all are visible, including highlight
234
235 for (int i = 0; i < model.count(); ++i) {
236 QQuickText *name = findItem<QQuickText>(parent: pathview, objectName: "textName", index: i);
237 QVERIFY(name != nullptr);
238 QCOMPARE(name->text(), model.name(i));
239 QQuickText *number = findItem<QQuickText>(parent: pathview, objectName: "textNumber", index: i);
240 QVERIFY(number != nullptr);
241 QCOMPARE(number->text(), model.number(i));
242 }
243
244 QQuickPath *path = qobject_cast<QQuickPath*>(object: pathview->path());
245 QVERIFY(path);
246
247 QVERIFY(pathview->highlightItem());
248 QPointF start = path->pointAtPercent(t: 0.0);
249 QPointF offset;
250 offset.setX(pathview->highlightItem()->width()/2);
251 offset.setY(pathview->highlightItem()->height()/2);
252 QCOMPARE(pathview->highlightItem()->position() + offset, start);
253}
254
255void tst_QQuickPathView::initialCurrentItem()
256{
257 QScopedPointer<QQuickView> window(createView());
258
259 QaimModel model;
260 model.addItem(name: "Jules", number: "12345");
261 model.addItem(name: "Vicent", number: "2345");
262 model.addItem(name: "Marvin", number: "54321");
263
264 QQmlContext *ctxt = window->rootContext();
265 ctxt->setContextProperty("testModel", &model);
266
267 window->setSource(testFileUrl(fileName: "pathview4.qml"));
268 qApp->processEvents();
269
270 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
271 QVERIFY(pathview != nullptr);
272 QVERIFY(pathview->currentIndex() != -1);
273 QVERIFY(!window->rootObject()->property("currentItemIsNull").toBool());
274}
275
276void tst_QQuickPathView::pathview2()
277{
278 QQmlEngine engine;
279 QQmlComponent c(&engine, testFileUrl(fileName: "pathview2.qml"));
280 QQuickPathView *obj = qobject_cast<QQuickPathView*>(object: c.create());
281
282 QVERIFY(obj != nullptr);
283 QVERIFY(obj->path() != nullptr);
284 QVERIFY(obj->delegate() != nullptr);
285 QVERIFY(obj->model() != QVariant());
286 QCOMPARE(obj->currentIndex(), 0);
287 QCOMPARE(obj->offset(), 0.);
288 QCOMPARE(obj->preferredHighlightBegin(), 0.);
289 QCOMPARE(obj->dragMargin(), 0.);
290 QCOMPARE(obj->count(), 8);
291 QCOMPARE(obj->pathItemCount(), 10);
292
293 delete obj;
294}
295
296void tst_QQuickPathView::pathview3()
297{
298 QQmlEngine engine;
299 QQmlComponent c(&engine, testFileUrl(fileName: "pathview3.qml"));
300 QQuickPathView *obj = qobject_cast<QQuickPathView*>(object: c.create());
301
302 QVERIFY(obj != nullptr);
303 QVERIFY(obj->path() != nullptr);
304 QVERIFY(obj->delegate() != nullptr);
305 QVERIFY(obj->model() != QVariant());
306 QCOMPARE(obj->currentIndex(), 7);
307 QCOMPARE(obj->offset(), 1.0);
308 QCOMPARE(obj->preferredHighlightBegin(), 0.5);
309 QCOMPARE(obj->dragMargin(), 24.);
310 QCOMPARE(obj->count(), 8);
311 QCOMPARE(obj->pathItemCount(), 4);
312
313 delete obj;
314}
315
316void tst_QQuickPathView::initialCurrentIndex()
317{
318 QQmlEngine engine;
319 QQmlComponent c(&engine, testFileUrl(fileName: "initialCurrentIndex.qml"));
320 QQuickPathView *obj = qobject_cast<QQuickPathView*>(object: c.create());
321
322 QVERIFY(obj != nullptr);
323 QVERIFY(obj->path() != nullptr);
324 QVERIFY(obj->delegate() != nullptr);
325 QVERIFY(obj->model() != QVariant());
326 QCOMPARE(obj->currentIndex(), 3);
327 QCOMPARE(obj->offset(), 5.0);
328 QCOMPARE(obj->preferredHighlightBegin(), 0.5);
329 QCOMPARE(obj->dragMargin(), 24.);
330 QCOMPARE(obj->count(), 8);
331 QCOMPARE(obj->pathItemCount(), 4);
332
333 delete obj;
334}
335
336void tst_QQuickPathView::insertModel_data()
337{
338 QTest::addColumn<int>(name: "mode");
339 QTest::addColumn<int>(name: "idx");
340 QTest::addColumn<int>(name: "count");
341 QTest::addColumn<qreal>(name: "offset");
342 QTest::addColumn<int>(name: "currentIndex");
343
344 // We have 8 items, with currentIndex == 4
345 QTest::newRow(dataTag: "insert after current")
346 << int(QQuickPathView::StrictlyEnforceRange) << 6 << 1 << qreal(5.) << 4;
347 QTest::newRow(dataTag: "insert before current")
348 << int(QQuickPathView::StrictlyEnforceRange) << 2 << 1 << qreal(4.)<< 5;
349 QTest::newRow(dataTag: "insert multiple after current")
350 << int(QQuickPathView::StrictlyEnforceRange) << 5 << 2 << qreal(6.) << 4;
351 QTest::newRow(dataTag: "insert multiple before current")
352 << int(QQuickPathView::StrictlyEnforceRange) << 1 << 2 << qreal(4.) << 6;
353 QTest::newRow(dataTag: "insert at end")
354 << int(QQuickPathView::StrictlyEnforceRange) << 8 << 1 << qreal(5.) << 4;
355 QTest::newRow(dataTag: "insert at beginning")
356 << int(QQuickPathView::StrictlyEnforceRange) << 0 << 1 << qreal(4.) << 5;
357 QTest::newRow(dataTag: "insert at current")
358 << int(QQuickPathView::StrictlyEnforceRange) << 4 << 1 << qreal(4.) << 5;
359
360 QTest::newRow(dataTag: "no range - insert after current")
361 << int(QQuickPathView::NoHighlightRange) << 6 << 1 << qreal(5.) << 4;
362 QTest::newRow(dataTag: "no range - insert before current")
363 << int(QQuickPathView::NoHighlightRange) << 2 << 1 << qreal(4.) << 5;
364 QTest::newRow(dataTag: "no range - insert multiple after current")
365 << int(QQuickPathView::NoHighlightRange) << 5 << 2 << qreal(6.) << 4;
366 QTest::newRow(dataTag: "no range - insert multiple before current")
367 << int(QQuickPathView::NoHighlightRange) << 1 << 2 << qreal(4.) << 6;
368 QTest::newRow(dataTag: "no range - insert at end")
369 << int(QQuickPathView::NoHighlightRange) << 8 << 1 << qreal(5.) << 4;
370 QTest::newRow(dataTag: "no range - insert at beginning")
371 << int(QQuickPathView::NoHighlightRange) << 0 << 1 << qreal(4.) << 5;
372 QTest::newRow(dataTag: "no range - insert at current")
373 << int(QQuickPathView::NoHighlightRange) << 4 << 1 << qreal(4.) << 5;
374}
375
376void tst_QQuickPathView::insertModel()
377{
378#ifdef Q_OS_MACOS
379 QSKIP("this test currently crashes on MacOS. See QTBUG-68048");
380#endif
381
382 QFETCH(int, mode);
383 QFETCH(int, idx);
384 QFETCH(int, count);
385 QFETCH(qreal, offset);
386 QFETCH(int, currentIndex);
387
388 QScopedPointer<QQuickView> window(createView());
389 window->show();
390
391 QaimModel model;
392 model.addItem(name: "Ben", number: "12345");
393 model.addItem(name: "Bohn", number: "2345");
394 model.addItem(name: "Bob", number: "54321");
395 model.addItem(name: "Bill", number: "4321");
396 model.addItem(name: "Jinny", number: "679");
397 model.addItem(name: "Milly", number: "73378");
398 model.addItem(name: "Jimmy", number: "3535");
399 model.addItem(name: "Barb", number: "9039");
400
401 QQmlContext *ctxt = window->rootContext();
402 ctxt->setContextProperty("testModel", &model);
403
404 window->setSource(testFileUrl(fileName: "pathview0.qml"));
405 qApp->processEvents();
406
407 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
408 QVERIFY(pathview != nullptr);
409
410 pathview->setHighlightRangeMode((QQuickPathView::HighlightRangeMode)mode);
411
412 pathview->setCurrentIndex(4);
413 if (mode == QQuickPathView::StrictlyEnforceRange)
414 QTRY_COMPARE(pathview->offset(), 4.0);
415 else
416 pathview->setOffset(4);
417
418 QList<QPair<QString, QString> > items;
419 for (int i = 0; i < count; ++i)
420 items.append(t: qMakePair(x: QString("New"), y: QString::number(i)));
421
422 model.insertItems(index: idx, items);
423 QTRY_COMPARE(pathview->offset(), offset);
424
425 QCOMPARE(pathview->currentIndex(), currentIndex);
426}
427
428void tst_QQuickPathView::removeModel_data()
429{
430 QTest::addColumn<int>(name: "mode");
431 QTest::addColumn<int>(name: "idx");
432 QTest::addColumn<int>(name: "count");
433 QTest::addColumn<qreal>(name: "offset");
434 QTest::addColumn<int>(name: "currentIndex");
435
436 // We have 8 items, with currentIndex == 4
437 QTest::newRow(dataTag: "remove after current")
438 << int(QQuickPathView::StrictlyEnforceRange) << 6 << 1 << qreal(3.) << 4;
439 QTest::newRow(dataTag: "remove before current")
440 << int(QQuickPathView::StrictlyEnforceRange) << 2 << 1 << qreal(4.) << 3;
441 QTest::newRow(dataTag: "remove multiple after current")
442 << int(QQuickPathView::StrictlyEnforceRange) << 5 << 2 << qreal(2.) << 4;
443 QTest::newRow(dataTag: "remove multiple before current")
444 << int(QQuickPathView::StrictlyEnforceRange) << 1 << 2 << qreal(4.) << 2;
445 QTest::newRow(dataTag: "remove last")
446 << int(QQuickPathView::StrictlyEnforceRange) << 7 << 1 << qreal(3.) << 4;
447 QTest::newRow(dataTag: "remove first")
448 << int(QQuickPathView::StrictlyEnforceRange) << 0 << 1 << qreal(4.) << 3;
449 QTest::newRow(dataTag: "remove current")
450 << int(QQuickPathView::StrictlyEnforceRange) << 4 << 1 << qreal(3.) << 4;
451 QTest::newRow(dataTag: "remove all")
452 << int(QQuickPathView::StrictlyEnforceRange) << 0 << 8 << qreal(0.) << 0;
453
454 QTest::newRow(dataTag: "no range - remove after current")
455 << int(QQuickPathView::NoHighlightRange) << 6 << 1 << qreal(3.) << 4;
456 QTest::newRow(dataTag: "no range - remove before current")
457 << int(QQuickPathView::NoHighlightRange) << 2 << 1 << qreal(4.) << 3;
458 QTest::newRow(dataTag: "no range - remove multiple after current")
459 << int(QQuickPathView::NoHighlightRange) << 5 << 2 << qreal(2.) << 4;
460 QTest::newRow(dataTag: "no range - remove multiple before current")
461 << int(QQuickPathView::NoHighlightRange) << 1 << 2 << qreal(4.) << 2;
462 QTest::newRow(dataTag: "no range - remove last")
463 << int(QQuickPathView::NoHighlightRange) << 7 << 1 << qreal(3.) << 4;
464 QTest::newRow(dataTag: "no range - remove first")
465 << int(QQuickPathView::NoHighlightRange) << 0 << 1 << qreal(4.) << 3;
466 QTest::newRow(dataTag: "no range - remove current offset")
467 << int(QQuickPathView::NoHighlightRange) << 4 << 1 << qreal(4.) << 4;
468 QTest::newRow(dataTag: "no range - remove all")
469 << int(QQuickPathView::NoHighlightRange) << 0 << 8 << qreal(0.) << 0;
470}
471
472void tst_QQuickPathView::removeModel()
473{
474 QFETCH(int, mode);
475 QFETCH(int, idx);
476 QFETCH(int, count);
477 QFETCH(qreal, offset);
478 QFETCH(int, currentIndex);
479
480 QScopedPointer<QQuickView> window(createView());
481
482 window->show();
483
484 QaimModel model;
485 model.addItem(name: "Ben", number: "12345");
486 model.addItem(name: "Bohn", number: "2345");
487 model.addItem(name: "Bob", number: "54321");
488 model.addItem(name: "Bill", number: "4321");
489 model.addItem(name: "Jinny", number: "679");
490 model.addItem(name: "Milly", number: "73378");
491 model.addItem(name: "Jimmy", number: "3535");
492 model.addItem(name: "Barb", number: "9039");
493
494 QQmlContext *ctxt = window->rootContext();
495 ctxt->setContextProperty("testModel", &model);
496
497 window->setSource(testFileUrl(fileName: "pathview0.qml"));
498 qApp->processEvents();
499
500 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
501 QVERIFY(pathview != nullptr);
502
503 pathview->setHighlightRangeMode((QQuickPathView::HighlightRangeMode)mode);
504
505 pathview->setCurrentIndex(4);
506 if (mode == QQuickPathView::StrictlyEnforceRange)
507 QTRY_COMPARE(pathview->offset(), 4.0);
508 else
509 pathview->setOffset(4);
510
511 model.removeItems(index: idx, count);
512 QTRY_COMPARE(pathview->offset(), offset);
513
514 QCOMPARE(pathview->currentIndex(), currentIndex);
515}
516
517
518void tst_QQuickPathView::moveModel_data()
519{
520 QTest::addColumn<int>(name: "mode");
521 QTest::addColumn<int>(name: "from");
522 QTest::addColumn<int>(name: "to");
523 QTest::addColumn<int>(name: "count");
524 QTest::addColumn<qreal>(name: "offset");
525 QTest::addColumn<int>(name: "currentIndex");
526
527 // We have 8 items, with currentIndex == 4
528 QTest::newRow(dataTag: "move after current")
529 << int(QQuickPathView::StrictlyEnforceRange) << 5 << 6 << 1 << qreal(4.) << 4;
530 QTest::newRow(dataTag: "move before current")
531 << int(QQuickPathView::StrictlyEnforceRange) << 2 << 3 << 1 << qreal(4.) << 4;
532 QTest::newRow(dataTag: "move before current to after")
533 << int(QQuickPathView::StrictlyEnforceRange) << 2 << 6 << 1 << qreal(5.) << 3;
534 QTest::newRow(dataTag: "move multiple after current")
535 << int(QQuickPathView::StrictlyEnforceRange) << 5 << 6 << 2 << qreal(4.) << 4;
536 QTest::newRow(dataTag: "move multiple before current")
537 << int(QQuickPathView::StrictlyEnforceRange) << 0 << 1 << 2 << qreal(4.) << 4;
538 QTest::newRow(dataTag: "move before current to end")
539 << int(QQuickPathView::StrictlyEnforceRange) << 2 << 7 << 1 << qreal(5.) << 3;
540 QTest::newRow(dataTag: "move last to beginning")
541 << int(QQuickPathView::StrictlyEnforceRange) << 7 << 0 << 1 << qreal(3.) << 5;
542 QTest::newRow(dataTag: "move current")
543 << int(QQuickPathView::StrictlyEnforceRange) << 4 << 6 << 1 << qreal(2.) << 6;
544
545 QTest::newRow(dataTag: "no range - move after current")
546 << int(QQuickPathView::NoHighlightRange) << 5 << 6 << 1 << qreal(4.) << 4;
547 QTest::newRow(dataTag: "no range - move before current")
548 << int(QQuickPathView::NoHighlightRange) << 2 << 3 << 1 << qreal(4.) << 4;
549 QTest::newRow(dataTag: "no range - move before current to after")
550 << int(QQuickPathView::NoHighlightRange) << 2 << 6 << 1 << qreal(5.) << 3;
551 QTest::newRow(dataTag: "no range - move multiple after current")
552 << int(QQuickPathView::NoHighlightRange) << 5 << 6 << 2 << qreal(4.) << 4;
553 QTest::newRow(dataTag: "no range - move multiple before current")
554 << int(QQuickPathView::NoHighlightRange) << 0 << 1 << 2 << qreal(4.) << 4;
555 QTest::newRow(dataTag: "no range - move before current to end")
556 << int(QQuickPathView::NoHighlightRange) << 2 << 7 << 1 << qreal(5.) << 3;
557 QTest::newRow(dataTag: "no range - move last to beginning")
558 << int(QQuickPathView::NoHighlightRange) << 7 << 0 << 1 << qreal(3.) << 5;
559 QTest::newRow(dataTag: "no range - move current")
560 << int(QQuickPathView::NoHighlightRange) << 4 << 6 << 1 << qreal(4.) << 6;
561 QTest::newRow(dataTag: "no range - move multiple incl. current")
562 << int(QQuickPathView::NoHighlightRange) << 0 << 1 << 5 << qreal(4.) << 5;
563}
564
565void tst_QQuickPathView::moveModel()
566{
567 QFETCH(int, mode);
568 QFETCH(int, from);
569 QFETCH(int, to);
570 QFETCH(int, count);
571 QFETCH(qreal, offset);
572 QFETCH(int, currentIndex);
573
574 QScopedPointer<QQuickView> window(createView());
575 window->show();
576
577 QaimModel model;
578 model.addItem(name: "Ben", number: "12345");
579 model.addItem(name: "Bohn", number: "2345");
580 model.addItem(name: "Bob", number: "54321");
581 model.addItem(name: "Bill", number: "4321");
582 model.addItem(name: "Jinny", number: "679");
583 model.addItem(name: "Milly", number: "73378");
584 model.addItem(name: "Jimmy", number: "3535");
585 model.addItem(name: "Barb", number: "9039");
586
587 QQmlContext *ctxt = window->rootContext();
588 ctxt->setContextProperty("testModel", &model);
589
590 window->setSource(testFileUrl(fileName: "pathview0.qml"));
591 qApp->processEvents();
592
593 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
594 QVERIFY(pathview != nullptr);
595
596 pathview->setHighlightRangeMode((QQuickPathView::HighlightRangeMode)mode);
597
598 pathview->setCurrentIndex(4);
599 if (mode == QQuickPathView::StrictlyEnforceRange)
600 QTRY_COMPARE(pathview->offset(), 4.0);
601 else
602 pathview->setOffset(4);
603
604 model.moveItems(from, to, count);
605 QTRY_COMPARE(pathview->offset(), offset);
606
607 QCOMPARE(pathview->currentIndex(), currentIndex);
608}
609
610void tst_QQuickPathView::consecutiveModelChanges_data()
611{
612 QTest::addColumn<QQuickPathView::HighlightRangeMode>(name: "mode");
613 QTest::addColumn<QList<ListChange> >(name: "changes");
614 QTest::addColumn<int>(name: "count");
615 QTest::addColumn<qreal>(name: "offset");
616 QTest::addColumn<int>(name: "currentIndex");
617
618 QTest::newRow(dataTag: "no range - insert after, insert before")
619 << QQuickPathView::NoHighlightRange
620 << (QList<ListChange>()
621 << ListChange::insert(index: 7, count: 2)
622 << ListChange::insert(index: 1, count: 3))
623 << 13
624 << 6.
625 << 7;
626 QTest::newRow(dataTag: "no range - remove after, remove before")
627 << QQuickPathView::NoHighlightRange
628 << (QList<ListChange>()
629 << ListChange::remove(index: 6, count: 2)
630 << ListChange::remove(index: 1, count: 3))
631 << 3
632 << 2.
633 << 1;
634
635 QTest::newRow(dataTag: "no range - remove after, insert before")
636 << QQuickPathView::NoHighlightRange
637 << (QList<ListChange>()
638 << ListChange::remove(index: 5, count: 2)
639 << ListChange::insert(index: 1, count: 3))
640 << 9
641 << 2.
642 << 7;
643
644 QTest::newRow(dataTag: "no range - insert after, remove before")
645 << QQuickPathView::NoHighlightRange
646 << (QList<ListChange>()
647 << ListChange::insert(index: 6, count: 2)
648 << ListChange::remove(index: 1, count: 3))
649 << 7
650 << 6.
651 << 1;
652
653 QTest::newRow(dataTag: "no range - insert, remove all, polish, insert")
654 << QQuickPathView::NoHighlightRange
655 << (QList<ListChange>()
656 << ListChange::insert(index: 3, count: 1)
657 << ListChange::remove(index: 0, count: 9)
658 << ListChange::polish()
659 << ListChange::insert(index: 0, count: 3))
660 << 3
661 << 0.
662 << 0;
663}
664
665void tst_QQuickPathView::consecutiveModelChanges()
666{
667 QFETCH(QQuickPathView::HighlightRangeMode, mode);
668 QFETCH(QList<ListChange>, changes);
669 QFETCH(int, count);
670 QFETCH(qreal, offset);
671 QFETCH(int, currentIndex);
672
673 QScopedPointer<QQuickView> window(createView());
674 window->show();
675
676 QaimModel model;
677 model.addItem(name: "Ben", number: "12345");
678 model.addItem(name: "Bohn", number: "2345");
679 model.addItem(name: "Bob", number: "54321");
680 model.addItem(name: "Bill", number: "4321");
681 model.addItem(name: "Jinny", number: "679");
682 model.addItem(name: "Milly", number: "73378");
683 model.addItem(name: "Jimmy", number: "3535");
684 model.addItem(name: "Barb", number: "9039");
685
686 QQmlContext *ctxt = window->rootContext();
687 ctxt->setContextProperty("testModel", &model);
688
689 window->setSource(testFileUrl(fileName: "pathview0.qml"));
690 qApp->processEvents();
691
692 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
693 QVERIFY(pathview != nullptr);
694
695 pathview->setHighlightRangeMode(mode);
696
697 pathview->setCurrentIndex(4);
698 if (mode == QQuickPathView::StrictlyEnforceRange)
699 QTRY_COMPARE(pathview->offset(), 4.0);
700 else
701 pathview->setOffset(4);
702
703 for (int i=0; i<changes.count(); i++) {
704 switch (changes[i].type) {
705 case ListChange::Inserted:
706 {
707 QList<QPair<QString, QString> > items;
708 for (int j=changes[i].index; j<changes[i].index + changes[i].count; ++j)
709 items << qMakePair(x: QString("new item %1").arg(a: j), y: QString::number(j));
710 model.insertItems(index: changes[i].index, items);
711 break;
712 }
713 case ListChange::Removed:
714 model.removeItems(index: changes[i].index, count: changes[i].count);
715 break;
716 case ListChange::Moved:
717 model.moveItems(from: changes[i].index, to: changes[i].to, count: changes[i].count);
718 break;
719 case ListChange::SetCurrent:
720 pathview->setCurrentIndex(changes[i].index);
721 break;
722 case ListChange::Polish:
723 QQuickTest::qWaitForItemPolished(item: pathview);
724 break;
725 default:
726 continue;
727 }
728 }
729 QQuickTest::qWaitForItemPolished(item: pathview);
730
731 QCOMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), count);
732 QCOMPARE(pathview->count(), count);
733 QTRY_COMPARE(pathview->offset(), offset);
734
735 QCOMPARE(pathview->currentIndex(), currentIndex);
736
737}
738
739void tst_QQuickPathView::path()
740{
741 QQmlEngine engine;
742 QQmlComponent c(&engine, testFileUrl(fileName: "pathtest.qml"));
743 QQuickPath *obj = qobject_cast<QQuickPath*>(object: c.create());
744
745 QVERIFY(obj != nullptr);
746 QCOMPARE(obj->startX(), 120.);
747 QCOMPARE(obj->startY(), 100.);
748 QVERIFY(obj->path() != QPainterPath());
749
750 QQmlListReference list(obj, "pathElements");
751 QCOMPARE(list.count(), 5);
752
753 QQuickPathAttribute* attr = qobject_cast<QQuickPathAttribute*>(object: list.at(0));
754 QVERIFY(attr != nullptr);
755 QCOMPARE(attr->name(), QString("scale"));
756 QCOMPARE(attr->value(), 1.0);
757
758 QQuickPathQuad* quad = qobject_cast<QQuickPathQuad*>(object: list.at(1));
759 QVERIFY(quad != nullptr);
760 QCOMPARE(quad->x(), 120.);
761 QCOMPARE(quad->y(), 25.);
762 QCOMPARE(quad->controlX(), 260.);
763 QCOMPARE(quad->controlY(), 75.);
764
765 QQuickPathPercent* perc = qobject_cast<QQuickPathPercent*>(object: list.at(2));
766 QVERIFY(perc != nullptr);
767 QCOMPARE(perc->value(), 0.3);
768
769 QQuickPathLine* line = qobject_cast<QQuickPathLine*>(object: list.at(3));
770 QVERIFY(line != nullptr);
771 QCOMPARE(line->x(), 120.);
772 QCOMPARE(line->y(), 100.);
773
774 QQuickPathCubic* cubic = qobject_cast<QQuickPathCubic*>(object: list.at(4));
775 QVERIFY(cubic != nullptr);
776 QCOMPARE(cubic->x(), 180.);
777 QCOMPARE(cubic->y(), 0.);
778 QCOMPARE(cubic->control1X(), -10.);
779 QCOMPARE(cubic->control1Y(), 90.);
780 QCOMPARE(cubic->control2X(), 210.);
781 QCOMPARE(cubic->control2Y(), 90.);
782
783 delete obj;
784}
785
786void tst_QQuickPathView::dataModel()
787{
788#ifdef Q_OS_MACOS
789 QSKIP("this test currently crashes on MacOS. See QTBUG-68047");
790#endif
791
792 QScopedPointer<QQuickView> window(createView());
793 window->show();
794
795 QQmlContext *ctxt = window->rootContext();
796 TestObject *testObject = new TestObject;
797 ctxt->setContextProperty("testObject", testObject);
798
799 QaimModel model;
800 model.addItem(name: "red", number: "1");
801 model.addItem(name: "green", number: "2");
802 model.addItem(name: "blue", number: "3");
803 model.addItem(name: "purple", number: "4");
804 model.addItem(name: "gray", number: "5");
805 model.addItem(name: "brown", number: "6");
806 model.addItem(name: "yellow", number: "7");
807 model.addItem(name: "thistle", number: "8");
808 model.addItem(name: "cyan", number: "9");
809 model.addItem(name: "peachpuff", number: "10");
810 model.addItem(name: "powderblue", number: "11");
811 model.addItem(name: "gold", number: "12");
812 model.addItem(name: "sandybrown", number: "13");
813
814 ctxt->setContextProperty("testData", &model);
815
816 window->setSource(testFileUrl(fileName: "datamodel.qml"));
817 qApp->processEvents();
818
819 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
820 QVERIFY(pathview != nullptr);
821
822 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
823 QVERIFY(!testObject->error());
824
825 QQuickItem *item = findItem<QQuickItem>(parent: pathview, objectName: "wrapper", index: 0);
826 QVERIFY(item);
827 QCOMPARE(item->x(), 110.0);
828 QCOMPARE(item->y(), 10.0);
829
830 model.insertItem(index: 4, name: "orange", number: "10");
831 QTest::qWait(ms: 100);
832
833 QCOMPARE(window->rootObject()->property("viewCount").toInt(), model.count());
834 QTRY_COMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), 14);
835
836 QCOMPARE(pathview->currentIndex(), 0);
837 QCOMPARE(pathview->currentItem(), findItem<QQuickItem>(pathview, "wrapper", 0));
838
839 QQuickText *text = findItem<QQuickText>(parent: pathview, objectName: "myText", index: 4);
840 QVERIFY(text);
841 QCOMPARE(text->text(), model.name(4));
842
843 model.removeItem(index: 2);
844 QCOMPARE(window->rootObject()->property("viewCount").toInt(), model.count());
845 text = findItem<QQuickText>(parent: pathview, objectName: "myText", index: 2);
846 QVERIFY(text);
847 QCOMPARE(text->text(), model.name(2));
848 QCOMPARE(pathview->currentItem(), findItem<QQuickItem>(pathview, "wrapper", 0));
849
850 testObject->setPathItemCount(5);
851 QMetaObject::invokeMethod(obj: window->rootObject(), member: "checkProperties");
852 QVERIFY(!testObject->error());
853
854 QTRY_COMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), 5);
855
856 QQuickRectangle *testItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 4);
857 QVERIFY(testItem != nullptr);
858 testItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 5);
859 QVERIFY(!testItem);
860
861 pathview->setCurrentIndex(1);
862 QCOMPARE(pathview->currentIndex(), 1);
863 QCOMPARE(pathview->currentItem(), findItem<QQuickItem>(pathview, "wrapper", 1));
864
865 model.insertItem(index: 2, name: "pink", number: "2");
866
867 QTRY_COMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), 5);
868 QCOMPARE(pathview->currentIndex(), 1);
869 QCOMPARE(pathview->currentItem(), findItem<QQuickItem>(pathview, "wrapper", 1));
870
871 QTRY_VERIFY(text = findItem<QQuickText>(pathview, "myText", 2));
872 QCOMPARE(text->text(), model.name(2));
873
874 model.removeItem(index: 3);
875 QTRY_COMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), 5);
876 text = findItem<QQuickText>(parent: pathview, objectName: "myText", index: 3);
877 QVERIFY(text);
878 QCOMPARE(text->text(), model.name(3));
879 QCOMPARE(pathview->currentItem(), findItem<QQuickItem>(pathview, "wrapper", 1));
880
881 model.moveItem(from: 3, to: 5);
882 QTRY_COMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), 5);
883 QList<QQuickItem*> items = findItems<QQuickItem>(parent: pathview, objectName: "wrapper");
884 foreach (QQuickItem *item, items) {
885 QVERIFY(item->property("onPath").toBool());
886 }
887 QCOMPARE(pathview->currentItem(), findItem<QQuickItem>(pathview, "wrapper", 1));
888
889 // QTBUG-14199
890 pathview->setOffset(7);
891 pathview->setOffset(0);
892 QCOMPARE(findItems<QQuickItem>(pathview, "wrapper").count(), 5);
893
894 pathview->setCurrentIndex(model.count()-1);
895 QTRY_COMPARE(pathview->offset(), 1.0);
896 model.removeItem(index: model.count()-1);
897 QCOMPARE(pathview->currentIndex(), model.count()-1);
898
899 delete testObject;
900}
901
902void tst_QQuickPathView::pathMoved()
903{
904 QScopedPointer<QQuickView> window(createView());
905 window->show();
906
907 QaimModel model;
908 model.addItem(name: "Ben", number: "12345");
909 model.addItem(name: "Bohn", number: "2345");
910 model.addItem(name: "Bob", number: "54321");
911 model.addItem(name: "Bill", number: "4321");
912
913 QQmlContext *ctxt = window->rootContext();
914 ctxt->setContextProperty("testModel", &model);
915
916 window->setSource(testFileUrl(fileName: "pathview0.qml"));
917 qApp->processEvents();
918
919 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
920 QVERIFY(pathview != nullptr);
921
922 QQuickRectangle *firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
923 QVERIFY(firstItem);
924 QQuickPath *path = qobject_cast<QQuickPath*>(object: pathview->path());
925 QVERIFY(path);
926 QPointF start = path->pointAtPercent(t: 0.0);
927 QPointF offset;//Center of item is at point, but pos is from corner
928 offset.setX(firstItem->width()/2);
929 offset.setY(firstItem->height()/2);
930 QTRY_COMPARE(firstItem->position() + offset, start);
931 pathview->setOffset(1.0);
932
933 for (int i=0; i<model.count(); i++) {
934 QQuickRectangle *curItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: i);
935 QPointF itemPos(path->pointAtPercent(t: 0.25 + i*0.25));
936 QCOMPARE(curItem->position() + offset, QPointF(itemPos.x(), itemPos.y()));
937 }
938
939 QCOMPARE(pathview->currentIndex(), 3);
940
941 pathview->setOffset(0.0);
942 QCOMPARE(firstItem->position() + offset, start);
943 QCOMPARE(pathview->currentIndex(), 0);
944
945 // Change delegate size
946 pathview->setOffset(0.1);
947 pathview->setOffset(0.0);
948 window->rootObject()->setProperty(name: "delegateWidth", value: 30);
949 QCOMPARE(firstItem->width(), 30.0);
950 offset.setX(firstItem->width()/2);
951 QTRY_COMPARE(firstItem->position() + offset, start);
952
953 // Change delegate scale
954 pathview->setOffset(0.1);
955 pathview->setOffset(0.0);
956 window->rootObject()->setProperty(name: "delegateScale", value: 1.2);
957 QTRY_COMPARE(firstItem->position() + offset, start);
958
959}
960
961void tst_QQuickPathView::offset_data()
962{
963 QTest::addColumn<qreal>(name: "offset");
964 QTest::addColumn<int>(name: "currentIndex");
965
966 QTest::newRow(dataTag: "0.0") << 0.0 << 0;
967 QTest::newRow(dataTag: "1.0") << 7.0 << 1;
968 QTest::newRow(dataTag: "5.0") << 5.0 << 3;
969 QTest::newRow(dataTag: "4.6") << 4.6 << 3;
970 QTest::newRow(dataTag: "4.4") << 4.4 << 4;
971 QTest::newRow(dataTag: "5.4") << 5.4 << 3;
972 QTest::newRow(dataTag: "5.6") << 5.6 << 2;
973}
974
975void tst_QQuickPathView::offset()
976{
977 QFETCH(qreal, offset);
978 QFETCH(int, currentIndex);
979
980 QQmlEngine engine;
981 QQmlComponent c(&engine, testFileUrl(fileName: "pathview3.qml"));
982 QQuickPathView *view = qobject_cast<QQuickPathView*>(object: c.create());
983
984 view->setOffset(offset);
985 QCOMPARE(view->currentIndex(), currentIndex);
986
987 delete view;
988}
989
990void tst_QQuickPathView::setCurrentIndex()
991{
992 QScopedPointer<QQuickView> window(createView());
993 window->show();
994
995 QaimModel model;
996 model.addItem(name: "Ben", number: "12345");
997 model.addItem(name: "Bohn", number: "2345");
998 model.addItem(name: "Bob", number: "54321");
999 model.addItem(name: "Bill", number: "4321");
1000
1001 QQmlContext *ctxt = window->rootContext();
1002 ctxt->setContextProperty("testModel", &model);
1003
1004 window->setSource(testFileUrl(fileName: "pathview0.qml"));
1005 qApp->processEvents();
1006
1007 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
1008 QVERIFY(pathview != nullptr);
1009
1010 QQuickRectangle *firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1011 QVERIFY(firstItem);
1012 QQuickPath *path = qobject_cast<QQuickPath*>(object: pathview->path());
1013 QVERIFY(path);
1014 QPointF start = path->pointAtPercent(t: 0.0);
1015 QPointF offset;//Center of item is at point, but pos is from corner
1016 offset.setX(firstItem->width()/2);
1017 offset.setY(firstItem->height()/2);
1018 QCOMPARE(firstItem->position() + offset, start);
1019 QCOMPARE(window->rootObject()->property("currentA").toInt(), 0);
1020 QCOMPARE(window->rootObject()->property("currentB").toInt(), 0);
1021
1022 pathview->setCurrentIndex(2);
1023
1024 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 2);
1025 QTRY_COMPARE(firstItem->position() + offset, start);
1026 QCOMPARE(window->rootObject()->property("currentA").toInt(), 2);
1027 QCOMPARE(window->rootObject()->property("currentB").toInt(), 2);
1028 QCOMPARE(pathview->currentItem(), firstItem);
1029 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1030
1031 pathview->decrementCurrentIndex();
1032 QTRY_COMPARE(pathview->currentIndex(), 1);
1033 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 1);
1034 QVERIFY(firstItem);
1035 QTRY_COMPARE(firstItem->position() + offset, start);
1036 QCOMPARE(pathview->currentItem(), firstItem);
1037 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1038
1039 pathview->decrementCurrentIndex();
1040 QTRY_COMPARE(pathview->currentIndex(), 0);
1041 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1042 QVERIFY(firstItem);
1043 QTRY_COMPARE(firstItem->position() + offset, start);
1044 QCOMPARE(pathview->currentItem(), firstItem);
1045 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1046
1047 pathview->decrementCurrentIndex();
1048 QTRY_COMPARE(pathview->currentIndex(), 3);
1049 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 3);
1050 QVERIFY(firstItem);
1051 QTRY_COMPARE(firstItem->position() + offset, start);
1052 QCOMPARE(pathview->currentItem(), firstItem);
1053 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1054
1055 pathview->incrementCurrentIndex();
1056 QTRY_COMPARE(pathview->currentIndex(), 0);
1057 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1058 QVERIFY(firstItem);
1059 QTRY_COMPARE(firstItem->position() + offset, start);
1060 QCOMPARE(pathview->currentItem(), firstItem);
1061 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1062
1063 // Test positive indexes are wrapped.
1064 pathview->setCurrentIndex(6);
1065 QTRY_COMPARE(pathview->currentIndex(), 2);
1066 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 2);
1067 QVERIFY(firstItem);
1068 QTRY_COMPARE(firstItem->position() + offset, start);
1069 QCOMPARE(pathview->currentItem(), firstItem);
1070 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1071
1072 // Test negative indexes are wrapped.
1073 pathview->setCurrentIndex(-3);
1074 QTRY_COMPARE(pathview->currentIndex(), 1);
1075 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 1);
1076 QVERIFY(firstItem);
1077 QTRY_COMPARE(firstItem->position() + offset, start);
1078 QCOMPARE(pathview->currentItem(), firstItem);
1079 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1080
1081 // move an item, set move duration to 0, and change currentIndex to moved item. QTBUG-22786
1082 model.moveItem(from: 0, to: 3);
1083 pathview->setHighlightMoveDuration(0);
1084 pathview->setCurrentIndex(3);
1085 QCOMPARE(pathview->currentIndex(), 3);
1086 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 3);
1087 QVERIFY(firstItem);
1088 QCOMPARE(pathview->currentItem(), firstItem);
1089 QTRY_COMPARE(firstItem->position() + offset, start);
1090 model.moveItem(from: 3, to: 0);
1091 pathview->setCurrentIndex(0);
1092 pathview->setHighlightMoveDuration(300);
1093
1094 // Check the current item is still created when outside the bounds of pathItemCount.
1095 pathview->setPathItemCount(2);
1096 pathview->setHighlightRangeMode(QQuickPathView::NoHighlightRange);
1097 QVERIFY(findItem<QQuickRectangle>(pathview, "wrapper", 0));
1098 QVERIFY(findItem<QQuickRectangle>(pathview, "wrapper", 1));
1099 QVERIFY(!findItem<QQuickRectangle>(pathview, "wrapper", 2));
1100 QVERIFY(!findItem<QQuickRectangle>(pathview, "wrapper", 3));
1101
1102 pathview->setCurrentIndex(2);
1103 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 2);
1104 QCOMPARE(pathview->currentItem(), firstItem);
1105 QCOMPARE(firstItem->property("onPath"), QVariant(false));
1106
1107 pathview->decrementCurrentIndex();
1108 QTRY_COMPARE(pathview->currentIndex(), 1);
1109 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 1);
1110 QVERIFY(firstItem);
1111 QCOMPARE(pathview->currentItem(), firstItem);
1112 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1113
1114 pathview->decrementCurrentIndex();
1115 QTRY_COMPARE(pathview->currentIndex(), 0);
1116 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1117 QVERIFY(firstItem);
1118 QCOMPARE(pathview->currentItem(), firstItem);
1119 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1120
1121 pathview->decrementCurrentIndex();
1122 QTRY_COMPARE(pathview->currentIndex(), 3);
1123 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 3);
1124 QVERIFY(firstItem);
1125 QCOMPARE(pathview->currentItem(), firstItem);
1126 QCOMPARE(firstItem->property("onPath"), QVariant(false));
1127
1128 pathview->incrementCurrentIndex();
1129 QTRY_COMPARE(pathview->currentIndex(), 0);
1130 firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1131 QVERIFY(firstItem);
1132 QCOMPARE(pathview->currentItem(), firstItem);
1133 QCOMPARE(firstItem->property("onPath"), QVariant(true));
1134
1135 // check for bogus currentIndexChanged() signals
1136 QSignalSpy currentIndexSpy(pathview, SIGNAL(currentIndexChanged()));
1137 QVERIFY(currentIndexSpy.isValid());
1138 pathview->setHighlightMoveDuration(100);
1139 pathview->setHighlightRangeMode(QQuickPathView::StrictlyEnforceRange);
1140 pathview->setSnapMode(QQuickPathView::SnapToItem);
1141 pathview->setCurrentIndex(3);
1142 QTRY_COMPARE(pathview->currentIndex(), 3);
1143 QCOMPARE(currentIndexSpy.count(), 1);
1144}
1145
1146void tst_QQuickPathView::setCurrentIndexWrap()
1147{
1148 QScopedPointer<QQuickView> window(createView());
1149 window->setSource(testFileUrl(fileName: "pathview5.qml"));
1150 window->show();
1151 qApp->processEvents();
1152
1153 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1154 QVERIFY(pathview);
1155
1156 // set current index to last item
1157 pathview->setCurrentIndex(4);
1158 // set currentIndex to first item, then quickly set it back (QTBUG-74508)
1159 QSignalSpy currentIndexSpy(pathview, SIGNAL(currentIndexChanged()));
1160 QSignalSpy movementStartedSpy(pathview, SIGNAL(movementStarted()));
1161 pathview->setCurrentIndex(0);
1162 pathview->setCurrentIndex(4);
1163 QCOMPARE(pathview->currentIndex(), 4);
1164 QCOMPARE(currentIndexSpy.count(), 2);
1165 QCOMPARE(movementStartedSpy.count(), 0);
1166}
1167
1168void tst_QQuickPathView::resetModel()
1169{
1170 QScopedPointer<QQuickView> window(createView());
1171
1172 QStringList strings;
1173 strings << "one" << "two" << "three";
1174 QStringListModel model(strings);
1175
1176 QQmlContext *ctxt = window->rootContext();
1177 ctxt->setContextProperty("testModel", &model);
1178
1179 window->setSource(testFileUrl(fileName: "displaypath.qml"));
1180 qApp->processEvents();
1181
1182 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
1183 QVERIFY(pathview != nullptr);
1184
1185 QCOMPARE(pathview->count(), model.rowCount());
1186
1187 for (int i = 0; i < model.rowCount(); ++i) {
1188 QQuickText *display = findItem<QQuickText>(parent: pathview, objectName: "displayText", index: i);
1189 QVERIFY(display != nullptr);
1190 QCOMPARE(display->text(), strings.at(i));
1191 }
1192
1193 strings.clear();
1194 strings << "four" << "five" << "six" << "seven";
1195 model.setStringList(strings);
1196
1197 QCOMPARE(pathview->count(), model.rowCount());
1198
1199 for (int i = 0; i < model.rowCount(); ++i) {
1200 QQuickText *display = findItem<QQuickText>(parent: pathview, objectName: "displayText", index: i);
1201 QVERIFY(display != nullptr);
1202 QCOMPARE(display->text(), strings.at(i));
1203 }
1204
1205}
1206
1207void tst_QQuickPathView::propertyChanges()
1208{
1209 QScopedPointer<QQuickView> window(createView());
1210 QVERIFY(window);
1211 window->setSource(testFileUrl(fileName: "propertychanges.qml"));
1212
1213 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "pathView");
1214 QVERIFY(pathView);
1215
1216 QSignalSpy snapPositionSpy(pathView, SIGNAL(preferredHighlightBeginChanged()));
1217 QSignalSpy dragMarginSpy(pathView, SIGNAL(dragMarginChanged()));
1218
1219 QCOMPARE(pathView->preferredHighlightBegin(), 0.1);
1220 QCOMPARE(pathView->dragMargin(), 5.0);
1221
1222 pathView->setPreferredHighlightBegin(0.4);
1223 pathView->setPreferredHighlightEnd(0.4);
1224 pathView->setDragMargin(20.0);
1225
1226 QCOMPARE(pathView->preferredHighlightBegin(), 0.4);
1227 QCOMPARE(pathView->preferredHighlightEnd(), 0.4);
1228 QCOMPARE(pathView->dragMargin(), 20.0);
1229
1230 QCOMPARE(snapPositionSpy.count(), 1);
1231 QCOMPARE(dragMarginSpy.count(), 1);
1232
1233 pathView->setPreferredHighlightBegin(0.4);
1234 pathView->setPreferredHighlightEnd(0.4);
1235 pathView->setDragMargin(20.0);
1236
1237 QCOMPARE(snapPositionSpy.count(), 1);
1238 QCOMPARE(dragMarginSpy.count(), 1);
1239
1240 QSignalSpy maximumFlickVelocitySpy(pathView, SIGNAL(maximumFlickVelocityChanged()));
1241 pathView->setMaximumFlickVelocity(1000);
1242 QCOMPARE(maximumFlickVelocitySpy.count(), 1);
1243 pathView->setMaximumFlickVelocity(1000);
1244 QCOMPARE(maximumFlickVelocitySpy.count(), 1);
1245
1246}
1247
1248void tst_QQuickPathView::pathChanges()
1249{
1250 QScopedPointer<QQuickView> window(createView());
1251 QVERIFY(window);
1252 window->setSource(testFileUrl(fileName: "propertychanges.qml"));
1253
1254 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "pathView");
1255 QVERIFY(pathView);
1256
1257 QQuickPath *path = window->rootObject()->findChild<QQuickPath*>(aName: "path");
1258 QVERIFY(path);
1259
1260 QSignalSpy startXSpy(path, SIGNAL(startXChanged()));
1261 QSignalSpy startYSpy(path, SIGNAL(startYChanged()));
1262
1263 QCOMPARE(path->startX(), 220.0);
1264 QCOMPARE(path->startY(), 200.0);
1265
1266 path->setStartX(240.0);
1267 path->setStartY(220.0);
1268
1269 QCOMPARE(path->startX(), 240.0);
1270 QCOMPARE(path->startY(), 220.0);
1271
1272 QCOMPARE(startXSpy.count(),1);
1273 QCOMPARE(startYSpy.count(),1);
1274
1275 path->setStartX(240);
1276 path->setStartY(220);
1277
1278 QCOMPARE(startXSpy.count(),1);
1279 QCOMPARE(startYSpy.count(),1);
1280
1281 QQuickPath *alternatePath = window->rootObject()->findChild<QQuickPath*>(aName: "alternatePath");
1282 QVERIFY(alternatePath);
1283
1284 QSignalSpy pathSpy(pathView, SIGNAL(pathChanged()));
1285
1286 QCOMPARE(pathView->path(), path);
1287
1288 pathView->setPath(alternatePath);
1289 QCOMPARE(pathView->path(), alternatePath);
1290 QCOMPARE(pathSpy.count(),1);
1291
1292 pathView->setPath(alternatePath);
1293 QCOMPARE(pathSpy.count(),1);
1294
1295 QQuickPathAttribute *pathAttribute = window->rootObject()->findChild<QQuickPathAttribute*>(aName: "pathAttribute");
1296 QVERIFY(pathAttribute);
1297
1298 QSignalSpy nameSpy(pathAttribute, SIGNAL(nameChanged()));
1299 QCOMPARE(pathAttribute->name(), QString("opacity"));
1300
1301 pathAttribute->setName("scale");
1302 QCOMPARE(pathAttribute->name(), QString("scale"));
1303 QCOMPARE(nameSpy.count(),1);
1304
1305 pathAttribute->setName("scale");
1306 QCOMPARE(nameSpy.count(),1);
1307}
1308
1309void tst_QQuickPathView::componentChanges()
1310{
1311 QScopedPointer<QQuickView> window(createView());
1312 QVERIFY(window);
1313 window->setSource(testFileUrl(fileName: "propertychanges.qml"));
1314
1315 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "pathView");
1316 QVERIFY(pathView);
1317
1318 QQmlComponent delegateComponent(window->engine());
1319 delegateComponent.setData("import QtQuick 2.0; Text { text: '<b>Name:</b> ' + name }", baseUrl: QUrl::fromLocalFile(localfile: ""));
1320
1321 QSignalSpy delegateSpy(pathView, SIGNAL(delegateChanged()));
1322
1323 pathView->setDelegate(&delegateComponent);
1324 QCOMPARE(pathView->delegate(), &delegateComponent);
1325 QCOMPARE(delegateSpy.count(),1);
1326
1327 pathView->setDelegate(&delegateComponent);
1328 QCOMPARE(delegateSpy.count(),1);
1329}
1330
1331void tst_QQuickPathView::modelChanges()
1332{
1333 QScopedPointer<QQuickView> window(createView());
1334 QVERIFY(window);
1335 window->setSource(testFileUrl(fileName: "propertychanges.qml"));
1336
1337 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "pathView");
1338 QVERIFY(pathView);
1339 pathView->setCurrentIndex(3);
1340 QTRY_COMPARE(pathView->offset(), 6.0);
1341
1342 QQmlListModel *alternateModel = window->rootObject()->findChild<QQmlListModel*>(aName: "alternateModel");
1343 QVERIFY(alternateModel);
1344 QVariant modelVariant = QVariant::fromValue<QObject *>(value: alternateModel);
1345 QSignalSpy modelSpy(pathView, SIGNAL(modelChanged()));
1346 QSignalSpy currentIndexSpy(pathView, SIGNAL(currentIndexChanged()));
1347
1348 QCOMPARE(pathView->currentIndex(), 3);
1349 pathView->setModel(modelVariant);
1350 QCOMPARE(pathView->model(), modelVariant);
1351 QCOMPARE(modelSpy.count(),1);
1352 QCOMPARE(pathView->currentIndex(), 0);
1353 QCOMPARE(currentIndexSpy.count(), 1);
1354
1355 pathView->setModel(modelVariant);
1356 QCOMPARE(modelSpy.count(),1);
1357
1358 pathView->setModel(QVariant());
1359 QCOMPARE(modelSpy.count(),2);
1360 QCOMPARE(pathView->currentIndex(), 0);
1361 QCOMPARE(currentIndexSpy.count(), 1);
1362
1363}
1364
1365void tst_QQuickPathView::pathUpdateOnStartChanged()
1366{
1367 QScopedPointer<QQuickView> window(createView());
1368 QVERIFY(window);
1369 window->setSource(testFileUrl(fileName: "pathUpdateOnStartChanged.qml"));
1370
1371 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "pathView");
1372 QVERIFY(pathView);
1373
1374 QQuickPath *path = window->rootObject()->findChild<QQuickPath*>(aName: "path");
1375 QVERIFY(path);
1376 QCOMPARE(path->startX(), 400.0);
1377 QCOMPARE(path->startY(), 300.0);
1378
1379 QQuickItem *item = findItem<QQuickItem>(parent: pathView, objectName: "wrapper", index: 0);
1380 QVERIFY(item);
1381 QCOMPARE(item->x(), path->startX() - item->width() / 2.0);
1382 QCOMPARE(item->y(), path->startY() - item->height() / 2.0);
1383
1384}
1385
1386void tst_QQuickPathView::package()
1387{
1388 QScopedPointer<QQuickView> window(createView());
1389 QVERIFY(window);
1390 window->setSource(testFileUrl(fileName: "pathview_package.qml"));
1391 window->show();
1392 window->requestActivate();
1393 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1394
1395 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "photoPathView");
1396 QVERIFY(pathView);
1397
1398#ifdef Q_OS_MAC
1399 QSKIP("QTBUG-27170 view does not reliably receive polish without a running animation");
1400#endif
1401
1402 QQuickTest::qWaitForItemPolished(item: pathView);
1403 QQuickItem *item = findItem<QQuickItem>(parent: pathView, objectName: "pathItem");
1404 QVERIFY(item);
1405 QVERIFY(item->scale() != 1.0);
1406
1407}
1408
1409//QTBUG-13017
1410void tst_QQuickPathView::emptyModel()
1411{
1412 QScopedPointer<QQuickView> window(createView());
1413
1414 QStringListModel model;
1415
1416 QQmlContext *ctxt = window->rootContext();
1417 ctxt->setContextProperty("emptyModel", &model);
1418
1419 window->setSource(testFileUrl(fileName: "emptymodel.qml"));
1420 qApp->processEvents();
1421
1422 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1423 QVERIFY(pathview != nullptr);
1424
1425 QCOMPARE(pathview->offset(), qreal(0.0));
1426
1427}
1428
1429void tst_QQuickPathView::emptyPath()
1430{
1431 QQuickView *window = createView();
1432
1433 window->setSource(testFileUrl(fileName: "emptypath.qml"));
1434 qApp->processEvents();
1435
1436 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1437 QVERIFY(pathview != nullptr);
1438
1439 delete window;
1440}
1441
1442void tst_QQuickPathView::closed()
1443{
1444 QQmlEngine engine;
1445
1446 {
1447 QQmlComponent c(&engine, testFileUrl(fileName: "openPath.qml"));
1448 QQuickPath *obj = qobject_cast<QQuickPath*>(object: c.create());
1449 QVERIFY(obj);
1450 QCOMPARE(obj->isClosed(), false);
1451 delete obj;
1452 }
1453
1454 {
1455 QQmlComponent c(&engine, testFileUrl(fileName: "closedPath.qml"));
1456 QQuickPath *obj = qobject_cast<QQuickPath*>(object: c.create());
1457 QVERIFY(obj);
1458 QCOMPARE(obj->isClosed(), true);
1459 delete obj;
1460 }
1461}
1462
1463// QTBUG-14239
1464void tst_QQuickPathView::pathUpdate()
1465{
1466 QScopedPointer<QQuickView> window(createView());
1467 QVERIFY(window);
1468 window->setSource(testFileUrl(fileName: "pathUpdate.qml"));
1469
1470 QQuickPathView *pathView = window->rootObject()->findChild<QQuickPathView*>(aName: "pathView");
1471 QVERIFY(pathView);
1472
1473 QQuickItem *item = findItem<QQuickItem>(parent: pathView, objectName: "wrapper", index: 0);
1474 QVERIFY(item);
1475 QCOMPARE(item->x(), 150.0);
1476
1477}
1478
1479void tst_QQuickPathView::visualDataModel()
1480{
1481 QQmlEngine engine;
1482 QQmlComponent c(&engine, testFileUrl(fileName: "vdm.qml"));
1483
1484 QQuickPathView *obj = qobject_cast<QQuickPathView*>(object: c.create());
1485 QVERIFY(obj != nullptr);
1486
1487 QCOMPARE(obj->count(), 3);
1488
1489 delete obj;
1490}
1491
1492void tst_QQuickPathView::undefinedPath()
1493{
1494 QQmlEngine engine;
1495
1496 // QPainterPath warnings are only received if QT_NO_DEBUG is not defined
1497 if (QLibraryInfo::isDebugBuild()) {
1498 QRegularExpression warning1("^QPainterPath::moveTo:.*ignoring call$");
1499 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: warning1);
1500
1501 QRegularExpression warning2("^QPainterPath::lineTo:.*ignoring call$");
1502 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: warning2);
1503 }
1504
1505 QQmlComponent c(&engine, testFileUrl(fileName: "undefinedpath.qml"));
1506
1507 QQuickPathView *obj = qobject_cast<QQuickPathView*>(object: c.create());
1508 QVERIFY(obj != nullptr);
1509
1510 QCOMPARE(obj->count(), 3);
1511
1512 delete obj;
1513}
1514
1515void tst_QQuickPathView::mouseDrag()
1516{
1517 QScopedPointer<QQuickView> window(createView());
1518 QQuickViewTestUtil::moveMouseAway(window: window.data());
1519 window->setSource(testFileUrl(fileName: "dragpath.qml"));
1520 window->show();
1521 window->requestActivate();
1522 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1523 QCOMPARE(window.data(), qGuiApp->focusWindow());
1524
1525 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1526 QVERIFY(pathview != nullptr);
1527
1528 QSignalSpy movingSpy(pathview, SIGNAL(movingChanged()));
1529 QSignalSpy moveStartedSpy(pathview, SIGNAL(movementStarted()));
1530 QSignalSpy moveEndedSpy(pathview, SIGNAL(movementEnded()));
1531 QSignalSpy draggingSpy(pathview, SIGNAL(draggingChanged()));
1532 QSignalSpy dragStartedSpy(pathview, SIGNAL(dragStarted()));
1533 QSignalSpy dragEndedSpy(pathview, SIGNAL(dragEnded()));
1534
1535 int current = pathview->currentIndex();
1536
1537 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,100));
1538 QTest::qWait(ms: 100);
1539
1540 {
1541 QMouseEvent mv(QEvent::MouseMove, QPoint(50,100), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1542 QGuiApplication::sendEvent(receiver: window.data(), event: &mv);
1543 }
1544 // first move beyond threshold does not trigger drag
1545 QVERIFY(!pathview->isMoving());
1546 QVERIFY(!pathview->isDragging());
1547 QCOMPARE(movingSpy.count(), 0);
1548 QCOMPARE(moveStartedSpy.count(), 0);
1549 QCOMPARE(moveEndedSpy.count(), 0);
1550 QCOMPARE(draggingSpy.count(), 0);
1551 QCOMPARE(dragStartedSpy.count(), 0);
1552 QCOMPARE(dragEndedSpy.count(), 0);
1553
1554 {
1555 QMouseEvent mv(QEvent::MouseMove, QPoint(90,100), Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
1556 QGuiApplication::sendEvent(receiver: window.data(), event: &mv);
1557 }
1558 // next move beyond threshold does trigger drag
1559#ifdef Q_OS_WIN
1560 if (!pathview->isMoving())
1561 QSKIP("Skipping due to interference from external mouse move events.");
1562#endif // Q_OS_WIN
1563 QVERIFY(pathview->isMoving());
1564 QVERIFY(pathview->isDragging());
1565 QCOMPARE(movingSpy.count(), 1);
1566 QCOMPARE(moveStartedSpy.count(), 1);
1567 QCOMPARE(moveEndedSpy.count(), 0);
1568 QCOMPARE(draggingSpy.count(), 1);
1569 QCOMPARE(dragStartedSpy.count(), 1);
1570 QCOMPARE(dragEndedSpy.count(), 0);
1571
1572 QVERIFY(pathview->currentIndex() != current);
1573
1574 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(40,100));
1575 QVERIFY(!pathview->isDragging());
1576 QCOMPARE(draggingSpy.count(), 2);
1577 QCOMPARE(dragStartedSpy.count(), 1);
1578 QCOMPARE(dragEndedSpy.count(), 1);
1579 QTRY_COMPARE(movingSpy.count(), 2);
1580 QTRY_COMPARE(moveEndedSpy.count(), 1);
1581 QCOMPARE(moveStartedSpy.count(), 1);
1582
1583}
1584
1585void tst_QQuickPathView::nestedMouseAreaDrag()
1586{
1587 QScopedPointer<QQuickView> window(createView());
1588 QQuickViewTestUtil::moveMouseAway(window: window.data());
1589 window->setSource(testFileUrl(fileName: "nestedmousearea.qml"));
1590 window->show();
1591 window->requestActivate();
1592 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1593 QCOMPARE(window.data(), qGuiApp->focusWindow());
1594
1595
1596 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1597 QVERIFY(pathview != nullptr);
1598
1599 // Dragging the child mouse area should move it and not animate the PathView
1600 flick(window: window.data(), from: QPoint(200,200), to: QPoint(400,200), duration: 200);
1601 QVERIFY(!pathview->isMoving());
1602
1603 // Dragging outside the mouse are should animate the PathView.
1604 flick(window: window.data(), from: QPoint(75,75), to: QPoint(175,75), duration: 200);
1605 QVERIFY(pathview->isMoving());
1606}
1607
1608void tst_QQuickPathView::flickNClick() // QTBUG-77173
1609{
1610 QScopedPointer<QQuickView> window(createView());
1611 QQuickViewTestUtil::moveMouseAway(window: window.data());
1612 window->setSource(testFileUrl(fileName: "nestedmousearea2.qml"));
1613 window->show();
1614 window->requestActivate();
1615 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1616 QCOMPARE(window.data(), qGuiApp->focusWindow());
1617
1618 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1619 QVERIFY(pathview != nullptr);
1620 QSignalSpy movingChangedSpy(pathview, SIGNAL(movingChanged()));
1621 QSignalSpy draggingSpy(pathview, SIGNAL(draggingChanged()));
1622 QSignalSpy dragStartedSpy(pathview, SIGNAL(dragStarted()));
1623 QSignalSpy dragEndedSpy(pathview, SIGNAL(dragEnded()));
1624 QSignalSpy currentIndexSpy(pathview, SIGNAL(currentIndexChanged()));
1625 QSignalSpy moveStartedSpy(pathview, SIGNAL(movementStarted()));
1626 QSignalSpy moveEndedSpy(pathview, SIGNAL(movementEnded()));
1627 QSignalSpy flickingSpy(pathview, SIGNAL(flickingChanged()));
1628 QSignalSpy flickStartedSpy(pathview, SIGNAL(flickStarted()));
1629 QSignalSpy flickEndedSpy(pathview, SIGNAL(flickEnded()));
1630
1631 for (int duration = 100; duration > 0; duration -= 20) {
1632 movingChangedSpy.clear();
1633 draggingSpy.clear();
1634 dragStartedSpy.clear();
1635 dragEndedSpy.clear();
1636 currentIndexSpy.clear();
1637 moveStartedSpy.clear();
1638 moveEndedSpy.clear();
1639 flickingSpy.clear();
1640 flickStartedSpy.clear();
1641 flickEndedSpy.clear();
1642 // Dragging the child mouse area should animate the PathView (MA has no drag target)
1643 flick(window: window.data(), from: QPoint(200,200), to: QPoint(400,200), duration);
1644 QVERIFY(pathview->isMoving());
1645 QCOMPARE(movingChangedSpy.count(), 1);
1646 QCOMPARE(draggingSpy.count(), 2);
1647 QCOMPARE(dragStartedSpy.count(), 1);
1648 QCOMPARE(dragEndedSpy.count(), 1);
1649 QVERIFY(currentIndexSpy.count() > 0);
1650 QCOMPARE(moveStartedSpy.count(), 1);
1651 QCOMPARE(moveEndedSpy.count(), 0);
1652 QCOMPARE(flickingSpy.count(), 1);
1653 QCOMPARE(flickStartedSpy.count(), 1);
1654 QCOMPARE(flickEndedSpy.count(), 0);
1655
1656 // Now while it's still moving, click it.
1657 // The PathView should stop at a position such that offset is a whole number.
1658 QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(200, 200));
1659 QTRY_VERIFY(!pathview->isMoving());
1660 QCOMPARE(movingChangedSpy.count(), 2); // QTBUG-78926
1661 QCOMPARE(draggingSpy.count(), 2);
1662 QCOMPARE(dragStartedSpy.count(), 1);
1663 QCOMPARE(dragEndedSpy.count(), 1);
1664 QCOMPARE(moveStartedSpy.count(), 1);
1665 QCOMPARE(moveEndedSpy.count(), 1);
1666 QCOMPARE(flickingSpy.count(), 2);
1667 QCOMPARE(flickStartedSpy.count(), 1);
1668 QCOMPARE(flickEndedSpy.count(), 1);
1669 QVERIFY(qFuzzyIsNull(pathview->offset() - int(pathview->offset())));
1670 }
1671}
1672
1673void tst_QQuickPathView::treeModel()
1674{
1675 QScopedPointer<QQuickView> window(createView());
1676 window->show();
1677
1678 QStandardItemModel model;
1679 initStandardTreeModel(model: &model);
1680 window->engine()->rootContext()->setContextProperty("myModel", &model);
1681
1682 window->setSource(testFileUrl(fileName: "treemodel.qml"));
1683
1684 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1685 QVERIFY(pathview != nullptr);
1686 QCOMPARE(pathview->count(), 3);
1687
1688 QQuickText *item = findItem<QQuickText>(parent: pathview, objectName: "wrapper", index: 0);
1689 QVERIFY(item);
1690 QCOMPARE(item->text(), QLatin1String("Row 1 Item"));
1691
1692 QVERIFY(QMetaObject::invokeMethod(pathview, "setRoot", Q_ARG(QVariant, 1)));
1693 QCOMPARE(pathview->count(), 1);
1694
1695 QTRY_VERIFY(item = findItem<QQuickText>(pathview, "wrapper", 0));
1696 QTRY_COMPARE(item->text(), QLatin1String("Row 2 Child Item"));
1697
1698}
1699
1700void tst_QQuickPathView::changePreferredHighlight()
1701{
1702 QScopedPointer<QQuickView> window(createView());
1703 window->setGeometry(posx: 0,posy: 0,w: 400,h: 200);
1704 window->setSource(testFileUrl(fileName: "dragpath.qml"));
1705 window->show();
1706 window->requestActivate();
1707 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1708 QCOMPARE(window.data(), qGuiApp->focusWindow());
1709
1710 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1711 QVERIFY(pathview != nullptr);
1712
1713 int current = pathview->currentIndex();
1714 QCOMPARE(current, 0);
1715
1716 QQuickRectangle *firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1717 QVERIFY(firstItem);
1718 QQuickPath *path = qobject_cast<QQuickPath*>(object: pathview->path());
1719 QVERIFY(path);
1720 QPointF start = path->pointAtPercent(t: 0.5);
1721 QPointF offset;//Center of item is at point, but pos is from corner
1722 offset.setX(firstItem->width()/2);
1723 offset.setY(firstItem->height()/2);
1724 QTRY_COMPARE(firstItem->position() + offset, start);
1725
1726 pathview->setPreferredHighlightBegin(0.8);
1727 pathview->setPreferredHighlightEnd(0.8);
1728 start = path->pointAtPercent(t: 0.8);
1729 QTRY_COMPARE(firstItem->position() + offset, start);
1730 QCOMPARE(pathview->currentIndex(), 0);
1731
1732}
1733
1734void tst_QQuickPathView::creationContext()
1735{
1736 QQuickView window;
1737 window.setGeometry(posx: 0,posy: 0,w: 240,h: 320);
1738 window.setSource(testFileUrl(fileName: "creationContext.qml"));
1739
1740 QQuickItem *rootItem = qobject_cast<QQuickItem *>(object: window.rootObject());
1741 QVERIFY(rootItem);
1742 QVERIFY(rootItem->property("count").toInt() > 0);
1743
1744 QQuickItem *item = findItem<QQuickItem>(parent: rootItem, objectName: "listItem", index: 0);
1745 QVERIFY(item);
1746 QCOMPARE(item->property("text").toString(), QString("Hello!"));
1747}
1748
1749// QTBUG-21320
1750void tst_QQuickPathView::currentOffsetOnInsertion()
1751{
1752 QScopedPointer<QQuickView> window(createView());
1753 window->show();
1754
1755 QaimModel model;
1756
1757 QQmlContext *ctxt = window->rootContext();
1758 ctxt->setContextProperty("testModel", &model);
1759
1760 window->setSource(testFileUrl(fileName: "pathline.qml"));
1761 qApp->processEvents();
1762
1763 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "view");
1764 QVERIFY(pathview != nullptr);
1765
1766 pathview->setPreferredHighlightBegin(0.5);
1767 pathview->setPreferredHighlightEnd(0.5);
1768
1769 QCOMPARE(pathview->count(), model.count());
1770
1771 model.addItem(name: "item0", number: "0");
1772
1773 QCOMPARE(pathview->count(), model.count());
1774
1775 QQuickRectangle *item = nullptr;
1776 QTRY_VERIFY(item = findItem<QQuickRectangle>(pathview, "wrapper", 0));
1777
1778 QQuickPath *path = qobject_cast<QQuickPath*>(object: pathview->path());
1779 QVERIFY(path);
1780
1781 QPointF start = path->pointAtPercent(t: 0.5);
1782 QPointF offset;//Center of item is at point, but pos is from corner
1783 offset.setX(item->width()/2);
1784 offset.setY(item->height()/2);
1785 QCOMPARE(item->position() + offset, start);
1786
1787 QSignalSpy currentIndexSpy(pathview, SIGNAL(currentIndexChanged()));
1788
1789 // insert an item at the beginning
1790 model.insertItem(index: 0, name: "item1", number: "1");
1791 qApp->processEvents();
1792
1793 QCOMPARE(currentIndexSpy.count(), 1);
1794
1795 // currentIndex is now 1
1796 item = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 1);
1797 QVERIFY(item);
1798
1799 // verify that current item (item 1) is still at offset 0.5
1800 QCOMPARE(item->position() + offset, start);
1801
1802 // insert another item at the beginning
1803 model.insertItem(index: 0, name: "item2", number: "2");
1804 qApp->processEvents();
1805
1806 QCOMPARE(currentIndexSpy.count(), 2);
1807
1808 // currentIndex is now 2
1809 item = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 2);
1810 QVERIFY(item);
1811
1812 // verify that current item (item 2) is still at offset 0.5
1813 QCOMPARE(item->position() + offset, start);
1814
1815 // verify that remove before current maintains current item
1816 model.removeItem(index: 0);
1817 qApp->processEvents();
1818
1819 QCOMPARE(currentIndexSpy.count(), 3);
1820
1821 // currentIndex is now 1
1822 item = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 1);
1823 QVERIFY(item);
1824
1825 // verify that current item (item 1) is still at offset 0.5
1826 QCOMPARE(item->position() + offset, start);
1827
1828}
1829
1830void tst_QQuickPathView::asynchronous()
1831{
1832 QScopedPointer<QQuickView> window(createView());
1833 window->show();
1834 QQmlIncubationController controller;
1835 window->engine()->setIncubationController(&controller);
1836
1837 window->setSource(testFileUrl(fileName: "asyncloader.qml"));
1838
1839 QQuickItem *rootObject = qobject_cast<QQuickItem*>(object: window->rootObject());
1840 QVERIFY(rootObject);
1841
1842 QQuickPathView *pathview = nullptr;
1843 while (!pathview) {
1844 bool b = false;
1845 controller.incubateWhile(flag: &b);
1846 pathview = rootObject->findChild<QQuickPathView*>(aName: "view");
1847 }
1848
1849 // items will be created one at a time
1850 for (int i = 0; i < 5; ++i) {
1851 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", i) == nullptr);
1852 QQuickItem *item = nullptr;
1853 while (!item) {
1854 bool b = false;
1855 controller.incubateWhile(flag: &b);
1856 item = findItem<QQuickItem>(parent: pathview, objectName: "wrapper", index: i);
1857 }
1858 }
1859
1860 {
1861 bool b = true;
1862 controller.incubateWhile(flag: &b);
1863 }
1864
1865 // verify positioning
1866 QQuickRectangle *firstItem = findItem<QQuickRectangle>(parent: pathview, objectName: "wrapper", index: 0);
1867 QVERIFY(firstItem);
1868 QQuickPath *path = qobject_cast<QQuickPath*>(object: pathview->path());
1869 QVERIFY(path);
1870 QPointF start = path->pointAtPercent(t: 0.0);
1871 QPointF offset;//Center of item is at point, but pos is from corner
1872 offset.setX(firstItem->width()/2);
1873 offset.setY(firstItem->height()/2);
1874 QTRY_COMPARE(firstItem->position() + offset, start);
1875 pathview->setOffset(1.0);
1876
1877 for (int i=0; i<5; i++) {
1878 QQuickItem *curItem = findItem<QQuickItem>(parent: pathview, objectName: "wrapper", index: i);
1879 QPointF itemPos(path->pointAtPercent(t: 0.2 + i*0.2));
1880 QCOMPARE(curItem->position() + offset, itemPos);
1881 }
1882
1883}
1884
1885void tst_QQuickPathView::missingPercent()
1886{
1887 QQmlEngine engine;
1888 QQmlComponent c(&engine, testFileUrl(fileName: "missingPercent.qml"));
1889 QQuickPath *obj = qobject_cast<QQuickPath*>(object: c.create());
1890 QVERIFY(obj);
1891 QCOMPARE(obj->attributeAt("_qfx_percent", 1.0), qreal(1.0));
1892 delete obj;
1893}
1894
1895static inline bool hasFraction(qreal o)
1896{
1897 const bool result = o != qFloor(v: o);
1898 if (!result)
1899 qDebug() << "o != qFloor(o)" << o;
1900 return result;
1901}
1902
1903void tst_QQuickPathView::cancelDrag()
1904{
1905 QScopedPointer<QQuickView> window(createView());
1906 window->setSource(testFileUrl(fileName: "dragpath.qml"));
1907 QQuickViewTestUtil::moveMouseAway(window: window.data());
1908 window->show();
1909 window->requestActivate();
1910 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1911 QCOMPARE(window.data(), qGuiApp->focusWindow());
1912
1913 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1914 QVERIFY(pathview != nullptr);
1915
1916 QSignalSpy draggingSpy(pathview, SIGNAL(draggingChanged()));
1917 QSignalSpy dragStartedSpy(pathview, SIGNAL(dragStarted()));
1918 QSignalSpy dragEndedSpy(pathview, SIGNAL(dragEnded()));
1919
1920 // drag between snap points
1921 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(10,100));
1922 QTest::qWait(ms: 100);
1923 QTest::mouseMove(window: window.data(), pos: QPoint(80, 100));
1924 QTest::mouseMove(window: window.data(), pos: QPoint(130, 100));
1925
1926 QTRY_VERIFY(hasFraction(pathview->offset()));
1927 QTRY_VERIFY(pathview->isMoving());
1928 QVERIFY(pathview->isDragging());
1929 QCOMPARE(draggingSpy.count(), 1);
1930 QCOMPARE(dragStartedSpy.count(), 1);
1931 QCOMPARE(dragEndedSpy.count(), 0);
1932
1933 // steal mouse grab - cancels PathView dragging
1934 QQuickItem *item = window->rootObject()->findChild<QQuickItem*>(aName: "text");
1935 item->grabMouse();
1936
1937 // returns to a snap point.
1938 QTRY_COMPARE(pathview->offset(), qreal(qFloor(pathview->offset())));
1939 QTRY_VERIFY(!pathview->isMoving());
1940 QVERIFY(!pathview->isDragging());
1941 QCOMPARE(draggingSpy.count(), 2);
1942 QCOMPARE(dragStartedSpy.count(), 1);
1943 QCOMPARE(dragEndedSpy.count(), 1);
1944
1945 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(40,100));
1946
1947}
1948
1949void tst_QQuickPathView::maximumFlickVelocity()
1950{
1951 QScopedPointer<QQuickView> window(createView());
1952 window->setSource(testFileUrl(fileName: "dragpath.qml"));
1953 QQuickViewTestUtil::moveMouseAway(window: window.data());
1954 window->show();
1955 window->requestActivate();
1956 QVERIFY(QTest::qWaitForWindowActive(window.data()));
1957 QCOMPARE(window.data(), qGuiApp->focusWindow());
1958
1959 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
1960 QVERIFY(pathview != nullptr);
1961
1962 pathview->setMaximumFlickVelocity(700);
1963 flick(window: window.data(), from: QPoint(200,10), to: QPoint(10,10), duration: 180);
1964 QVERIFY(pathview->isMoving());
1965 QVERIFY(pathview->isFlicking());
1966 QTRY_VERIFY_WITH_TIMEOUT(!pathview->isMoving(), 50000);
1967
1968 double dist1 = 100 - pathview->offset();
1969
1970 pathview->setOffset(0.);
1971 pathview->setMaximumFlickVelocity(300);
1972 flick(window: window.data(), from: QPoint(200,10), to: QPoint(10,10), duration: 180);
1973 QVERIFY(pathview->isMoving());
1974 QVERIFY(pathview->isFlicking());
1975 QTRY_VERIFY_WITH_TIMEOUT(!pathview->isMoving(), 50000);
1976
1977 double dist2 = 100 - pathview->offset();
1978
1979 pathview->setOffset(0.);
1980 pathview->setMaximumFlickVelocity(500);
1981 flick(window: window.data(), from: QPoint(200,10), to: QPoint(10,10), duration: 180);
1982 QVERIFY(pathview->isMoving());
1983 QVERIFY(pathview->isFlicking());
1984 QTRY_VERIFY_WITH_TIMEOUT(!pathview->isMoving(), 50000);
1985
1986 double dist3 = 100 - pathview->offset();
1987
1988 QVERIFY(dist1 > dist2);
1989 QVERIFY(dist3 > dist2);
1990 QVERIFY(dist2 < dist1);
1991
1992}
1993
1994void tst_QQuickPathView::snapToItem()
1995{
1996 QFETCH(bool, enforceRange);
1997
1998 QScopedPointer<QQuickView> window(createView());
1999 QQuickViewTestUtil::moveMouseAway(window: window.data());
2000 window->setSource(testFileUrl(fileName: "panels.qml"));
2001 window->show();
2002 window->requestActivate();
2003 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2004 QCOMPARE(window.data(), qGuiApp->focusWindow());
2005
2006 QQuickPathView *pathview = window->rootObject()->findChild<QQuickPathView*>(aName: "view");
2007 QVERIFY(pathview != nullptr);
2008
2009 window->rootObject()->setProperty(name: "enforceRange", value: enforceRange);
2010 QTRY_VERIFY(!pathview->isMoving()); // ensure stable
2011
2012 int currentIndex = pathview->currentIndex();
2013
2014 QSignalSpy snapModeSpy(pathview, SIGNAL(snapModeChanged()));
2015
2016 flick(window: window.data(), from: QPoint(200,10), to: QPoint(10,10), duration: 180);
2017
2018 QVERIFY(pathview->isMoving());
2019 QTRY_VERIFY_WITH_TIMEOUT(!pathview->isMoving(), 50000);
2020
2021 QCOMPARE(pathview->offset(), qreal(qFloor(pathview->offset())));
2022
2023 if (enforceRange)
2024 QVERIFY(pathview->currentIndex() != currentIndex);
2025 else
2026 QCOMPARE(pathview->currentIndex(), currentIndex);
2027
2028}
2029
2030void tst_QQuickPathView::snapToItem_data()
2031{
2032 QTest::addColumn<bool>(name: "enforceRange");
2033
2034 QTest::newRow(dataTag: "no enforce range") << false;
2035 QTest::newRow(dataTag: "enforce range") << true;
2036}
2037
2038void tst_QQuickPathView::snapOneItem()
2039{
2040 QFETCH(bool, enforceRange);
2041
2042 QScopedPointer<QQuickView> window(createView());
2043 QQuickViewTestUtil::moveMouseAway(window: window.data());
2044 window->setSource(testFileUrl(fileName: "panels.qml"));
2045 window->show();
2046 window->requestActivate();
2047 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2048 QCOMPARE(window.data(), qGuiApp->focusWindow());
2049
2050 QQuickPathView *pathview = window->rootObject()->findChild<QQuickPathView*>(aName: "view");
2051 QVERIFY(pathview != nullptr);
2052
2053 window->rootObject()->setProperty(name: "enforceRange", value: enforceRange);
2054
2055 QSignalSpy snapModeSpy(pathview, SIGNAL(snapModeChanged()));
2056
2057 window->rootObject()->setProperty(name: "snapOne", value: true);
2058 QCOMPARE(snapModeSpy.count(), 1);
2059 QTRY_VERIFY(!pathview->isMoving()); // ensure stable
2060
2061 int currentIndex = pathview->currentIndex();
2062
2063 double startOffset = pathview->offset();
2064 flick(window: window.data(), from: QPoint(200,10), to: QPoint(10,10), duration: 180);
2065
2066 QVERIFY(pathview->isMoving());
2067 QTRY_VERIFY(!pathview->isMoving());
2068
2069 // must have moved only one item
2070 QCOMPARE(pathview->offset(), fmodf(3.0 + startOffset - 1.0, 3.0));
2071
2072 if (enforceRange)
2073 QCOMPARE(pathview->currentIndex(), currentIndex + 1);
2074 else
2075 QCOMPARE(pathview->currentIndex(), currentIndex);
2076
2077}
2078
2079void tst_QQuickPathView::snapOneItem_data()
2080{
2081 QTest::addColumn<bool>(name: "enforceRange");
2082
2083 QTest::newRow(dataTag: "no enforce range") << false;
2084 QTest::newRow(dataTag: "enforce range") << true;
2085}
2086
2087void tst_QQuickPathView::positionViewAtIndex()
2088{
2089 QFETCH(bool, enforceRange);
2090 QFETCH(int, pathItemCount);
2091 QFETCH(qreal, initOffset);
2092 QFETCH(int, index);
2093 QFETCH(QQuickPathView::PositionMode, mode);
2094 QFETCH(qreal, offset);
2095
2096 QScopedPointer<QQuickView> window(createView());
2097 window->setSource(testFileUrl(fileName: "pathview3.qml"));
2098 window->show();
2099 window->requestActivate();
2100 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2101 QCOMPARE(window.data(), qGuiApp->focusWindow());
2102
2103 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
2104 QVERIFY(pathview != nullptr);
2105
2106 window->rootObject()->setProperty(name: "enforceRange", value: enforceRange);
2107 if (pathItemCount == -1)
2108 pathview->resetPathItemCount();
2109 else
2110 pathview->setPathItemCount(pathItemCount);
2111 pathview->setOffset(initOffset);
2112
2113 pathview->positionViewAtIndex(index, mode);
2114
2115 QCOMPARE(pathview->offset(), offset);
2116
2117}
2118
2119void tst_QQuickPathView::positionViewAtIndex_data()
2120{
2121 QTest::addColumn<bool>(name: "enforceRange");
2122 QTest::addColumn<int>(name: "pathItemCount");
2123 QTest::addColumn<qreal>(name: "initOffset");
2124 QTest::addColumn<int>(name: "index");
2125 QTest::addColumn<QQuickPathView::PositionMode>(name: "mode");
2126 QTest::addColumn<qreal>(name: "offset");
2127
2128 QTest::newRow(dataTag: "no range, all items, Beginning") << false << -1 << 0.0 << 3 << QQuickPathView::Beginning << 5.0;
2129 QTest::newRow(dataTag: "no range, all items, Center") << false << -1 << 0.0 << 3 << QQuickPathView::Center << 1.0;
2130 QTest::newRow(dataTag: "no range, all items, End") << false << -1 << 0.0 << 3 << QQuickPathView::End << 5.0;
2131 QTest::newRow(dataTag: "no range, 5 items, Beginning") << false << 5 << 0.0 << 3 << QQuickPathView::Beginning << 5.0;
2132 QTest::newRow(dataTag: "no range, 5 items, Center") << false << 5 << 0.0 << 3 << QQuickPathView::Center << 7.5;
2133 QTest::newRow(dataTag: "no range, 5 items, End") << false << 5 << 0.0 << 3 << QQuickPathView::End << 2.0;
2134 QTest::newRow(dataTag: "no range, 5 items, Contain") << false << 5 << 0.0 << 3 << QQuickPathView::Contain << 0.0;
2135 QTest::newRow(dataTag: "no range, 5 items, init offset 4.0, Contain") << false << 5 << 4.0 << 3 << QQuickPathView::Contain << 5.0;
2136 QTest::newRow(dataTag: "no range, 5 items, init offset 3.0, Contain") << false << 5 << 3.0 << 3 << QQuickPathView::Contain << 2.0;
2137
2138 QTest::newRow(dataTag: "strict range, all items, Beginning") << true << -1 << 0.0 << 3 << QQuickPathView::Beginning << 1.0;
2139 QTest::newRow(dataTag: "strict range, all items, Center") << true << -1 << 0.0 << 3 << QQuickPathView::Center << 5.0;
2140 QTest::newRow(dataTag: "strict range, all items, End") << true << -1 << 0.0 << 3 << QQuickPathView::End << 0.0;
2141 QTest::newRow(dataTag: "strict range, all items, Contain") << true << -1 << 0.0 << 3 << QQuickPathView::Contain << 0.0;
2142 QTest::newRow(dataTag: "strict range, all items, init offset 1.0, Contain") << true << -1 << 1.0 << 3 << QQuickPathView::Contain << 1.0;
2143 QTest::newRow(dataTag: "strict range, all items, SnapPosition") << true << -1 << 0.0 << 3 << QQuickPathView::SnapPosition << 5.0;
2144 QTest::newRow(dataTag: "strict range, 5 items, Beginning") << true << 5 << 0.0 << 3 << QQuickPathView::Beginning << 3.0;
2145 QTest::newRow(dataTag: "strict range, 5 items, Center") << true << 5 << 0.0 << 3 << QQuickPathView::Center << 5.0;
2146 QTest::newRow(dataTag: "strict range, 5 items, End") << true << 5 << 0.0 << 3 << QQuickPathView::End << 7.0;
2147 QTest::newRow(dataTag: "strict range, 5 items, Contain") << true << 5 << 0.0 << 3 << QQuickPathView::Contain << 7.0;
2148 QTest::newRow(dataTag: "strict range, 5 items, init offset 1.0, Contain") << true << 5 << 1.0 << 3 << QQuickPathView::Contain << 7.0;
2149 QTest::newRow(dataTag: "strict range, 5 items, init offset 2.0, Contain") << true << 5 << 2.0 << 3 << QQuickPathView::Contain << 3.0;
2150 QTest::newRow(dataTag: "strict range, 5 items, SnapPosition") << true << 5 << 0.0 << 3 << QQuickPathView::SnapPosition << 5.0;
2151}
2152
2153void tst_QQuickPathView::indexAt_itemAt()
2154{
2155 QFETCH(qreal, x);
2156 QFETCH(qreal, y);
2157 QFETCH(int, index);
2158
2159 QScopedPointer<QQuickView> window(createView());
2160 window->setSource(testFileUrl(fileName: "pathview3.qml"));
2161 window->show();
2162 window->requestActivate();
2163 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2164 QCOMPARE(window.data(), qGuiApp->focusWindow());
2165
2166 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
2167 QVERIFY(pathview != nullptr);
2168
2169 QQuickItem *item = nullptr;
2170 if (index >= 0) {
2171 item = findItem<QQuickItem>(parent: pathview, objectName: "wrapper", index);
2172 QVERIFY(item);
2173 }
2174 QCOMPARE(pathview->indexAt(x,y), index);
2175 QCOMPARE(pathview->itemAt(x,y), item);
2176
2177}
2178
2179void tst_QQuickPathView::indexAt_itemAt_data()
2180{
2181 QTest::addColumn<qreal>(name: "x");
2182 QTest::addColumn<qreal>(name: "y");
2183 QTest::addColumn<int>(name: "index");
2184
2185 QTest::newRow(dataTag: "Item 0 - 585, 95") << qreal(585.) << qreal(95.) << 0;
2186 QTest::newRow(dataTag: "Item 0 - 660, 165") << qreal(660.) << qreal(165.) << 0;
2187 QTest::newRow(dataTag: "No Item a - 580, 95") << qreal(580.) << qreal(95.) << -1;
2188 QTest::newRow(dataTag: "No Item b - 585, 85") << qreal(585.) << qreal(85.) << -1;
2189 QTest::newRow(dataTag: "Item 7 - 360, 200") << qreal(360.) << qreal(200.) << 7;
2190}
2191
2192void tst_QQuickPathView::cacheItemCount()
2193{
2194 QScopedPointer<QQuickView> window(createView());
2195
2196 window->setSource(testFileUrl(fileName: "pathview3.qml"));
2197 window->show();
2198 qApp->processEvents();
2199
2200 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
2201 QVERIFY(pathview != nullptr);
2202
2203 QMetaObject::invokeMethod(obj: pathview, member: "addColor", Q_ARG(QVariant, QString("orange")));
2204 QMetaObject::invokeMethod(obj: pathview, member: "addColor", Q_ARG(QVariant, QString("lightsteelblue")));
2205 QMetaObject::invokeMethod(obj: pathview, member: "addColor", Q_ARG(QVariant, QString("teal")));
2206 QMetaObject::invokeMethod(obj: pathview, member: "addColor", Q_ARG(QVariant, QString("aqua")));
2207
2208 pathview->setOffset(0);
2209
2210 pathview->setCacheItemCount(3);
2211 QCOMPARE(pathview->cacheItemCount(), 3);
2212
2213 QQmlIncubationController controller;
2214 window->engine()->setIncubationController(&controller);
2215
2216 // Items on the path are created immediately
2217 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 0));
2218 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 1));
2219 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 11));
2220 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 10));
2221
2222 const int cached[] = { 2, 3, 9, -1 }; // two appended, one prepended
2223
2224 int i = 0;
2225 while (cached[i] >= 0) {
2226 // items will be created one at a time
2227 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", cached[i]) == nullptr);
2228 QQuickItem *item = nullptr;
2229 while (!item) {
2230 bool b = false;
2231 controller.incubateWhile(flag: &b);
2232 item = findItem<QQuickItem>(parent: pathview, objectName: "wrapper", index: cached[i]);
2233 }
2234 ++i;
2235 }
2236
2237 {
2238 bool b = true;
2239 controller.incubateWhile(flag: &b);
2240 }
2241
2242 // move view and confirm items in view are visible immediately and outside are created async
2243 pathview->setOffset(4);
2244
2245 // Items on the path are created immediately
2246 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 6));
2247 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 7));
2248 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 8));
2249 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 9));
2250 // already created items within cache stay created
2251 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 10));
2252 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 11));
2253
2254 // one item prepended async.
2255 QVERIFY(findItem<QQuickItem>(pathview, "wrapper", 5) == nullptr);
2256 QQuickItem *item = nullptr;
2257 while (!item) {
2258 bool b = false;
2259 controller.incubateWhile(flag: &b);
2260 item = findItem<QQuickItem>(parent: pathview, objectName: "wrapper", index: 5);
2261 }
2262
2263 {
2264 bool b = true;
2265 controller.incubateWhile(flag: &b);
2266 }
2267}
2268
2269static void testCurrentIndexChange(QQuickPathView *pathView, const QStringList &objectNamesInOrder)
2270{
2271 for (int visualIndex = 0; visualIndex < objectNamesInOrder.size() - 1; ++visualIndex) {
2272 QQuickRectangle *delegate = findItem<QQuickRectangle>(parent: pathView, objectName: objectNamesInOrder.at(i: visualIndex));
2273 QVERIFY(delegate);
2274
2275 QQuickRectangle *nextDelegate = findItem<QQuickRectangle>(parent: pathView, objectName: objectNamesInOrder.at(i: visualIndex + 1));
2276 QVERIFY(nextDelegate);
2277
2278 QVERIFY(delegate->y() < nextDelegate->y());
2279 }
2280}
2281
2282void tst_QQuickPathView::changePathDuringRefill()
2283{
2284 QScopedPointer<QQuickView> window(createView());
2285
2286 window->setSource(testFileUrl(fileName: "changePathDuringRefill.qml"));
2287 window->show();
2288 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2289 QCOMPARE(window.data(), qGuiApp->focusWindow());
2290
2291 QQuickPathView *pathView = qobject_cast<QQuickPathView*>(object: window->rootObject());
2292 QVERIFY(pathView != nullptr);
2293
2294 testCurrentIndexChange(pathView, objectNamesInOrder: QStringList() << "delegateC" << "delegateA" << "delegateB");
2295
2296 pathView->incrementCurrentIndex();
2297 /*
2298 Decrementing moves delegateA down, resulting in an offset of 1,
2299 so incrementing will move it up, resulting in an offset of 2:
2300
2301 delegateC delegateA
2302 delegateA => delegateB
2303 delegateB delegateC
2304 */
2305 QTRY_COMPARE(pathView->offset(), 2.0);
2306 testCurrentIndexChange(pathView, objectNamesInOrder: QStringList() << "delegateA" << "delegateB" << "delegateC");
2307}
2308
2309void tst_QQuickPathView::nestedinFlickable()
2310{
2311 QScopedPointer<QQuickView> window(createView());
2312 QQuickViewTestUtil::moveMouseAway(window: window.data());
2313 window->setSource(testFileUrl(fileName: "nestedInFlickable.qml"));
2314 window->show();
2315 window->requestActivate();
2316 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2317 QCOMPARE(window.data(), qGuiApp->focusWindow());
2318
2319 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "pathView");
2320 QVERIFY(pathview != nullptr);
2321
2322 QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(object: window->rootObject());
2323 QVERIFY(flickable != nullptr);
2324
2325 QSignalSpy movingSpy(pathview, SIGNAL(movingChanged()));
2326 QSignalSpy moveStartedSpy(pathview, SIGNAL(movementStarted()));
2327 QSignalSpy moveEndedSpy(pathview, SIGNAL(movementEnded()));
2328
2329 QSignalSpy fflickingSpy(flickable, SIGNAL(flickingChanged()));
2330 QSignalSpy fflickStartedSpy(flickable, SIGNAL(flickStarted()));
2331 QSignalSpy fflickEndedSpy(flickable, SIGNAL(flickEnded()));
2332
2333 int waitInterval = 5;
2334
2335 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(23,218));
2336
2337 QTest::mouseMove(window: window.data(), pos: QPoint(25,218), delay: waitInterval);
2338 QTest::mouseMove(window: window.data(), pos: QPoint(26,218), delay: waitInterval);
2339 QTest::mouseMove(window: window.data(), pos: QPoint(28,219), delay: waitInterval);
2340 QTest::mouseMove(window: window.data(), pos: QPoint(31,219), delay: waitInterval);
2341 QTest::mouseMove(window: window.data(), pos: QPoint(53,219), delay: waitInterval);
2342
2343 // first move beyond threshold does not trigger drag
2344 QVERIFY(!pathview->isMoving());
2345 QVERIFY(!pathview->isDragging());
2346 QCOMPARE(movingSpy.count(), 0);
2347 QCOMPARE(moveStartedSpy.count(), 0);
2348 QCOMPARE(moveEndedSpy.count(), 0);
2349 QCOMPARE(fflickingSpy.count(), 0);
2350 QCOMPARE(fflickStartedSpy.count(), 0);
2351 QCOMPARE(fflickEndedSpy.count(), 0);
2352
2353 // no further moves after the initial move beyond threshold
2354 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(73,219));
2355 QTRY_COMPARE(movingSpy.count(), 2);
2356 QTRY_COMPARE(moveEndedSpy.count(), 1);
2357 QCOMPARE(moveStartedSpy.count(), 1);
2358 // Flickable should not handle this
2359 QCOMPARE(fflickingSpy.count(), 0);
2360 QCOMPARE(fflickStartedSpy.count(), 0);
2361 QCOMPARE(fflickEndedSpy.count(), 0);
2362
2363 // now test that two quick flicks are both handled by the pathview
2364 movingSpy.clear();
2365 moveStartedSpy.clear();
2366 moveEndedSpy.clear();
2367 fflickingSpy.clear();
2368 fflickStartedSpy.clear();
2369 fflickEndedSpy.clear();
2370 int shortInterval = 2;
2371 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(23,216));
2372 QTest::mouseMove(window: window.data(), pos: QPoint(48,216), delay: shortInterval);
2373 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(73,217));
2374 QVERIFY(pathview->isMoving());
2375 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(21,216));
2376 QTest::mouseMove(window: window.data(), pos: QPoint(46,216), delay: shortInterval);
2377 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(71,217));
2378 QVERIFY(pathview->isMoving());
2379 // moveEndedSpy.count() and moveStartedSpy.count() should be exactly 1
2380 // but in CI we sometimes see a scheduling issue being hit which
2381 // causes the main thread to be stalled while the animation thread
2382 // continues, allowing the animation timer to finish after the first
2383 // call to QVERIFY(pathview->isMoving()) in the code above, prior to
2384 // the detected beginning of the second flick, which can cause both of
2385 // those signal counts to be 2 rather than 1.
2386 // Note that this is not a true problem (this scheduling quirk just
2387 // means that the unit test is not testing the enforced behavior
2388 // as strictly as it would otherwise); it is only a bug if it results
2389 // in the Flickable handling one or more of the flicks, and that
2390 // is unconditionally tested below.
2391 // To avoid false positive test failure in the scheduling quirk case
2392 // we allow the multiple signal count case, rather than simply:
2393 // QTRY_COMPARE(moveEndedSpy.count(), 1);
2394 // QCOMPARE(moveStartedSpy.count(), 1);
2395 QTRY_VERIFY(moveEndedSpy.count() > 0);
2396 qCDebug(lcTests) << "After receiving moveEnded signal:"
2397 << "moveEndedSpy.count():" << moveEndedSpy.count()
2398 << "moveStartedSpy.count():" << moveStartedSpy.count()
2399 << "fflickingSpy.count():" << fflickingSpy.count()
2400 << "fflickStartedSpy.count():" << fflickStartedSpy.count()
2401 << "fflickEndedSpy.count():" << fflickEndedSpy.count();
2402 QTRY_COMPARE(moveStartedSpy.count(), moveEndedSpy.count());
2403 qCDebug(lcTests) << "After receiving matched moveEnded signal(s):"
2404 << "moveEndedSpy.count():" << moveEndedSpy.count()
2405 << "moveStartedSpy.count():" << moveStartedSpy.count()
2406 << "fflickingSpy.count():" << fflickingSpy.count()
2407 << "fflickStartedSpy.count():" << fflickStartedSpy.count()
2408 << "fflickEndedSpy.count():" << fflickEndedSpy.count();
2409 QVERIFY(moveStartedSpy.count() <= 2);
2410 // Flickable should not handle this
2411 QCOMPARE(fflickingSpy.count(), 0);
2412 QCOMPARE(fflickStartedSpy.count(), 0);
2413 QCOMPARE(fflickEndedSpy.count(), 0);
2414
2415}
2416
2417void tst_QQuickPathView::ungrabNestedinFlickable()
2418{
2419 QScopedPointer<QQuickView> window(createView());
2420 QQuickViewTestUtil::moveMouseAway(window: window.data());
2421 window->setSource(testFileUrl(fileName: "ungrabNestedinFlickable.qml"));
2422 window->show();
2423 window->requestActivate();
2424 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2425 QCOMPARE(window.data(), qGuiApp->focusWindow());
2426
2427 QQuickPathView *pathview = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "pathView");
2428 QVERIFY(pathview != nullptr);
2429
2430 double pathviewOffsetBefore = pathview->offset();
2431
2432 // Drag slowly upwards so that it does not flick, release, and let it start snapping back
2433 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(200, 350));
2434 for (int i = 0; i < 4; ++i)
2435 QTest::mouseMove(window: window.data(), pos: QPoint(200, 325 - i * 25), delay: 500);
2436 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(200, 250));
2437 QCOMPARE(pathview->isMoving(), true);
2438
2439 // Press again to stop moving
2440 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(200, 350));
2441 QTRY_COMPARE(pathview->isMoving(), false);
2442
2443 // Cancel the grab, wait for movement to stop, and expect it to snap to
2444 // the nearest delegate, which should be at the same offset as where we started
2445 pathview->ungrabMouse();
2446 QTRY_COMPARE(pathview->offset(), pathviewOffsetBefore);
2447 QCOMPARE(pathview->isMoving(), false);
2448 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: 0, pos: QPoint(200, 350));
2449}
2450
2451void tst_QQuickPathView::flickableDelegate()
2452{
2453 QScopedPointer<QQuickView> window(createView());
2454 QQuickViewTestUtil::moveMouseAway(window: window.data());
2455 window->setSource(testFileUrl(fileName: "flickableDelegate.qml"));
2456 window->show();
2457 window->requestActivate();
2458 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2459 QCOMPARE(window.data(), qGuiApp->focusWindow());
2460
2461 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
2462 QVERIFY(pathview != nullptr);
2463
2464 QQuickFlickable *flickable = qobject_cast<QQuickFlickable*>(object: pathview->currentItem());
2465 QVERIFY(flickable != nullptr);
2466
2467 QSignalSpy movingSpy(pathview, SIGNAL(movingChanged()));
2468 QSignalSpy moveStartedSpy(pathview, SIGNAL(movementStarted()));
2469 QSignalSpy moveEndedSpy(pathview, SIGNAL(movementEnded()));
2470
2471 QSignalSpy fflickingSpy(flickable, SIGNAL(flickingChanged()));
2472 QSignalSpy fflickStartedSpy(flickable, SIGNAL(flickStarted()));
2473 QSignalSpy fflickEndedSpy(flickable, SIGNAL(flickEnded()));
2474
2475 int waitInterval = 5;
2476
2477 QTest::mousePress(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(23,100));
2478
2479 QTest::mouseMove(window: window.data(), pos: QPoint(25,100), delay: waitInterval);
2480 QTest::mouseMove(window: window.data(), pos: QPoint(26,100), delay: waitInterval);
2481 QTest::mouseMove(window: window.data(), pos: QPoint(28,100), delay: waitInterval);
2482 QTest::mouseMove(window: window.data(), pos: QPoint(31,100), delay: waitInterval);
2483 QTest::mouseMove(window: window.data(), pos: QPoint(39,100), delay: waitInterval);
2484
2485 // first move beyond threshold does not trigger drag
2486 QVERIFY(!flickable->isMoving());
2487 QVERIFY(!flickable->isDragging());
2488 QCOMPARE(movingSpy.count(), 0);
2489 QCOMPARE(moveStartedSpy.count(), 0);
2490 QCOMPARE(moveEndedSpy.count(), 0);
2491 QCOMPARE(fflickingSpy.count(), 0);
2492 QCOMPARE(fflickStartedSpy.count(), 0);
2493 QCOMPARE(fflickEndedSpy.count(), 0);
2494
2495 // no further moves after the initial move beyond threshold
2496 QTest::mouseRelease(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(53,100));
2497 QTRY_COMPARE(fflickingSpy.count(), 2);
2498 QTRY_COMPARE(fflickStartedSpy.count(), 1);
2499 QCOMPARE(fflickEndedSpy.count(), 1);
2500 // PathView should not handle this
2501 QTRY_COMPARE(movingSpy.count(), 0);
2502 QTRY_COMPARE(moveEndedSpy.count(), 0);
2503 QCOMPARE(moveStartedSpy.count(), 0);
2504}
2505
2506void tst_QQuickPathView::jsArrayChange()
2507{
2508 QQmlEngine engine;
2509 QQmlComponent component(&engine);
2510 component.setData("import QtQuick 2.4; PathView {}", baseUrl: QUrl());
2511
2512 QScopedPointer<QQuickPathView> view(qobject_cast<QQuickPathView *>(object: component.create()));
2513 QVERIFY(!view.isNull());
2514
2515 QSignalSpy spy(view.data(), SIGNAL(modelChanged()));
2516 QVERIFY(spy.isValid());
2517
2518 QJSValue array1 = engine.newArray(length: 3);
2519 QJSValue array2 = engine.newArray(length: 3);
2520 for (int i = 0; i < 3; ++i) {
2521 array1.setProperty(arrayIndex: i, value: i);
2522 array2.setProperty(arrayIndex: i, value: i);
2523 }
2524
2525 view->setModel(QVariant::fromValue(value: array1));
2526 QCOMPARE(spy.count(), 1);
2527
2528 // no change
2529 view->setModel(QVariant::fromValue(value: array2));
2530 QCOMPARE(spy.count(), 1);
2531}
2532
2533void tst_QQuickPathView::qtbug37815()
2534{
2535 QScopedPointer<QQuickView> window(createView());
2536
2537 window->setSource(testFileUrl(fileName: "qtbug37815.qml"));
2538 window->show();
2539 window->requestActivate();
2540 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2541
2542 // cache items will be created async. Let's wait...
2543 QTest::qWait(ms: 1000);
2544
2545 QQuickPathView *pathView = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "pathView");
2546 QVERIFY(pathView != nullptr);
2547
2548 const int pathItemCount = pathView->pathItemCount();
2549 const int cacheItemCount = pathView->cacheItemCount();
2550 int totalCount = 0;
2551 foreach (QQuickItem *item, pathView->childItems()) {
2552 if (item->objectName().startsWith(s: QLatin1String("delegate")))
2553 ++totalCount;
2554 }
2555 QCOMPARE(pathItemCount + cacheItemCount, totalCount);
2556}
2557
2558/* This bug was one where if you jump the list such that the sole missing item needed to be
2559 * added in the middle of the list, it would instead move an item somewhere else in the list
2560 * to the middle (messing it up very badly).
2561 *
2562 * The test checks correct visual order both before and after the jump.
2563 */
2564void tst_QQuickPathView::qtbug42716()
2565{
2566 QScopedPointer<QQuickView> window(createView());
2567
2568 window->setSource(testFileUrl(fileName: "qtbug42716.qml"));
2569 window->show();
2570 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2571 QCOMPARE(window.data(), qGuiApp->focusWindow());
2572
2573 QQuickPathView *pathView = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "pathView");
2574 QVERIFY(pathView != nullptr);
2575
2576 int order1[] = {5,6,7,0,1,2,3};
2577 int missing1 = 4;
2578 int order2[] = {0,1,2,3,4,5,6};
2579 int missing2 = 7;
2580
2581 qreal lastY = 0.0;
2582 for (int i = 0; i<7; i++) {
2583 QQuickItem *item = findItem<QQuickItem>(parent: pathView, objectName: QString("delegate%1").arg(a: order1[i]));
2584 QVERIFY(item);
2585 QVERIFY(item->y() > lastY);
2586 lastY = item->y();
2587 }
2588 QQuickItem *itemMiss = findItem<QQuickItem>(parent: pathView, objectName: QString("delegate%1").arg(a: missing1));
2589 QVERIFY(!itemMiss);
2590
2591 pathView->setOffset(0.0882353);
2592 //Note refill is delayed, needs time to take effect
2593 QTest::qWait(ms: 100);
2594
2595 lastY = 0.0;
2596 for (int i = 0; i<7; i++) {
2597 QQuickItem *item = findItem<QQuickItem>(parent: pathView, objectName: QString("delegate%1").arg(a: order2[i]));
2598 QVERIFY(item);
2599 QVERIFY(item->y() > lastY);
2600 lastY = item->y();
2601 }
2602 itemMiss = findItem<QQuickItem>(parent: pathView, objectName: QString("delegate%1").arg(a: missing2));
2603 QVERIFY(!itemMiss);
2604}
2605
2606void tst_QQuickPathView::qtbug53464()
2607{
2608 QScopedPointer<QQuickView> window(createView());
2609
2610 window->setSource(testFileUrl(fileName: "qtbug53464.qml"));
2611 window->show();
2612 window->requestActivate();
2613 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2614
2615 QQuickPathView *pathView = findItem<QQuickPathView>(parent: window->rootObject(), objectName: "pathView");
2616 QVERIFY(pathView != nullptr);
2617 const int currentIndex = pathView->currentIndex();
2618 QCOMPARE(currentIndex, 8);
2619
2620 const int pathItemCount = pathView->pathItemCount();
2621 int totalCount = 0;
2622 foreach (QQuickItem *item, pathView->childItems()) {
2623 if (item->objectName().startsWith(s: QLatin1String("delegate")))
2624 ++totalCount;
2625 }
2626 QCOMPARE(pathItemCount, totalCount);
2627}
2628
2629void tst_QQuickPathView::addCustomAttribute()
2630{
2631 const QScopedPointer<QQuickView> window(createView());
2632 window->setSource(testFileUrl(fileName: "customAttribute.qml"));
2633 window->show();
2634}
2635
2636void tst_QQuickPathView::movementDirection_data()
2637{
2638 QTest::addColumn<QQuickPathView::MovementDirection>(name: "movementdirection");
2639 QTest::addColumn<int>(name: "toidx");
2640 QTest::addColumn<qreal>(name: "fromoffset");
2641 QTest::addColumn<qreal>(name: "tooffset");
2642
2643 QTest::newRow(dataTag: "default-shortest") << QQuickPathView::Shortest << 3 << 8.0 << 5.0;
2644 QTest::newRow(dataTag: "negative") << QQuickPathView::Negative << 2 << 0.0 << 6.0;
2645 QTest::newRow(dataTag: "positive") << QQuickPathView::Positive << 3 << 8.0 << 5.0;
2646
2647}
2648
2649static void verify_offsets(QQuickPathView *pathview, int toidx, qreal fromoffset, qreal tooffset)
2650{
2651 pathview->setCurrentIndex(toidx);
2652 bool started = false;
2653 qreal first, second;
2654 QTest::qWait(ms: 100);
2655 first = pathview->offset();
2656 while (1) {
2657 if (first == 0)
2658 first = pathview->offset();
2659 QTest::qWait(ms: 10); // highlightMoveDuration: 1000
2660 second = pathview->offset();
2661 if (!started && first != 0 && second != first) { // animation started
2662 started = true;
2663 break;
2664 }
2665 }
2666
2667 if (tooffset > fromoffset) {
2668 QVERIFY(fromoffset <= first);
2669 QVERIFY(first <= second);
2670 QVERIFY(second <= tooffset);
2671 } else {
2672 QVERIFY(fromoffset >= first);
2673 QVERIFY(first >= second);
2674 QVERIFY(second >= tooffset);
2675 }
2676 QTRY_COMPARE(pathview->offset(), tooffset);
2677}
2678
2679void tst_QQuickPathView::movementDirection()
2680{
2681 QFETCH(QQuickPathView::MovementDirection, movementdirection);
2682 QFETCH(int, toidx);
2683 QFETCH(qreal, fromoffset);
2684 QFETCH(qreal, tooffset);
2685
2686 QScopedPointer<QQuickView> window(createView());
2687 QQuickViewTestUtil::moveMouseAway(window: window.data());
2688 window->setSource(testFileUrl(fileName: "movementDirection.qml"));
2689 window->show();
2690 window->requestActivate();
2691 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2692 QCOMPARE(window.data(), qGuiApp->focusWindow());
2693
2694 QQuickPathView *pathview = window->rootObject()->findChild<QQuickPathView*>(aName: "view");
2695 QVERIFY(pathview != nullptr);
2696 QVERIFY(pathview->offset() == 0.0);
2697 QVERIFY(pathview->currentIndex() == 0);
2698 pathview->setMovementDirection(movementdirection);
2699 QVERIFY(pathview->movementDirection() == movementdirection);
2700
2701 verify_offsets(pathview, toidx, fromoffset, tooffset);
2702}
2703
2704void tst_QQuickPathView::removePath()
2705{
2706 QScopedPointer<QQuickView> window(createView());
2707 window->setSource(testFileUrl(fileName: "removePath.qml"));
2708 window->show();
2709
2710 QQuickPathView *pathview = qobject_cast<QQuickPathView*>(object: window->rootObject());
2711 QVERIFY(pathview != nullptr);
2712
2713 QVERIFY(QMetaObject::invokeMethod(pathview, "removePath"));
2714 QVERIFY(QMetaObject::invokeMethod(pathview, "setPath"));
2715}
2716
2717/*
2718 Tests that moving items in an ObjectModel and then deleting the view
2719 doesn't cause heap-use-after-free when run through ASAN.
2720
2721 The test case is based on a Qt Quick Controls 2 test where the issue was
2722 discovered.
2723*/
2724void tst_QQuickPathView::objectModelMove()
2725{
2726 QScopedPointer<QQuickView> window(createView());
2727 window->setSource(testFileUrl(fileName: "objectModelMove.qml"));
2728 window->show();
2729
2730 // Create the view.
2731 QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "newView"));
2732 QPointer<QQuickPathView> pathView = window->rootObject()->property(name: "pathViewItem").value<QQuickPathView*>();
2733 QVERIFY(pathView);
2734 QCOMPARE(pathView->count(), 3);
2735 pathView->highlightItem()->setObjectName("highlight");
2736
2737 // Move an item from index 0 to 1.
2738 QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "move"));
2739 QCOMPARE(pathView->count(), 3);
2740
2741 // Keep track of the amount of listeners
2742 QVector<QString> itemObjectNames;
2743 itemObjectNames << QLatin1String("red") << QLatin1String("green") << QLatin1String("blue");
2744 QVector<QQuickItem*> childItems;
2745 for (const QString itemObjectName : qAsConst(t&: itemObjectNames)) {
2746 QQuickItem *childItem = findItem<QQuickItem>(parent: pathView, objectName: itemObjectName);
2747 QVERIFY(childItem);
2748 childItems.append(t: childItem);
2749 }
2750
2751 // Destroy the view (via destroy()).
2752 QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "destroyView"));
2753 // Ensure that the view has been destroyed. This check is also necessary in order for
2754 // ASAN to complain (it will complain after the test function has finished).
2755 QTRY_VERIFY(pathView.isNull());
2756 // By this point, all of its cached items should have been released,
2757 // which means none of the items should have any listeners.
2758 for (const auto childItem : qAsConst(t&: childItems)) {
2759 const QQuickItemPrivate *childItemPrivate = QQuickItemPrivate::get(item: childItem);
2760 QCOMPARE(childItemPrivate->changeListeners.size(), 0);
2761 }
2762}
2763
2764void tst_QQuickPathView::requiredPropertiesInDelegate()
2765{
2766 {
2767 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "Bill JonesBerlin0");
2768 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "Jane DoeOslo1");
2769 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "John SmithOulo2");
2770 QScopedPointer<QQuickView> window(createView());
2771 window->setSource(testFileUrl(fileName: "delegateWithRequiredProperties.qml"));
2772 window->show();
2773 }
2774 {
2775 QScopedPointer<QQuickView> window(createView());
2776 window->setSource(testFileUrl(fileName: "delegateWithRequiredProperties.2.qml"));
2777 window->show();
2778 QTRY_VERIFY(window->rootObject()->property("working").toBool());
2779 }
2780 {
2781 QScopedPointer<QQuickView> window(createView());
2782 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, messagePattern: QRegularExpression("Writing to \"name\" broke the binding to the underlying model"));
2783 window->setSource(testFileUrl(fileName: "delegateWithRequiredProperties.3.qml"));
2784 window->show();
2785 QTRY_VERIFY(window->rootObject()->property("working").toBool());
2786 }
2787}
2788
2789void tst_QQuickPathView::requiredPropertiesInDelegatePreventUnrelated()
2790{
2791 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "ReferenceError");
2792 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "ReferenceError");
2793 QTest::ignoreMessage(type: QtMsgType::QtInfoMsg, message: "ReferenceError");
2794 QScopedPointer<QQuickView> window(createView());
2795 window->setSource(testFileUrl(fileName: "delegatewithUnrelatedRequiredPreventsAccessToModel.qml"));
2796 window->show();
2797}
2798
2799QTEST_MAIN(tst_QQuickPathView)
2800
2801#include "tst_qquickpathview.moc"
2802

source code of qtdeclarative/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp