1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "visualtestutils_p.h"
5
6#include <QtCore/QCoreApplication>
7#include <QtCore/private/qvariantanimation_p.h>
8#include <QtCore/QDebug>
9#include <QtQuick/QQuickItem>
10#if QT_CONFIG(quick_itemview)
11#include <QtQuick/private/qquickitemview_p.h>
12#endif
13#include <QtQuickTest/QtQuickTest>
14#include <QtQuickTestUtils/private/viewtestutils_p.h>
15
16QT_BEGIN_NAMESPACE
17
18QQuickItem *QQuickVisualTestUtils::findVisibleChild(QQuickItem *parent, const QString &objectName)
19{
20 QQuickItem *item = nullptr;
21 QList<QQuickItem*> items = parent->findChildren<QQuickItem*>(aName: objectName);
22 for (int i = 0; i < items.size(); ++i) {
23 if (items.at(i)->isVisible() && !QQuickItemPrivate::get(item: items.at(i))->culled) {
24 item = items.at(i);
25 break;
26 }
27 }
28 return item;
29}
30
31void QQuickVisualTestUtils::dumpTree(QQuickItem *parent, int depth)
32{
33 static QString padding = QStringLiteral(" ");
34 for (int i = 0; i < parent->childItems().size(); ++i) {
35 QQuickItem *item = qobject_cast<QQuickItem*>(o: parent->childItems().at(i));
36 if (!item)
37 continue;
38 qDebug() << padding.left(n: depth*2) << item;
39 dumpTree(parent: item, depth: depth+1);
40 }
41}
42
43void QQuickVisualTestUtils::moveMouseAway(QQuickWindow *window)
44{
45#if QT_CONFIG(cursor) // Get the cursor out of the way.
46 // Using "bottomRight() + QPoint(100, 100)" was causing issues on Ubuntu,
47 // where the window was positioned at the bottom right corner of the window
48 // (even after centering the window on the screen), so we use another position.
49 QCursor::setPos(window->frameGeometry().bottomLeft() + QPoint(-10, 10));
50#endif
51
52 // make sure hover events from QQuickDeliveryAgentPrivate::flushFrameSynchronousEvents()
53 // do not interfere with the tests
54 QEvent leave(QEvent::Leave);
55 QCoreApplication::sendEvent(receiver: window, event: &leave);
56}
57
58void QQuickVisualTestUtils::centerOnScreen(QQuickWindow *window)
59{
60 const QRect screenGeometry = window->screen()->availableGeometry();
61 const QPoint offset = QPoint(window->width() / 2, window->height() / 2);
62 window->setFramePosition(screenGeometry.center() - offset);
63}
64
65QPoint QQuickVisualTestUtils::lerpPoints(const QPoint &point1, const QPoint &point2, qreal t)
66{
67 return QPoint(_q_interpolate(f: point1.x(), t: point2.x(), progress: t), _q_interpolate(f: point1.y(), t: point2.y(), progress: t));
68};
69
70/*!
71 \internal
72
73 Convenience class to linearly interpolate between two pointer move points.
74
75 \code
76 PointLerper pointLerper(window);
77 // Lerps from {0, 0} to {15, 15}.
78 pointLerper.move(15, 15);
79 QVERIFY(parentButton->isHovered());
80
81 // Lerps from {15, 15} to {25, 25}.
82 pointLerper.move(25, 25);
83 QVERIFY(childButton->isHovered());
84 \endcode
85*/
86QQuickVisualTestUtils::PointLerper::PointLerper(QQuickWindow *window, const QPointingDevice *pointingDevice)
87 : mWindow(window)
88 , mPointingDevice(pointingDevice)
89{
90}
91
92/*!
93 \internal
94
95 Moves from the last pos (or {0, 0} if there have been no calls
96 to this function yet) to \a pos using linear interpolation
97 over 10 (default value) steps with 1 ms (default value) delays
98 between each step.
99*/
100void QQuickVisualTestUtils::PointLerper::move(const QPoint &pos, int steps, int delayInMilliseconds)
101{
102 forEachStep(steps, func: [&](qreal progress) {
103 QQuickTest::pointerMove(dev: mPointingDevice, window: mWindow, pointId: 0, p: lerpPoints(point1: mFrom, point2: pos, t: progress));
104 QTest::qWait(ms: delayInMilliseconds);
105 });
106 mFrom = pos;
107};
108
109void QQuickVisualTestUtils::PointLerper::move(int x, int y, int steps, int delayInMilliseconds)
110{
111 move(pos: QPoint(x, y), steps, delayInMilliseconds);
112};
113
114bool QQuickVisualTestUtils::delegateVisible(QQuickItem *item)
115{
116 return item->isVisible() && !QQuickItemPrivate::get(item)->culled;
117}
118
119/*!
120 \internal
121
122 Compares \a ia with \a ib, returning \c true if the images are equal.
123 If they are not equal, \c false is returned and \a errorMessage is set.
124
125 A custom compare function to avoid issues such as:
126 When running on native Nvidia graphics cards on linux, the
127 distance field glyph pixels have a measurable, but not visible
128 pixel error. This was GT-216 with the ubuntu "nvidia-319" driver package.
129 llvmpipe does not show the same issue.
130*/
131bool QQuickVisualTestUtils::compareImages(const QImage &ia, const QImage &ib, QString *errorMessage)
132{
133 if (ia.size() != ib.size()) {
134 QDebug(errorMessage) << "Images are of different size:" << ia.size() << ib.size()
135 << "DPR:" << ia.devicePixelRatio() << ib.devicePixelRatio();
136 return false;
137 }
138 if (ia.format() != ib.format()) {
139 QDebug(errorMessage) << "Images are of different formats:" << ia.format() << ib.format();
140 return false;
141 }
142
143 int w = ia.width();
144 int h = ia.height();
145 const int tolerance = 5;
146 for (int y=0; y<h; ++y) {
147 const uint *as= (const uint *) ia.constScanLine(y);
148 const uint *bs= (const uint *) ib.constScanLine(y);
149 for (int x=0; x<w; ++x) {
150 uint a = as[x];
151 uint b = bs[x];
152
153 // No tolerance for error in the alpha.
154 if ((a & 0xff000000) != (b & 0xff000000)
155 || qAbs(t: qRed(rgb: a) - qRed(rgb: b)) > tolerance
156 || qAbs(t: qRed(rgb: a) - qRed(rgb: b)) > tolerance
157 || qAbs(t: qRed(rgb: a) - qRed(rgb: b)) > tolerance) {
158 QDebug(errorMessage) << "Mismatch at:" << x << y << ':'
159 << Qt::hex << Qt::showbase << a << b;
160 return false;
161 }
162 }
163 }
164 return true;
165}
166
167#if QT_CONFIG(quick_itemview)
168/*!
169 \internal
170
171 Finds the delegate at \c index belonging to \c itemView, using the given \c flags.
172
173 If the view needs to be polished, the function will wait for it to be done before continuing,
174 and returns \c nullptr if the polish didn't happen.
175*/
176QQuickItem *QQuickVisualTestUtils::findViewDelegateItem(QQuickItemView *itemView, int index, FindViewDelegateItemFlags flags)
177{
178 if (QQuickTest::qIsPolishScheduled(item: itemView)) {
179 if (!QQuickTest::qWaitForPolish(item: itemView)) {
180 qWarning() << "failed to polish" << itemView;
181 return nullptr;
182 }
183 }
184
185 // Do this after the polish, just in case the count changes after a polish...
186 if (index <= -1 || index >= itemView->count()) {
187 qWarning() << "index" << index << "is out of bounds for" << itemView;
188 return nullptr;
189 }
190
191 if (flags.testFlag(flag: FindViewDelegateItemFlag::PositionViewAtIndex))
192 itemView->positionViewAtIndex(index, mode: QQuickItemView::Center);
193
194 return itemView->itemAtIndex(index);
195}
196#endif
197
198QQuickVisualTestUtils::QQuickApplicationHelper::QQuickApplicationHelper(QQmlDataTest *testCase,
199 const QString &testFilePath, const QVariantMap &initialProperties, const QStringList &qmlImportPaths)
200{
201 for (const auto &path : qmlImportPaths)
202 engine.addImportPath(dir: path);
203
204 QQmlComponent component(&engine);
205
206 component.loadUrl(url: testCase->testFileUrl(fileName: testFilePath));
207 QVERIFY2(component.isReady(), qPrintable(component.errorString()));
208 QObject *rootObject = component.createWithInitialProperties(initialProperties);
209 cleanup.reset(other: rootObject);
210 if (component.isError() || !rootObject) {
211 errorMessage = QString::fromUtf8(utf8: "Failed to create window: %1").arg(a: component.errorString()).toUtf8();
212 return;
213 }
214
215 window = qobject_cast<QQuickWindow*>(object: rootObject);
216 if (!window) {
217 errorMessage = QString::fromUtf8(utf8: "Root object %1 must be a QQuickWindow subclass").arg(a: QDebug::toString(object&: window)).toUtf8();
218 return;
219 }
220
221 if (window->isVisible()) {
222 errorMessage = QString::fromUtf8(utf8: "Expected window not to be visible, but it is").toUtf8();
223 return;
224 }
225
226 ready = true;
227}
228
229QQuickVisualTestUtils::MnemonicKeySimulator::MnemonicKeySimulator(QWindow *window)
230 : m_window(window), m_modifiers(Qt::NoModifier)
231{
232}
233
234void QQuickVisualTestUtils::MnemonicKeySimulator::press(Qt::Key key)
235{
236 // QTest::keyPress() but not generating the press event for the modifier key.
237 if (key == Qt::Key_Alt)
238 m_modifiers |= Qt::AltModifier;
239 QTest::simulateEvent(window: m_window, press: true, code: key, modifier: m_modifiers, text: QString(), repeat: false);
240}
241
242void QQuickVisualTestUtils::MnemonicKeySimulator::release(Qt::Key key)
243{
244 // QTest::keyRelease() but not generating the release event for the modifier key.
245 if (key == Qt::Key_Alt)
246 m_modifiers &= ~Qt::AltModifier;
247 QTest::simulateEvent(window: m_window, press: false, code: key, modifier: m_modifiers, text: QString(), repeat: false);
248}
249
250void QQuickVisualTestUtils::MnemonicKeySimulator::click(Qt::Key key)
251{
252 press(key);
253 release(key);
254}
255
256QPoint QQuickVisualTestUtils::mapCenterToWindow(const QQuickItem *item)
257{
258 return item->mapToScene(point: QPointF(item->width() / 2, item->height() / 2)).toPoint();
259}
260
261QPoint QQuickVisualTestUtils::mapToWindow(const QQuickItem *item, qreal relativeX, qreal relativeY)
262{
263 return item->mapToScene(point: QPointF(relativeX, relativeY)).toPoint();
264}
265
266QPoint QQuickVisualTestUtils::mapToWindow(const QQuickItem *item, const QPointF &relativePos)
267{
268 return mapToWindow(item, relativeX: relativePos.x(), relativeY: relativePos.y());
269}
270
271QT_END_NAMESPACE
272
273#include "moc_visualtestutils_p.cpp"
274

source code of qtdeclarative/src/quicktestutils/quick/visualtestutils.cpp