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