1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
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 QQuickViewTestUtils::centerOnScreen(window);
61}
62
63QPoint QQuickVisualTestUtils::lerpPoints(const QPoint &point1, const QPoint &point2, qreal t)
64{
65 return QPoint(_q_interpolate(f: point1.x(), t: point2.x(), progress: t), _q_interpolate(f: point1.y(), t: point2.y(), progress: t));
66};
67
68/*!
69 \internal
70
71 Convenience class to linearly interpolate between two pointer move points.
72
73 \code
74 PointLerper pointLerper(window);
75 // Lerps from {0, 0} to {15, 15}.
76 pointLerper.move(15, 15);
77 QVERIFY(parentButton->isHovered());
78
79 // Lerps from {15, 15} to {25, 25}.
80 pointLerper.move(25, 25);
81 QVERIFY(childButton->isHovered());
82 \endcode
83*/
84QQuickVisualTestUtils::PointLerper::PointLerper(QQuickWindow *window, const QPointingDevice *pointingDevice)
85 : mWindow(window)
86 , mPointingDevice(pointingDevice)
87{
88}
89
90/*!
91 \internal
92
93 Moves from the last pos (or {0, 0} if there have been no calls
94 to this function yet) to \a pos using linear interpolation
95 over 10 (default value) steps with 1 ms (default value) delays
96 between each step.
97*/
98void QQuickVisualTestUtils::PointLerper::move(const QPoint &pos, int steps, int delayInMilliseconds)
99{
100 forEachStep(steps, func: [&](qreal progress) {
101 QQuickTest::pointerMove(dev: mPointingDevice, window: mWindow, pointId: 0, p: lerpPoints(point1: mFrom, point2: pos, t: progress));
102 QTest::qWait(ms: delayInMilliseconds);
103 });
104 mFrom = pos;
105};
106
107void QQuickVisualTestUtils::PointLerper::move(int x, int y, int steps, int delayInMilliseconds)
108{
109 move(pos: QPoint(x, y), steps, delayInMilliseconds);
110};
111
112/*!
113 \internal
114
115 Returns \c true if \c {item->isVisible()} returns \c true, and
116 the item is not culled.
117*/
118bool QQuickVisualTestUtils::isDelegateVisible(QQuickItem *item)
119{
120 return item->isVisible() && !QQuickItemPrivate::get(item)->culled;
121}
122
123/*!
124 \internal
125
126 Compares \a ia with \a ib, returning \c true if the images are equal.
127 If they are not equal, \c false is returned and \a errorMessage is set.
128
129 A custom compare function to avoid issues such as:
130 When running on native Nvidia graphics cards on linux, the
131 distance field glyph pixels have a measurable, but not visible
132 pixel error. This was GT-216 with the ubuntu "nvidia-319" driver package.
133 llvmpipe does not show the same issue.
134*/
135bool QQuickVisualTestUtils::compareImages(const QImage &ia, const QImage &ib, QString *errorMessage)
136{
137 if (ia.size() != ib.size()) {
138 QDebug(errorMessage) << "Images are of different size:" << ia.size() << ib.size()
139 << "DPR:" << ia.devicePixelRatio() << ib.devicePixelRatio();
140 return false;
141 }
142 if (ia.format() != ib.format()) {
143 QDebug(errorMessage) << "Images are of different formats:" << ia.format() << ib.format();
144 return false;
145 }
146 if (ia.depth() != 32) {
147 QDebug(errorMessage) << "This function only supports bit depths of 32 - depth of images is:" << ia.depth();
148 return false;
149 }
150
151 int w = ia.width();
152 int h = ia.height();
153 const int tolerance = 5;
154 for (int y=0; y<h; ++y) {
155 const uint *as= (const uint *) ia.constScanLine(y);
156 const uint *bs= (const uint *) ib.constScanLine(y);
157 for (int x=0; x<w; ++x) {
158 uint a = as[x];
159 uint b = bs[x];
160
161 // No tolerance for error in the alpha.
162 if ((a & 0xff000000) != (b & 0xff000000)
163 || qAbs(t: qRed(rgb: a) - qRed(rgb: b)) > tolerance
164 || qAbs(t: qGreen(rgb: a) - qGreen(rgb: b)) > tolerance
165 || qAbs(t: qBlue(rgb: a) - qBlue(rgb: b)) > tolerance) {
166 QDebug(errorMessage) << "Mismatch at:" << x << y << ':'
167 << Qt::hex << Qt::showbase << a << b;
168 return false;
169 }
170 }
171 }
172 return true;
173}
174
175#if QT_CONFIG(quick_itemview)
176/*!
177 \internal
178
179 Finds the delegate at \c index belonging to \c itemView, using the given \c flags.
180
181 If the view needs to be polished, the function will wait for it to be done before continuing,
182 and returns \c nullptr if the polish didn't happen.
183*/
184QQuickItem *QQuickVisualTestUtils::findViewDelegateItem(QQuickItemView *itemView, int index, FindViewDelegateItemFlags flags)
185{
186 if (QQuickTest::qIsPolishScheduled(item: itemView)) {
187 if (!QQuickTest::qWaitForPolish(item: itemView)) {
188 qWarning() << "failed to polish" << itemView;
189 return nullptr;
190 }
191 }
192
193 // Do this after the polish, just in case the count changes after a polish...
194 if (index <= -1 || index >= itemView->count()) {
195 qWarning() << "index" << index << "is out of bounds for" << itemView;
196 return nullptr;
197 }
198
199 if (flags.testFlag(flag: FindViewDelegateItemFlag::PositionViewAtIndex))
200 itemView->positionViewAtIndex(index, mode: QQuickItemView::Center);
201
202 return itemView->itemAtIndex(index);
203}
204#endif
205
206QQuickVisualTestUtils::QQuickApplicationHelper::QQuickApplicationHelper(QQmlDataTest *testCase,
207 const QString &testFilePath, const QVariantMap &initialProperties, const QStringList &qmlImportPaths)
208{
209 for (const auto &path : qmlImportPaths)
210 engine.addImportPath(dir: path);
211
212 QQmlComponent component(&engine);
213
214 component.loadUrl(url: testCase->testFileUrl(fileName: testFilePath));
215 QVERIFY2(component.isReady(), qPrintable(component.errorString()));
216 QObject *rootObject = component.createWithInitialProperties(initialProperties);
217 cleanup.reset(other: rootObject);
218 if (component.isError() || !rootObject) {
219 errorMessage = QString::fromUtf8(utf8: "Failed to create window: %1").arg(a: component.errorString()).toUtf8();
220 return;
221 }
222
223 window = qobject_cast<QQuickWindow*>(object: rootObject);
224 if (!window) {
225 errorMessage = QString::fromUtf8(utf8: "Root object %1 must be a QQuickWindow subclass").arg(a: QDebug::toString(object: window)).toUtf8();
226 return;
227 }
228
229 if (window->isVisible()) {
230 errorMessage = QString::fromUtf8(utf8: "Expected window not to be visible, but it is").toUtf8();
231 return;
232 }
233
234 ready = true;
235}
236
237QQuickVisualTestUtils::MnemonicKeySimulator::MnemonicKeySimulator(QWindow *window)
238 : m_window(window), m_modifiers(Qt::NoModifier)
239{
240}
241
242void QQuickVisualTestUtils::MnemonicKeySimulator::press(Qt::Key key)
243{
244 // QTest::keyPress() but not generating the press event for the modifier key.
245 if (key == Qt::Key_Alt)
246 m_modifiers |= Qt::AltModifier;
247 QTest::simulateEvent(window: m_window, press: true, code: key, modifier: m_modifiers, text: QString(), repeat: false);
248}
249
250void QQuickVisualTestUtils::MnemonicKeySimulator::release(Qt::Key key)
251{
252 // QTest::keyRelease() but not generating the release event for the modifier key.
253 if (key == Qt::Key_Alt)
254 m_modifiers &= ~Qt::AltModifier;
255 QTest::simulateEvent(window: m_window, press: false, code: key, modifier: m_modifiers, text: QString(), repeat: false);
256}
257
258void QQuickVisualTestUtils::MnemonicKeySimulator::click(Qt::Key key)
259{
260 press(key);
261 release(key);
262}
263
264QPoint QQuickVisualTestUtils::mapCenterToWindow(const QQuickItem *item)
265{
266 return item->mapToScene(point: QPointF(item->width() / 2, item->height() / 2)).toPoint();
267}
268
269QPoint QQuickVisualTestUtils::mapToWindow(const QQuickItem *item, qreal relativeX, qreal relativeY)
270{
271 return item->mapToScene(point: QPointF(relativeX, relativeY)).toPoint();
272}
273
274QPoint QQuickVisualTestUtils::mapToWindow(const QQuickItem *item, const QPointF &relativePos)
275{
276 return mapToWindow(item, relativeX: relativePos.x(), relativeY: relativePos.y());
277}
278
279QT_END_NAMESPACE
280
281#include "moc_visualtestutils_p.cpp"
282

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