aboutsummaryrefslogtreecommitdiffstats
path: root/src/quicktestutils/quick/viewtestutils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quicktestutils/quick/viewtestutils.cpp')
-rw-r--r--src/quicktestutils/quick/viewtestutils.cpp606
1 files changed, 606 insertions, 0 deletions
diff --git a/src/quicktestutils/quick/viewtestutils.cpp b/src/quicktestutils/quick/viewtestutils.cpp
new file mode 100644
index 0000000000..053e864660
--- /dev/null
+++ b/src/quicktestutils/quick/viewtestutils.cpp
@@ -0,0 +1,606 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "viewtestutils_p.h"
+
+#include <QtCore/QRandomGenerator>
+#include <QtCore/QTimer>
+#include <QtQuick/QQuickView>
+#include <QtQuick/QQuickView>
+#include <QtGui/QScreen>
+#include <QtGui/qpa/qwindowsysteminterface.h>
+
+#include <QtTest/QTest>
+
+#include <QtQuick/private/qquickdeliveryagent_p_p.h>
+#if QT_CONFIG(quick_itemview)
+#include <QtQuick/private/qquickitemview_p_p.h>
+#endif
+#include <QtQuick/private/qquickwindow_p.h>
+
+#include <QtQuickTestUtils/private/visualtestutils_p.h>
+
+QT_BEGIN_NAMESPACE
+
+QQuickView *QQuickViewTestUtils::createView()
+{
+ QQuickView *window = new QQuickView(0);
+ const QSize size(240, 320);
+ window->resize(size);
+ QQuickViewTestUtils::centerOnScreen(window, size);
+ return window;
+}
+
+void QQuickViewTestUtils::centerOnScreen(QQuickView *window, const QSize &size)
+{
+ const QRect screenGeometry = window->screen()->availableGeometry();
+ const QPoint offset = QPoint(size.width() / 2, size.height() / 2);
+ window->setFramePosition(screenGeometry.center() - offset);
+}
+
+void QQuickViewTestUtils::centerOnScreen(QQuickView *window)
+{
+ QQuickViewTestUtils::centerOnScreen(window, window->size());
+}
+
+void QQuickViewTestUtils::moveMouseAway(QQuickView *window)
+{
+#if QT_CONFIG(cursor) // Get the cursor out of the way.
+ QCursor::setPos(window->geometry().topRight() + QPoint(100, 100));
+#else
+ Q_UNUSED(window);
+#endif
+}
+
+void QQuickViewTestUtils::moveAndRelease(QQuickView *window, const QPoint &position)
+{
+ QTest::mouseMove(window, position);
+ QTest::mouseRelease(window, Qt::LeftButton, {}, position);
+}
+
+void QQuickViewTestUtils::moveAndPress(QQuickView *window, const QPoint &position)
+{
+ QTest::mouseMove(window, position);
+ QTest::mousePress(window, Qt::LeftButton, {}, position);
+}
+
+void QQuickViewTestUtils::flick(QQuickView *window, const QPoint &from, const QPoint &to, int duration)
+{
+ const int pointCount = 5;
+ QPoint diff = to - from;
+
+ // send press, five equally spaced moves, and release.
+ moveAndPress(window, from);
+
+ for (int i = 0; i < pointCount; ++i)
+ QTest::mouseMove(window, from + (i+1)*diff/pointCount, duration / pointCount);
+
+ moveAndRelease(window, to);
+ QTest::qWait(50);
+}
+
+QList<int> QQuickViewTestUtils::adjustIndexesForAddDisplaced(const QList<int> &indexes, int index, int count)
+{
+ QList<int> result;
+ for (int i=0; i<indexes.size(); i++) {
+ int num = indexes[i];
+ if (num >= index) {
+ num += count;
+ }
+ result << num;
+ }
+ return result;
+}
+
+QList<int> QQuickViewTestUtils::adjustIndexesForMove(const QList<int> &indexes, int from, int to, int count)
+{
+ QList<int> result;
+ for (int i=0; i<indexes.size(); i++) {
+ int num = indexes[i];
+ if (from < to) {
+ if (num >= from && num < from + count)
+ num += (to - from); // target
+ else if (num >= from && num < to + count)
+ num -= count; // displaced
+ } else if (from > to) {
+ if (num >= from && num < from + count)
+ num -= (from - to); // target
+ else if (num >= to && num < from + count)
+ num += count; // displaced
+ }
+ result << num;
+ }
+ return result;
+}
+
+QList<int> QQuickViewTestUtils::adjustIndexesForRemoveDisplaced(const QList<int> &indexes, int index, int count)
+{
+ QList<int> result;
+ for (int i=0; i<indexes.size(); i++) {
+ int num = indexes[i];
+ if (num >= index)
+ num -= count;
+ result << num;
+ }
+ return result;
+}
+
+QQuickViewTestUtils::QaimModel::QaimModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+int QQuickViewTestUtils::QaimModel::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return list.size();
+}
+
+int QQuickViewTestUtils::QaimModel::columnCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return columns;
+}
+
+QHash<int,QByteArray> QQuickViewTestUtils::QaimModel::roleNames() const
+{
+ QHash<int,QByteArray> roles = QAbstractListModel::roleNames();
+ roles.insert(Name, "name");
+ roles.insert(Number, "number");
+ return roles;
+}
+
+QVariant QQuickViewTestUtils::QaimModel::data(const QModelIndex &index, int role) const
+{
+ QVariant rv;
+ if (role == Name)
+ rv = list.at(index.row()).first;
+ else if (role == Number)
+ rv = list.at(index.row()).second;
+
+ return rv;
+}
+
+int QQuickViewTestUtils::QaimModel::count() const
+{
+ return rowCount() * columnCount();
+}
+
+QString QQuickViewTestUtils::QaimModel::name(int index) const
+{
+ return list.at(index).first;
+}
+
+QString QQuickViewTestUtils::QaimModel::number(int index) const
+{
+ return list.at(index).second;
+}
+
+void QQuickViewTestUtils::QaimModel::addItem(const QString &name, const QString &number)
+{
+ emit beginInsertRows(QModelIndex(), list.size(), list.size());
+ list.append(QPair<QString,QString>(name, number));
+ emit endInsertRows();
+}
+
+void QQuickViewTestUtils::QaimModel::addItems(const QList<QPair<QString, QString> > &items)
+{
+ emit beginInsertRows(QModelIndex(), list.size(), list.size()+items.size()-1);
+ for (int i=0; i<items.size(); i++)
+ list.append(QPair<QString,QString>(items[i].first, items[i].second));
+ emit endInsertRows();
+}
+
+void QQuickViewTestUtils::QaimModel::insertItem(int index, const QString &name, const QString &number)
+{
+ emit beginInsertRows(QModelIndex(), index, index);
+ list.insert(index, QPair<QString,QString>(name, number));
+ emit endInsertRows();
+}
+
+void QQuickViewTestUtils::QaimModel::insertItems(int index, const QList<QPair<QString, QString> > &items)
+{
+ emit beginInsertRows(QModelIndex(), index, index+items.size()-1);
+ for (int i=0; i<items.size(); i++)
+ list.insert(index + i, QPair<QString,QString>(items[i].first, items[i].second));
+ emit endInsertRows();
+}
+
+void QQuickViewTestUtils::QaimModel::removeItem(int index)
+{
+ emit beginRemoveRows(QModelIndex(), index, index);
+ list.removeAt(index);
+ emit endRemoveRows();
+}
+
+void QQuickViewTestUtils::QaimModel::removeItems(int index, int count)
+{
+ emit beginRemoveRows(QModelIndex(), index, index+count-1);
+ while (count--)
+ list.removeAt(index);
+ emit endRemoveRows();
+}
+
+void QQuickViewTestUtils::QaimModel::moveItem(int from, int to)
+{
+ emit beginMoveRows(QModelIndex(), from, from, QModelIndex(), to);
+ list.move(from, to);
+ emit endMoveRows();
+}
+
+void QQuickViewTestUtils::QaimModel::moveItems(int from, int to, int count)
+{
+ emit beginMoveRows(QModelIndex(), from, from+count-1, QModelIndex(), to > from ? to+count : to);
+ qquickmodelviewstestutil_move(from, to, count, &list);
+ emit endMoveRows();
+}
+
+void QQuickViewTestUtils::QaimModel::modifyItem(int idx, const QString &name, const QString &number)
+{
+ list[idx] = QPair<QString,QString>(name, number);
+ emit dataChanged(index(idx,0), index(idx,0));
+}
+
+void QQuickViewTestUtils::QaimModel::clear()
+{
+ int count = list.size();
+ if (count > 0) {
+ beginRemoveRows(QModelIndex(), 0, count-1);
+ list.clear();
+ endRemoveRows();
+ }
+}
+
+void QQuickViewTestUtils::QaimModel::reset()
+{
+ emit beginResetModel();
+ emit endResetModel();
+}
+
+void QQuickViewTestUtils::QaimModel::resetItems(const QList<QPair<QString, QString> > &items)
+{
+ beginResetModel();
+ list = items;
+ endResetModel();
+}
+
+class ScopedPrintable
+{
+ Q_DISABLE_COPY_MOVE(ScopedPrintable)
+
+public:
+ ScopedPrintable(const QString &string) : data(QTest::toString(string)) {}
+ ~ScopedPrintable() { delete[] data; }
+
+ operator const char*() const { return data; }
+
+private:
+ const char *data;
+};
+
+void QQuickViewTestUtils::QaimModel::matchAgainst(const QList<QPair<QString, QString> > &other, const QString &error1, const QString &error2) {
+ for (int i=0; i<other.size(); i++) {
+ QVERIFY2(list.contains(other[i]),
+ ScopedPrintable(other[i].first + QLatin1Char(' ') + other[i].second + QLatin1Char(' ') + error1));
+ }
+ for (int i=0; i<list.size(); i++) {
+ QVERIFY2(other.contains(list[i]),
+ ScopedPrintable(list[i].first + QLatin1Char(' ') + list[i].second + QLatin1Char(' ') + error2));
+ }
+}
+
+
+
+QQuickViewTestUtils::ListRange::ListRange()
+ : valid(false)
+{
+}
+
+QQuickViewTestUtils::ListRange::ListRange(const ListRange &other)
+ : valid(other.valid)
+{
+ indexes = other.indexes;
+}
+
+QQuickViewTestUtils::ListRange::ListRange(int start, int end)
+ : valid(true)
+{
+ for (int i=start; i<=end; i++)
+ indexes << i;
+}
+
+QQuickViewTestUtils::ListRange::~ListRange()
+{
+}
+
+QQuickViewTestUtils::ListRange QQuickViewTestUtils::ListRange::operator+(const ListRange &other) const
+{
+ if (other == *this)
+ return *this;
+ ListRange a(*this);
+ a.indexes.append(other.indexes);
+ return a;
+}
+
+bool QQuickViewTestUtils::ListRange::operator==(const ListRange &other) const
+{
+ return QSet<int>(indexes.cbegin(), indexes.cend())
+ == QSet<int>(other.indexes.cbegin(), other.indexes.cend());
+}
+
+bool QQuickViewTestUtils::ListRange::operator!=(const ListRange &other) const
+{
+ return !(*this == other);
+}
+
+bool QQuickViewTestUtils::ListRange::isValid() const
+{
+ return valid;
+}
+
+int QQuickViewTestUtils::ListRange::count() const
+{
+ return indexes.size();
+}
+
+QList<QPair<QString,QString> > QQuickViewTestUtils::ListRange::getModelDataValues(const QaimModel &model)
+{
+ QList<QPair<QString,QString> > data;
+ if (!valid)
+ return data;
+ for (int i=0; i<indexes.size(); i++)
+ data.append(qMakePair(model.name(indexes[i]), model.number(indexes[i])));
+ return data;
+}
+
+QQuickViewTestUtils::StressTestModel::StressTestModel()
+ : QAbstractListModel()
+ , m_rowCount(20)
+{
+ QTimer *t = new QTimer(this);
+ t->setInterval(500);
+ t->start();
+
+ connect(t, &QTimer::timeout, this, &StressTestModel::updateModel);
+}
+
+int QQuickViewTestUtils::StressTestModel::rowCount(const QModelIndex &) const
+{
+ return m_rowCount;
+}
+
+QVariant QQuickViewTestUtils::StressTestModel::data(const QModelIndex &, int) const
+{
+ return QVariant();
+}
+
+void QQuickViewTestUtils::StressTestModel::updateModel()
+{
+ if (m_rowCount > 10) {
+ for (int i = 0; i < 10; ++i) {
+ int rnum = QRandomGenerator::global()->bounded(m_rowCount);
+ beginRemoveRows(QModelIndex(), rnum, rnum);
+ m_rowCount--;
+ endRemoveRows();
+ }
+ }
+ if (m_rowCount < 20) {
+ for (int i = 0; i < 10; ++i) {
+ int rnum = QRandomGenerator::global()->bounded(m_rowCount);
+ beginInsertRows(QModelIndex(), rnum, rnum);
+ m_rowCount++;
+ endInsertRows();
+ }
+ }
+}
+
+#if QT_CONFIG(quick_itemview) && defined(QT_BUILD_INTERNAL)
+bool QQuickViewTestUtils::testVisibleItems(const QQuickItemViewPrivate *priv, bool *nonUnique, FxViewItem **failItem, int *expectedIdx)
+{
+ QHash<QQuickItem*, int> uniqueItems;
+
+ int skip = 0;
+ for (int i = 0; i < priv->visibleItems.size(); ++i) {
+ FxViewItem *item = priv->visibleItems.at(i);
+ if (!item) {
+ *failItem = nullptr;
+ return false;
+ }
+#if 0
+ qDebug() << "\t" << item->index
+ << item->item
+ << item->position()
+ << (!item->item || QQuickItemPrivate::get(item->item)->culled ? "hidden" : "visible");
+#endif
+ if (item->index == -1) {
+ ++skip;
+ } else if (item->index != priv->visibleIndex + i - skip) {
+ *nonUnique = false;
+ *failItem = item;
+ *expectedIdx = priv->visibleIndex + i - skip;
+ return false;
+ } else if (uniqueItems.contains(item->item)) {
+ *nonUnique = true;
+ *failItem = item;
+ *expectedIdx = uniqueItems.find(item->item).value();
+ return false;
+ }
+
+ uniqueItems.insert(item->item, item->index);
+ }
+
+ return true;
+}
+#endif
+
+namespace QQuickTouchUtils {
+
+ /* QQuickWindow does event compression and only delivers events just
+ * before it is about to render the next frame. Since some tests
+ * rely on events being delivered immediately AND that no other
+ * event processing has occurred in the meanwhile, we flush the
+ * event manually and immediately.
+ */
+ void flush(QQuickWindow *window) {
+ if (!window)
+ return;
+ QQuickDeliveryAgentPrivate *da = QQuickWindowPrivate::get(window)->deliveryAgentPrivate();
+ if (!da || !da->delayedTouch)
+ return;
+ da->deliverDelayedTouchEvent();
+ }
+
+}
+
+namespace QTest {
+ int Q_TESTLIB_EXPORT defaultMouseDelay();
+}
+
+namespace QQuickTest {
+
+ /*! \internal
+ Initialize \a view, set \a url, center in available geometry, move mouse away if desired.
+ If \a errorMessage is given, QQuickView::errors() will be concatenated into it;
+ otherwise, the QWARN messages are generally enough to debug the test.
+
+ Returns \c false if the view fails to load the QML. That should be fatal in most tests,
+ so normally the return value should be checked with QVERIFY.
+ */
+ bool initView(QQuickView &view, const QUrl &url, bool moveMouseOut, QByteArray *errorMessage)
+ {
+ view.setSource(url);
+ while (view.status() == QQuickView::Loading)
+ QTest::qWait(10);
+ if (view.status() != QQuickView::Ready) {
+ if (errorMessage) {
+ for (const QQmlError &e : view.errors())
+ errorMessage->append(e.toString().toLocal8Bit() + '\n');
+ }
+ return false;
+ }
+ const QRect screenGeometry = view.screen()->availableGeometry();
+ const QSize size = view.size();
+ if (view.width() == 0)
+ view.setWidth(100);
+ if (view.height() == 0)
+ view.setHeight(100);
+ const QPoint offset = QPoint(size.width() / 2, size.height() / 2);
+ view.setFramePosition(screenGeometry.center() - offset);
+ #if QT_CONFIG(cursor) // Get the cursor out of the way.
+ if (moveMouseOut)
+ QCursor::setPos(view.geometry().topRight() + QPoint(100, 100));
+ #else
+ Q_UNUSED(moveMouseOut);
+ #endif
+ return true;
+ }
+
+ /*! \internal
+ Initialize \a view, set \a url, center in available geometry, move mouse away,
+ show the \a view, wait for it to be exposed, and verify that its rootObject is not null.
+
+ Returns \c false if anything fails, which should be fatal in most tests.
+ The usual way to call this function is
+ \code
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl("myitems.qml")));
+ \endcode
+ */
+ bool showView(QQuickView &view, const QUrl &url)
+ {
+ if (!initView(view, url))
+ return false;
+ view.show();
+ if (!QTest::qWaitForWindowExposed(&view))
+ return false;
+ if (!view.rootObject())
+ return false;
+ return true;
+ }
+
+ // TODO maybe move the generic pointerPress/Move/Release functions to QTestLib later on
+
+ static Qt::MouseButton pressedTabletButton = Qt::NoButton;
+ static Qt::KeyboardModifiers pressedTabletModifiers = Qt::NoModifier;
+
+ void pointerPress(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
+ Qt::MouseButton button, Qt::KeyboardModifiers modifiers)
+ {
+ switch (dev->type()) {
+ case QPointingDevice::DeviceType::Mouse:
+ case QPointingDevice::DeviceType::TouchPad:
+ QTest::mousePress(window, button, modifiers, p);
+ break;
+ case QPointingDevice::DeviceType::TouchScreen:
+ QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).press(pointId, p, window);
+ QQuickTouchUtils::flush(window);
+ break;
+ case QPointingDevice::DeviceType::Puck:
+ case QPointingDevice::DeviceType::Stylus:
+ case QPointingDevice::DeviceType::Airbrush:
+ QTest::lastMouseTimestamp += QTest::defaultMouseDelay();
+ pressedTabletButton = button;
+ pressedTabletModifiers = modifiers;
+ QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p),
+ button, 0.8, 0, 0, 0, 0, 0, modifiers);
+ break;
+ default:
+ qWarning() << "can't send a press event from" << dev;
+ break;
+ }
+ }
+
+ void pointerMove(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p)
+ {
+ switch (dev->type()) {
+ case QPointingDevice::DeviceType::Mouse:
+ case QPointingDevice::DeviceType::TouchPad:
+ QTest::mouseMove(window, p);
+ break;
+ case QPointingDevice::DeviceType::TouchScreen:
+ QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).move(pointId, p, window);
+ QQuickTouchUtils::flush(window);
+ break;
+ case QPointingDevice::DeviceType::Puck:
+ case QPointingDevice::DeviceType::Stylus:
+ case QPointingDevice::DeviceType::Airbrush:
+ QTest::lastMouseTimestamp += QTest::defaultMouseDelay();
+ QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p),
+ pressedTabletButton, 0, 0, 0, 0, 0, 0, pressedTabletModifiers);
+ break;
+ default:
+ qWarning() << "can't send a move event from" << dev;
+ break;
+ }
+ }
+
+ void pointerRelease(const QPointingDevice *dev, QQuickWindow *window, int pointId, const QPoint &p,
+ Qt::MouseButton button, Qt::KeyboardModifiers modifiers)
+ {
+ switch (dev->type()) {
+ case QPointingDevice::DeviceType::Mouse:
+ case QPointingDevice::DeviceType::TouchPad:
+ QTest::mouseRelease(window, button, modifiers, p);
+ break;
+ case QPointingDevice::DeviceType::TouchScreen:
+ QTest::touchEvent(window, const_cast<QPointingDevice *>(dev)).release(pointId, p, window);
+ QQuickTouchUtils::flush(window);
+ break;
+ case QPointingDevice::DeviceType::Puck:
+ case QPointingDevice::DeviceType::Stylus:
+ case QPointingDevice::DeviceType::Airbrush:
+ QTest::lastMouseTimestamp += QTest::defaultMouseDelay();
+ QWindowSystemInterface::handleTabletEvent(window, QTest::lastMouseTimestamp, dev, p, window->mapToGlobal(p),
+ Qt::NoButton, 0, 0, 0, 0, 0, 0, modifiers);
+ break;
+ default:
+ qWarning() << "can't send a press event from" << dev;
+ break;
+ }
+ }
+
+}
+
+QT_END_NAMESPACE
+
+#include "moc_viewtestutils_p.cpp"