on bd49d29 help sign in
unity8/tests/qmltests/Components/tst_DragHandle.cpp
489 lines 16 kB history raw
1/*
2 * Copyright (C) 2013-2014 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17#include <functional>
18#include <QtTest/QtTest>
19#include <QtCore/QObject>
20#include <qpa/qwindowsysteminterface.h>
21#include <QtQuick/QQuickView>
22#include <QtQml/QQmlEngine>
24#include <DirectionalDragArea.h>
25#include <Timer.h>
26#include <TouchRegistry.h>
28using namespace UbuntuGestures;
30class FakeTimeSource : public TimeSource
32public:
33 FakeTimeSource() { m_msecsSinceReference = 0; }
34 qint64 msecsSinceReference() override {return m_msecsSinceReference;}
35 qint64 m_msecsSinceReference;
36};
38class tst_DragHandle: public QObject
40 Q_OBJECT
41public:
42 tst_DragHandle() : m_device(0) { }
43private Q_SLOTS:
44 void initTestCase(); // will be called before the first test function is executed
45 void cleanupTestCase(); // will be called after the last test function was executed.
47 void init(); // called right before each and every test function is executed
48 void cleanup(); // called right after each and every test function is executed
50 void dragThreshold_horizontal();
51 void dragThreshold_horizontal_data();
52 void dragThreshold_vertical();
53 void dragThreshold_vertical_data();
54 void stretch_horizontal();
55 void stretch_vertical();
56 void hintingAnimation();
57 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
60private:
61 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
62 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
63 int numSteps, qint64 timeMs = 500);
64 DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName);
65 qreal fetchDragThreshold(DirectionalDragArea *dragHandle);
66 void tryCompare(std::function<qreal ()> actualFunc, qreal expectedValue);
68 QQuickView *createView();
69 QQuickView *m_view;
70 TouchRegistry *m_touchRegistry;
71 QTouchDevice *m_device;
72 FakeTimer *m_fakeTimer;
73 QSharedPointer<FakeTimeSource> m_fakeTimeSource;
74};
77void tst_DragHandle::initTestCase()
79 if (!m_device) {
80 m_device = new QTouchDevice;
81 m_device->setType(QTouchDevice::TouchScreen);
82 QWindowSystemInterface::registerTouchDevice(m_device);
83 }
85 m_view = 0;
88void tst_DragHandle::cleanupTestCase()
92void tst_DragHandle::init()
94 m_touchRegistry = new TouchRegistry;
96 m_view = createView();
97 m_view->installEventFilter(m_touchRegistry);
98 m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
99 m_view->show();
100 QVERIFY(QTest::qWaitForWindowExposed(m_view));
101 QVERIFY(m_view->rootObject() != 0);
102 qApp->processEvents();
104 // Hide the controls to ensure we don't hit them accidentally
105 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
106 controls->setVisible(false);
108 m_fakeTimer = new FakeTimer;
109 m_fakeTimeSource.reset(new FakeTimeSource);
112void tst_DragHandle::cleanup()
114 delete m_view;
115 m_view = 0;
117 delete m_touchRegistry;
118 m_touchRegistry = 0;
120 delete m_fakeTimer;
121 m_fakeTimer = 0;
123 m_fakeTimeSource.reset();
126QQuickView *tst_DragHandle::createView()
128 QQuickView *window = new QQuickView(0);
129 window->setResizeMode(QQuickView::SizeRootObjectToView);
130 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
131 window->engine()->addImportPath(QLatin1String(TEST_DIR));
133 return window;
136void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
137 qreal expectedValue)
139 int waitCount = 0;
140 while (actualFunc() != expectedValue && waitCount < 100) {
141 QTest::qWait(50);
142 ++waitCount;
143 }
144 QCOMPARE(actualFunc(), expectedValue);
147namespace {
148QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
150 QPointF localOrigin(0., 0.);
151 QPointF localDirection;
152 switch (edgeDragArea->direction()) {
153 case Direction::Upwards:
154 localDirection.rx() = 0.;
155 localDirection.ry() = -1.;
156 break;
157 case Direction::Downwards:
158 localDirection.rx() = 0.;
159 localDirection.ry() = 1;
160 break;
161 case Direction::Leftwards:
162 localDirection.rx() = -1.;
163 localDirection.ry() = 0.;
164 break;
165 default: // Direction::Rightwards:
166 localDirection.rx() = 1.;
167 localDirection.ry() = 0.;
168 break;
169 }
170 QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
171 QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
172 return sceneDirection - sceneOrigin;
176void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
177 qreal distance)
179 QPointF initialTouchPos = dragHandle->mapToScene(
180 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
181 QPointF touchPoint = initialTouchPos;
183 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
185 int numSteps = 10;
186 QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
187 drag(touchPoint, dragDirectionVector, distance, numSteps);
189 // Wait for quite a bit before finally releasing to make a very low flick/release
190 // speed.
191 m_fakeTimeSource->m_msecsSinceReference += 5000;
192 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
195void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
196 int numSteps, qint64 timeMs)
198 qint64 timeStep = timeMs / numSteps;
199 QPointF touchMovement = direction * (distance / (qreal)numSteps);
200 for (int i = 0; i < numSteps; ++i) {
201 touchPoint += touchMovement;
202 m_fakeTimeSource->m_msecsSinceReference += timeStep;
203 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
204 }
207DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
209 DirectionalDragArea *dragHandle =
210 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
211 Q_ASSERT(dragHandle != 0);
212 dragHandle->setRecognitionTimer(m_fakeTimer);
213 dragHandle->setTimeSource(m_fakeTimeSource);
215 AxisVelocityCalculator *edgeDragEvaluator =
216 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
217 Q_ASSERT(edgeDragEvaluator != 0);
218 edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
220 return dragHandle;
223qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
225 AxisVelocityCalculator *edgeDragEvaluator =
226 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
227 Q_ASSERT(edgeDragEvaluator != 0);
229 return edgeDragEvaluator->property("dragThreshold").toReal();
232/*
233 Checks that ending a low-speed drag before dragThreshold results in the
234 Showable getting back to its original position, whereas ending after dragThreshold
235 results in Showable continuing until reaching its new states (shown or hidden)
236 */
237void tst_DragHandle::dragThreshold_horizontal()
239 QFETCH(qreal, rotation);
241 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
242 baseItem->setRotation(rotation);
244 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
246 qreal dragThreshold = fetchDragThreshold(dragHandle);
248 // end before the threshold
249 flickAndHold(dragHandle, dragThreshold * 0.7);
251 // should rollback
252 QQuickItem *parentItem = dragHandle->parentItem();
253 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
254 QCOMPARE(parentItem->property("shown").toBool(), false);
256 // end after the threshold
257 flickAndHold(dragHandle, dragThreshold * 1.2);
259 // should keep going until completion
260 tryCompare([&](){ return parentItem->x(); }, 0);
261 QCOMPARE(parentItem->property("shown").toBool(), true);
263 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
265 dragThreshold = fetchDragThreshold(dragHandle);
267 // end before the threshold
268 flickAndHold(dragHandle, dragThreshold * 0.7);
270 // should rollback
271 tryCompare([&](){ return parentItem->x(); }, 0);
272 QCOMPARE(parentItem->property("shown").toBool(), true);
274 // end after the threshold
275 flickAndHold(dragHandle, dragThreshold * 1.2);
277 // should keep going until completion
278 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
279 QCOMPARE(parentItem->property("shown").toBool(), false);
282void tst_DragHandle::dragThreshold_horizontal_data()
284 QTest::addColumn<qreal>("rotation");
286 QTest::newRow("not rotated") << 0.;
287 QTest::newRow("rotated 90") << 90.;
290void tst_DragHandle::dragThreshold_vertical()
292 QFETCH(qreal, rotation);
294 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
295 baseItem->setRotation(rotation);
297 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
299 qreal dragThreshold = fetchDragThreshold(dragHandle);
301 // end before the threshold
302 flickAndHold(dragHandle, dragThreshold * 0.7);
304 // should rollback
305 QQuickItem *parentItem = dragHandle->parentItem();
306 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
307 QCOMPARE(parentItem->property("shown").toBool(), false);
309 // end after the threshold
310 flickAndHold(dragHandle, dragThreshold * 1.2);
312 // should keep going until completion
313 tryCompare([&](){ return parentItem->y(); }, 0);
314 QCOMPARE(parentItem->property("shown").toBool(), true);
316 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
318 dragThreshold = fetchDragThreshold(dragHandle);
320 // end before the threshold
321 flickAndHold(dragHandle, dragThreshold * 0.7);
323 // should rollback
324 tryCompare([&](){ return parentItem->y(); }, 0);
325 QCOMPARE(parentItem->property("shown").toBool(), true);
327 // end after the threshold
328 flickAndHold(dragHandle, dragThreshold * 1.2);
330 // should keep going until completion
331 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
332 QCOMPARE(parentItem->property("shown").toBool(), false);
335void tst_DragHandle::dragThreshold_vertical_data()
337 QTest::addColumn<qreal>("rotation");
339 QTest::newRow("not rotated") << 0.;
340 QTest::newRow("rotated 90") << 90.;
343/*
344 Checks that when the stretch property is true, dragging the DragHandle increases
345 the width or height (depending on its direction) of its parent Showable
346 */
347void tst_DragHandle::stretch_horizontal()
349 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
350 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
351 QQuickItem *parentItem = dragHandle->parentItem();
353 // enable strech mode
354 m_view->rootObject()->setProperty("stretch", QVariant(true));
356 QCOMPARE(parentItem->width(), 0.0);
358 // flick all the way
359 flickAndHold(dragHandle, totalDragDistance);
361 // should keep going until completion
362 // Parent item should now have its full height
363 tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
364 QCOMPARE(parentItem->property("shown").toBool(), true);
366 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
368 // flick all the way
369 flickAndHold(dragHandle, totalDragDistance);
371 // should keep going until completion
372 // Parent item should now have its full height
373 tryCompare([&](){ return parentItem->width(); }, 0.0);
374 QCOMPARE(parentItem->property("shown").toBool(), false);
377void tst_DragHandle::stretch_vertical()
379 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
380 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
381 QQuickItem *parentItem = dragHandle->parentItem();
383 // enable strech mode
384 m_view->rootObject()->setProperty("stretch", QVariant(true));
386 QCOMPARE(parentItem->height(), 0.0);
388 // flick all the way
389 flickAndHold(dragHandle, totalDragDistance);
391 // should keep going until completion
392 // Parent item should now have its full height
393 tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
394 QCOMPARE(parentItem->property("shown").toBool(), true);
396 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
398 // flick all the way
399 flickAndHold(dragHandle, totalDragDistance);
401 // should keep going until completion
402 // Parent item should now have its full height
403 tryCompare([&](){ return parentItem->height(); }, 0.0);
404 QCOMPARE(parentItem->property("shown").toBool(), false);
407/*
408 Set DragHandle.hintDisplacement to a value bigger than zero.
409 Then lay a finger on the DragHandle.
410 The expected behavior is that it will move or strech its parent Showable
411 by hintDisplacement pixels.
412 */
413void tst_DragHandle::hintingAnimation()
415 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
416 QQuickItem *parentItem = dragHandle->parentItem();
417 qreal hintDisplacement = 100.0;
419 // enable hinting animations and stretch mode
420 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
421 m_view->rootObject()->setProperty("stretch", QVariant(true));
423 QCOMPARE(parentItem->height(), 0.0);
425 QPointF initialTouchPos = dragHandle->mapToScene(
426 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
427 QPointF touchPoint = initialTouchPos;
429 // Pressing causes the Showable to be stretched by hintDisplacement pixels
430 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
431 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
433 // Releasing causes the Showable to shrink back to 0 pixels.
434 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
435 tryCompare([&](){ return parentItem->height(); }, 0.0);
437 QCOMPARE(parentItem->property("shown").toBool(), false);
440/*
441 Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022
443 1) Click on handle.
444 2) wait for hint portion to appear
445 3) slowly drag handle, only a few pixels
447 Expected outcome:
448 Nothing happens
450 Actual outcome:
451 Handle will momentarily move back to zero position, then back down to the
452 hint displacement location.
453 */
454void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed()
456 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
457 QQuickItem *parentItem = dragHandle->parentItem();
458 qreal hintDisplacement = 100.0;
460 // enable hinting animations
461 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
462 m_view->rootObject()->setProperty("stretch", QVariant(true));
464 QCOMPARE(parentItem->height(), 0.0);
466 QPointF initialTouchPos = dragHandle->mapToScene(
467 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
468 QPointF touchPoint = initialTouchPos;
470 // Pressing causes the Showable to be stretched by hintDisplacement pixels
471 const int touchId = 0;
472 QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint());
473 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
476 QSignalSpy parentHeightChangedSpy(parentItem, SIGNAL(heightChanged()));
478 drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/);
480 // Give some time for animations to run, if any
481 QTest::qWait(300);
483 // parentItem height shouldn't have changed at all
484 QVERIFY(parentHeightChangedSpy.isEmpty());
487QTEST_MAIN(tst_DragHandle)
489#include "tst_DragHandle.moc"