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) {
511 // Setting position is not supported on Wayland; message intentionaly not printed to
512 // avoid clutter in tests.
513 return true;
514 }
515 qCritical() << "Position failed to update";
516 return false;
517 }
518 return true;
519 }
520
521 // TODO maybe move the generic pointerPress/Move/Release functions to QTestLib later on
522
525
526 void pointerPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
527 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
528 {
529 const auto defaultDelay = QTest::defaultMouseDelay();
530 switch (dev->type()) {
531 case QPointingDevice::DeviceType::Mouse:
532 case QPointingDevice::DeviceType::TouchPad:
533 QTest::mousePress(window, button, modifiers, p, delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
534 break;
535 case QPointingDevice::DeviceType::TouchScreen:
536 // TODO apply delay when QTBUG-95421 is fixed
537 QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).press(pointId, p, window);
539 break;
540 case QPointingDevice::DeviceType::Puck:
541 case QPointingDevice::DeviceType::Stylus:
542 case QPointingDevice::DeviceType::Airbrush:{
543 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
544 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(p), window);
545 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
546 pressedTabletButton = button;
547 pressedTabletModifiers = modifiers;
548 QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
549 button, 0.8, 0, 0, 0, 0, 0, modifiers);
550 break;
551 }
552 default:
553 qWarning() << "can't send a press event from" << dev;
554 break;
555 }
556 }
557
558 void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p, int delay)
559 {
560 const auto defaultDelay = QTest::defaultMouseDelay();
561 switch (dev->type()) {
562 case QPointingDevice::DeviceType::Mouse:
563 case QPointingDevice::DeviceType::TouchPad:
564 QTest::mouseMove(window, p, delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
565 break;
566 case QPointingDevice::DeviceType::TouchScreen:
567 // TODO apply delay when QTBUG-95421 is fixed
568 QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).move(pointId, p, window);
570 break;
571 case QPointingDevice::DeviceType::Puck:
572 case QPointingDevice::DeviceType::Stylus:
573 case QPointingDevice::DeviceType::Airbrush: {
574 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
575 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(p), window);
576 const auto delay = QTest::defaultMouseDelay();
577 // often QTest::defaultMouseDelay() == 0; but avoid infinite velocity
578 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
579 QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
580 pressedTabletButton, pressedTabletButton == Qt::NoButton ? 0 : 0.75,
581 0, 0, 0, 0, 0, pressedTabletModifiers);
582 break;
583 }
584 default:
585 qWarning() << "can't send a move event from" << dev;
586 break;
587 }
588 }
589
590 void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
591 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
592 {
593 const auto defaultDelay = QTest::defaultMouseDelay();
594 switch (dev->type()) {
595 case QPointingDevice::DeviceType::Mouse:
596 case QPointingDevice::DeviceType::TouchPad:
597 QTest::mouseRelease(window, button, modifiers, p, delay >= 0 ? delay : defaultDelay ? defaultDelay : 1);
598 break;
599 case QPointingDevice::DeviceType::TouchScreen:
600 // TODO apply delay when QTBUG-95421 is fixed
601 QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).release(pointId, p, window);
603 break;
604 case QPointingDevice::DeviceType::Puck:
605 case QPointingDevice::DeviceType::Stylus:
606 case QPointingDevice::DeviceType::Airbrush: {
607 const QPointF nativeLocal = QHighDpi::toNativeLocalPosition(p, window);
608 const QPointF nativeGlobal = QHighDpi::toNativeGlobalPosition(window->mapToGlobal(p), window);
609 QTest::lastMouseTimestamp += delay >= 0 ? delay : defaultDelay ? defaultDelay : 1;
610 QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, nativeLocal, nativeGlobal,
611 Qt::NoButton, 0, 0, 0, 0, 0, 0, modifiers);
612 break;
613 }
614 default:
615 qWarning() << "can't send a press event from" << dev;
616 break;
617 }
618 }
619
620 void pointerMoveAndPress(const QPointingDevice *dev, QQuickWindow *window,
621 int pointId, const QPoint &p, Qt::MouseButton button,
622 Qt::KeyboardModifiers modifiers, int delay)
623 {
624 pointerMove(dev, window, pointId, p, delay);
625 pointerPress(dev, window, pointId, p, button, modifiers);
626 }
627
628 void pointerMoveAndRelease(const QPointingDevice *dev, QQuickWindow *window,
629 int pointId, const QPoint &p, Qt::MouseButton button,
630 Qt::KeyboardModifiers modifiers, int delay)
631 {
632 pointerMove(dev, window, pointId, p, delay);
633 pointerRelease(dev, window, pointId, p, button, modifiers);
634 }
635
636 void pointerFlick(const QPointingDevice *dev, QQuickWindow *window,
637 int pointId, const QPoint &from, const QPoint &to, int duration,
638 Qt::MouseButton button, Qt::KeyboardModifiers modifiers, int delay)
639 {
640 const int pointCount = 5;
641 const QPoint diff = to - from;
642
643 // send press, five equally spaced moves, and release.
644 pointerMoveAndPress(dev, window, pointId, from, button, modifiers, delay);
645
646 for (int i = 0; i < pointCount; ++i)
647 pointerMove(dev, window, pointId, from + (i + 1) * diff / pointCount, duration / pointCount);
648
649 pointerMoveAndRelease(dev, window, pointId, to, button, modifiers);
650 }
651}
652
653QT_END_NAMESPACE
654
655#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)