Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
viewtestutils.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5
6#include <QtCore/QRandomGenerator>
7#include <QtCore/QTimer>
8#include <QtQuick/QQuickView>
9#include <QtQuick/QQuickView>
10#include <QtGui/QScreen>
11#include <QtGui/qpa/qplatformintegration.h>
12#include <QtGui/qpa/qwindowsysteminterface.h>
13#include <QtGui/private/qguiapplication_p.h>
14#include <QtGui/private/qhighdpiscaling_p.h>
15
16#include <QtTest/QTest>
17
18#include <QtQuick/private/qquickdeliveryagent_p_p.h>
19#if QT_CONFIG(quick_itemview)
20#include <QtQuick/private/qquickitemview_p_p.h>
21#endif
22#include <QtQuick/private/qquickwindow_p.h>
23
24#include <QtQuickTestUtils/private/visualtestutils_p.h>
25
26QT_BEGIN_NAMESPACE
27
28QQuickView *QQuickViewTestUtils::createView()
29{
30 QQuickView *window = new QQuickView(0);
31 const QSize size(240, 320);
32 window->resize(size);
33 QQuickViewTestUtils::centerOnScreen(window, size);
34 return window;
35}
36
37void QQuickViewTestUtils::centerOnScreen(QQuickWindow *window, const QSize &size)
38{
39 const QRect screenGeometry = window->screen()->availableGeometry();
40 const QPoint offset = QPoint(size.width() / 2, size.height() / 2);
41 window->setFramePosition(screenGeometry.center() - offset);
42}
43
44void QQuickViewTestUtils::centerOnScreen(QQuickWindow *window)
45{
46 QQuickViewTestUtils::centerOnScreen(window, window->size());
47}
48
49void QQuickViewTestUtils::moveMouseAway(QQuickView *window)
50{
51#if QT_CONFIG(cursor) // Get the cursor out of the way.
52 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(
53 QPlatformIntegration::MouseCursorPositioning)) {
54 QCursor::setPos(window->geometry().topRight() + QPoint(100, 100));
55 }
56#else
57 Q_UNUSED(window);
58#endif
59}
60
61QList<int> QQuickViewTestUtils::adjustIndexesForAddDisplaced(const QList<int> &indexes, int index, int count)
62{
63 QList<int> result;
64 for (int i=0; i<indexes.size(); i++) {
65 int num = indexes[i];
66 if (num >= index) {
67 num += count;
68 }
69 result << num;
70 }
71 return result;
72}
73
74QList<int> QQuickViewTestUtils::adjustIndexesForMove(const QList<int> &indexes, int from, int to, int count)
75{
76 QList<int> result;
77 for (int i=0; i<indexes.size(); i++) {
78 int num = indexes[i];
79 if (from < to) {
80 if (num >= from && num < from + count)
81 num += (to - from); // target
82 else if (num >= from && num < to + count)
83 num -= count; // displaced
84 } else if (from > to) {
85 if (num >= from && num < from + count)
86 num -= (from - to); // target
87 else if (num >= to && num < from + count)
88 num += count; // displaced
89 }
90 result << num;
91 }
92 return result;
93}
94
95QList<int> QQuickViewTestUtils::adjustIndexesForRemoveDisplaced(const QList<int> &indexes, int index, int count)
96{
97 QList<int> result;
98 for (int i=0; i<indexes.size(); i++) {
99 int num = indexes[i];
100 if (num >= index)
101 num -= count;
102 result << num;
103 }
104 return result;
105}
106
107QQuickViewTestUtils::QaimModel::QaimModel(QObject *parent)
108 : QAbstractListModel(parent)
109{
110}
111
112int QQuickViewTestUtils::QaimModel::rowCount(const QModelIndex &parent) const
113{
114 Q_UNUSED(parent);
115 return list.size();
116}
117
118int QQuickViewTestUtils::QaimModel::columnCount(const QModelIndex &parent) const
119{
120 Q_UNUSED(parent);
121 return columns;
122}
123
124QHash<int,QByteArray> QQuickViewTestUtils::QaimModel::roleNames() const
125{
126 QHash<int,QByteArray> roles = QAbstractListModel::roleNames();
127 roles.insert(Name, "name");
128 roles.insert(Number, "number");
129 return roles;
130}
131
132QVariant QQuickViewTestUtils::QaimModel::data(const QModelIndex &index, int role) const
133{
134 QVariant rv;
135 if (role == Name)
136 rv = list.at(index.row()).first;
137 else if (role == Number)
138 rv = list.at(index.row()).second;
139
140 return rv;
141}
142
143int QQuickViewTestUtils::QaimModel::count() const
144{
145 return rowCount() * columnCount();
146}
147
148QString QQuickViewTestUtils::QaimModel::name(int index) const
149{
150 return list.at(index).first;
151}
152
153QString QQuickViewTestUtils::QaimModel::number(int index) const
154{
155 return list.at(index).second;
156}
157
158void QQuickViewTestUtils::QaimModel::addItem(const QString &name, const QString &number)
159{
160 emit beginInsertRows(QModelIndex(), list.size(), list.size());
161 list.append(std::pair<QString,QString>(name, number));
162 emit endInsertRows();
163}
164
165void QQuickViewTestUtils::QaimModel::addItems(const QList<std::pair<QString, QString> > &items)
166{
167 emit beginInsertRows(QModelIndex(), list.size(), list.size()+items.size()-1);
168 for (int i=0; i<items.size(); i++)
169 list.append(std::pair<QString,QString>(items[i].first, items[i].second));
170 emit endInsertRows();
171}
172
173void QQuickViewTestUtils::QaimModel::insertItem(int index, const QString &name, const QString &number)
174{
175 emit beginInsertRows(QModelIndex(), index, index);
176 list.insert(index, std::pair<QString,QString>(name, number));
177 emit endInsertRows();
178}
179
180void QQuickViewTestUtils::QaimModel::insertItems(int index, const QList<std::pair<QString, QString> > &items)
181{
182 emit beginInsertRows(QModelIndex(), index, index+items.size()-1);
183 for (int i=0; i<items.size(); i++)
184 list.insert(index + i, std::pair<QString,QString>(items[i].first, items[i].second));
185 emit endInsertRows();
186}
187
188void QQuickViewTestUtils::QaimModel::removeItem(int index)
189{
190 emit beginRemoveRows(QModelIndex(), index, index);
191 list.removeAt(index);
192 emit endRemoveRows();
193}
194
195void QQuickViewTestUtils::QaimModel::removeItems(int index, int count)
196{
197 emit beginRemoveRows(QModelIndex(), index, index+count-1);
198 while (count--)
199 list.removeAt(index);
200 emit endRemoveRows();
201}
202
203void QQuickViewTestUtils::QaimModel::moveItem(int from, int to)
204{
205 emit beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
206 list.move(from, to);
207 emit endMoveRows();
208}
209
210void QQuickViewTestUtils::QaimModel::moveItems(int from, int to, int count)
211{
212 emit beginMoveRows(QModelIndex(), from, from+count-1, QModelIndex(), to > from ? to+count : to);
213 qquickmodelviewstestutil_move(from, to, count, &list);
214 emit endMoveRows();
215}
216
217void QQuickViewTestUtils::QaimModel::modifyItem(int idx, const QString &name, const QString &number)
218{
219 list[idx] = std::pair<QString,QString>(name, number);
220 emit dataChanged(index(idx,0), index(idx,0));
221}
222
223void QQuickViewTestUtils::QaimModel::clear()
224{
225 int count = list.size();
226 if (count > 0) {
227 beginRemoveRows(QModelIndex(), 0, count-1);
228 list.clear();
229 endRemoveRows();
230 }
231}
232
233void QQuickViewTestUtils::QaimModel::reset()
234{
235 emit beginResetModel();
236 emit endResetModel();
237}
238
239void QQuickViewTestUtils::QaimModel::resetItems(const QList<std::pair<QString, QString> > &items)
240{
241 beginResetModel();
242 list = items;
243 endResetModel();
244}
245
246class ScopedPrintable
247{
248 Q_DISABLE_COPY_MOVE(ScopedPrintable)
249
250public:
251 ScopedPrintable(const QString &string) : data(QTest::toString(string)) {}
252 ~ScopedPrintable() { delete[] data; }
253
254 operator const char*() const { return data; }
255
256private:
257 const char *data;
258};
259
261 for (int i=0; i<other.size(); i++) {
262 QVERIFY2(list.contains(other[i]),
263 ScopedPrintable(other[i].first + QLatin1Char(' ') + other[i].second + QLatin1Char(' ') + error1));
264 }
265 for (int i=0; i<list.size(); i++) {
266 QVERIFY2(other.contains(list[i]),
267 ScopedPrintable(list[i].first + QLatin1Char(' ') + list[i].second + QLatin1Char(' ') + error2));
268 }
269}
270
271
272
274 : valid(false)
275{
276}
277
279 : valid(other.valid)
280{
281 indexes = other.indexes;
282}
283
285 : valid(true)
286{
287 for (int i=start; i<=end; i++)
288 indexes << i;
289}
290
294
296{
297 if (other == *this)
298 return *this;
299 ListRange a(*this);
300 a.indexes.append(other.indexes);
301 return a;
302}
303
305{
306 return QSet<int>(indexes.cbegin(), indexes.cend())
307 == QSet<int>(other.indexes.cbegin(), other.indexes.cend());
308}
309
311{
312 return !(*this == other);
313}
314
316{
317 return valid;
318}
319
321{
322 return indexes.size();
323}
324
325QList<std::pair<QString,QString> > QQuickViewTestUtils::ListRange::getModelDataValues(const QaimModel &model)
326{
327 QList<std::pair<QString,QString> > data;
328 if (!valid)
329 return data;
330 for (int i=0; i<indexes.size(); i++)
331 data.append(std::make_pair(model.name(indexes[i]), model.number(indexes[i])));
332 return data;
333}
334
336 : QAbstractListModel()
337 , m_rowCount(20)
338{
339 QTimer *t = new QTimer(this);
340 t->setInterval(500);
341 t->start();
342
343 connect(t, &QTimer::timeout, this, &StressTestModel::updateModel);
344}
345
346int QQuickViewTestUtils::StressTestModel::rowCount(const QModelIndex &) const
347{
348 return m_rowCount;
349}
350
351QVariant QQuickViewTestUtils::StressTestModel::data(const QModelIndex &, int) const
352{
353 return QVariant();
354}
355
357{
358 if (m_rowCount > 10) {
359 for (int i = 0; i < 10; ++i) {
360 int rnum = QRandomGenerator::global()->bounded(m_rowCount);
361 beginRemoveRows(QModelIndex(), rnum, rnum);
362 m_rowCount--;
363 endRemoveRows();
364 }
365 }
366 if (m_rowCount < 20) {
367 for (int i = 0; i < 10; ++i) {
368 int rnum = QRandomGenerator::global()->bounded(m_rowCount);
369 beginInsertRows(QModelIndex(), rnum, rnum);
370 m_rowCount++;
371 endInsertRows();
372 }
373 }
374}
375
376#if QT_CONFIG(quick_itemview) && defined(QT_BUILD_INTERNAL)
377bool QQuickViewTestUtils::testVisibleItems(const QQuickItemViewPrivate *priv, bool *nonUnique, FxViewItem **failItem, int *expectedIdx)
378{
379 QHash<QQuickItem*, int> uniqueItems;
380
381 int skip = 0;
382 for (int i = 0; i < priv->visibleItems.size(); ++i) {
383 FxViewItem *item = priv->visibleItems.at(i);
384 if (!item) {
385 *failItem = nullptr;
386 return false;
387 }
388#if 0
389 qDebug() << "\t" << item->index
390 << item->item
391 << item->position()
392 << (!item->item || QQuickItemPrivate::get(item->item)->culled ? "hidden" : "visible");
393#endif
394 if (item->index == -1) {
395 ++skip;
396 } else if (item->index != priv->visibleIndex + i - skip) {
397 *nonUnique = false;
398 *failItem = item;
399 *expectedIdx = priv->visibleIndex + i - skip;
400 return false;
401 } else if (uniqueItems.contains(item->item)) {
402 *nonUnique = true;
403 *failItem = item;
404 *expectedIdx = uniqueItems.find(item->item).value();
405 return false;
406 }
407
408 uniqueItems.insert(item->item, item->index);
409 }
410
411 return true;
412}
413#endif
414
416
417 /* QQuickWindow does event compression and only delivers events just
418 * before it is about to render the next frame. Since some tests
419 * rely on events being delivered immediately AND that no other
420 * event processing has occurred in the meanwhile, we flush the
421 * event manually and immediately.
422 */
423 void flush(QQuickWindow *window) {
424 if (!window)
425 return;
426 QQuickDeliveryAgentPrivate *da = QQuickWindowPrivate::get(window)->deliveryAgentPrivate();
427 if (!da || !da->delayedTouch)
428 return;
429 da->deliverDelayedTouchEvent();
430 }
431
432}
433
434namespace QTest {
435 int Q_TESTLIB_EXPORT defaultMouseDelay();
436}
437
438namespace QQuickTest {
439
440 /*! \internal
441 Initialize \a view, set \a url, center in available geometry, move mouse away if desired.
442 If \a errorMessage is given, QQuickView::errors() will be concatenated into it;
443 otherwise, the QWARN messages are generally enough to debug the test.
444
445 Returns \c false if the view fails to load the QML. That should be fatal in most tests,
446 so normally the return value should be checked with QVERIFY.
447 */
449 {
452 while (view.status() == QQuickView::Loading)
453 QTest::qWait(10);
454 if (view.status() != QQuickView::Ready) {
455 if (errorMessage) {
456 for (const QQmlError &e : view.errors())
458 }
459 return false;
460 }
461 if (view.width() == 0)
462 view.setWidth(100);
463 if (view.height() == 0)
464 view.setHeight(100);
465 if (!platformIsWayland) {
466 const QSize size = view.size();
468 const QPoint offset = QPoint(size.width() / 2, size.height() / 2);
470#if QT_CONFIG(cursor) // Get the cursor out of the way. But it's not possible on Wayland.
473 QCursor::setPos(view.geometry().topRight() + QPoint(100, 100));
474 }
475#else
477#endif
478 }
479 return true;
480 }
481
482 /*! \internal
483 Initialize \a view, set \a url, center in available geometry, move mouse away,
484 show the \a view, wait for it to be exposed, and verify that its rootObject is not null.
485
486 Returns \c false if anything fails, which should be fatal in most tests.
487 The usual way to call this function is
488 \code
489 QQuickView window;
490 QVERIFY(QQuickTest::showView(window, testFileUrl("myitems.qml")));
491 \endcode
492 */
494 {
496 if (!initView(view, url, true, &errorMessage)) {
498 return false;
499 }
501 view.show();
503 qCritical() << "qWaitForWindowExposed() failed";
504 return false;
505 }
506 if (!view.rootObject()) {
507 qCritical() << "View has no root object";
508 const auto errors = view.errors();
509 for (const auto &error : errors)
511 return false;
512 }
514 return true;
515 const bool positionOk = QTest::qWaitFor([&]{ return framePos != view.position(); });
516 if (!positionOk) {
518 // Setting position is not supported on Wayland; message intentionaly not printed to
519 // avoid clutter in tests.
520 return true;
521 }
522 qCritical() << "Position failed to update";
523 return false;
524 }
525 return true;
526 }
527
528 // TODO maybe move the generic pointerPress/Move/Release functions to QTestLib later on
529
532
533 void pointerPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
534 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
535 {
536 const auto defaultDelay = QTest::defaultMouseDelay();
537 switch (dev->type()) {
538 case QPointingDevice::DeviceType::Mouse:
539 case QPointingDevice::DeviceType::TouchPad:
540 QTest::mousePress(window, button, modifiers, p, delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
541 break;
542 case QPointingDevice::DeviceType::TouchScreen:
543 // TODO apply delay when QTBUG-95421 is fixed
544 QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).press(pointId, p, window);
546 break;
547 case QPointingDevice::DeviceType::Puck:
548 case QPointingDevice::DeviceType::Stylus:
549 case QPointingDevice::DeviceType::Airbrush:{
550 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
551 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(QPointF(p)), window);
552 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
553 pressedTabletButton = button;
554 pressedTabletModifiers = modifiers;
555 QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
556 button, 0.8, 0, 0, 0, 0, 0, modifiers);
557 break;
558 }
559 default:
560 qWarning() << "can't send a press event from" << dev;
561 break;
562 }
563 }
564
565 void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, int delay)
566 {
567 const auto defaultDelay = QTest::defaultMouseDelay();
568 switch (dev->type()) {
569 case QPointingDevice::DeviceType::Mouse:
570 case QPointingDevice::DeviceType::TouchPad:
571 QTest::mouseMove(window, p, delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
572 break;
573 case QPointingDevice::DeviceType::TouchScreen:
574 // TODO apply delay when QTBUG-95421 is fixed
575 QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).move(pointId, p, window);
577 break;
578 case QPointingDevice::DeviceType::Puck:
579 case QPointingDevice::DeviceType::Stylus:
580 case QPointingDevice::DeviceType::Airbrush: {
581 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
582 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(QPointF(p)), window);
583 const auto delay = QTest::defaultMouseDelay();
584 // often QTest::defaultMouseDelay() == 0; but avoid infinite velocity
585 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
586 QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
587 pressedTabletButton, pressedTabletButton == Qt::NoButton ? 0 : 0.75,
588 0, 0, 0, 0, 0, pressedTabletModifiers);
589 break;
590 }
591 default:
592 qWarning() << "can't send a move event from" << dev;
593 break;
594 }
595 }
596
597 void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
598 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
599 {
600 const auto defaultDelay = QTest::defaultMouseDelay();
601 switch (dev->type()) {
602 case QPointingDevice::DeviceType::Mouse:
603 case QPointingDevice::DeviceType::TouchPad:
604 QTest::mouseRelease(window, button, modifiers, p, delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
605 break;
606 case QPointingDevice::DeviceType::TouchScreen:
607 // TODO apply delay when QTBUG-95421 is fixed
608 QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).release(pointId, p, window);
610 break;
611 case QPointingDevice::DeviceType::Puck:
612 case QPointingDevice::DeviceType::Stylus:
613 case QPointingDevice::DeviceType::Airbrush: {
614 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
615 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(QPointF(p)), window);
616 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
617 QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
618 Qt::NoButton, 0, 0, 0, 0, 0, 0, modifiers);
619 break;
620 }
621 default:
622 qWarning() << "can't send a press event from" << dev;
623 break;
624 }
625 }
626
627 void pointerMoveAndPress(const QPointingDevice *dev, QQuickWindow *window,
628 int pointId, const QPoint &p, Qt::MouseButton button,
629 Qt::KeyboardModifiers modifiers, int delay)
630 {
631 pointerMove(dev, window, pointId, p, delay);
632 pointerPress(dev, window, pointId, p, button, modifiers);
633 }
634
635 void pointerMoveAndRelease(const QPointingDevice *dev, QQuickWindow *window,
636 int pointId, const QPoint &p, Qt::MouseButton button,
637 Qt::KeyboardModifiers modifiers, int delay)
638 {
639 pointerMove(dev, window, pointId, p, delay);
640 pointerRelease(dev, window, pointId, p, button, modifiers);
641 }
642
643 void pointerFlick(const QPointingDevice *dev, QQuickWindow *window,
644 int pointId, const QPoint &from, const QPoint &to, int duration,
645 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
646 {
647 const int pointCount = 5;
648 const QPoint diff = to - from;
649
650 // send press, five equally spaced moves, and release.
651 pointerMoveAndPress(dev, window, pointId, from, button, modifiers, delay);
652
653 for (int i = 0; i < pointCount; ++i)
654 pointerMove(dev, window, pointId, from + (i + 1) * diff / pointCount, duration / pointCount);
655
656 pointerMoveAndRelease(dev, window, pointId, to, button, modifiers);
657 }
658}
659
660QT_END_NAMESPACE
661
662#include "moc_viewtestutils_p.cpp"
bool operator==(const ListRange &other) const
ListRange(const ListRange &other)
bool operator!=(const ListRange &other) const
ListRange operator+(const ListRange &other) const
void matchAgainst(const QList< std::pair< QString, QString > > &other, const QString &error1, const QString &error2)
QVariant data(const QModelIndex &, int) const override
Returns the data stored under the given role for the item referred to by the index.
int rowCount(const QModelIndex &) const override
Returns the number of rows under the given parent.
void pointerPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
void pointerMoveAndPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
void pointerFlick(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &from, const QPoint &to, int duration, Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
bool initView(QQuickView &view, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage)
void pointerMoveAndRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, int delay)
bool showView(QQuickView &view, const QUrl &url)
static Qt::KeyboardModifiers pressedTabletModifiers
static Qt::MouseButton pressedTabletButton
void flush(QQuickWindow *window)