on cc17f86 help sign in
unity8/tests/qmltests/Components/tst_DragHandle.cpp
491 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>
23#include <private/qquickwindow_p.h>
25#include <AxisVelocityCalculator.h>
26#include <DirectionalDragArea.h>
27#include <DirectionalDragArea_p.h>
28#include <Timer.h>
29#include <TouchRegistry.h>
31using namespace UbuntuGestures;
33class tst_DragHandle: public QObject
35 Q_OBJECT
36public:
37 tst_DragHandle() : m_device(0) { }
38private Q_SLOTS:
39 void initTestCase(); // will be called before the first test function is executed
40 void cleanupTestCase(); // will be called after the last test function was executed.
42 void init(); // called right before each and every test function is executed
43 void cleanup(); // called right after each and every test function is executed
45 void dragThreshold_horizontal();
46 void dragThreshold_horizontal_data();
47 void dragThreshold_vertical();
48 void dragThreshold_vertical_data();
49 void stretch_horizontal();
50 void stretch_vertical();
51 void hintingAnimation();
52 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
54private:
55 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
56 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
57 int numSteps, qint64 timeMs = 500);
58 DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName);
59 qreal fetchDragThreshold(DirectionalDragArea *dragHandle);
60 void tryCompare(std::function<qreal ()> actualFunc, qreal expectedValue);
62 QQuickView *createView();
63 QQuickView *m_view;
64 TouchRegistry *m_touchRegistry;
65 QTouchDevice *m_device;
66 FakeTimer *m_fakeTimer;
67 QSharedPointer<FakeTimeSource> m_fakeTimeSource;
68};
71void tst_DragHandle::initTestCase()
73 if (!m_device) {
74 m_device = new QTouchDevice;
75 m_device->setType(QTouchDevice::TouchScreen);
76 QWindowSystemInterface::registerTouchDevice(m_device);
77 }
79 m_view = 0;
82void tst_DragHandle::cleanupTestCase()
86void tst_DragHandle::init()
88 m_touchRegistry = new TouchRegistry;
90 m_view = createView();
91 m_view->installEventFilter(m_touchRegistry);
92 m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
93 m_view->show();
94 QVERIFY(QTest::qWaitForWindowExposed(m_view));
95 QVERIFY(m_view->rootObject() != 0);
96 qApp->processEvents();
98 // Hide the controls to ensure we don't hit them accidentally
99 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
100 controls->setVisible(false);
102 m_fakeTimeSource.reset(new FakeTimeSource);
103 m_fakeTimer = new FakeTimer(m_fakeTimeSource);
106void tst_DragHandle::cleanup()
108 delete m_view;
109 m_view = 0;
111 delete m_touchRegistry;
112 m_touchRegistry = 0;
114 delete m_fakeTimer;
115 m_fakeTimer = 0;
117 m_fakeTimeSource.reset();
120QQuickView *tst_DragHandle::createView()
122 QQuickView *window = new QQuickView(0);
123 window->setResizeMode(QQuickView::SizeRootObjectToView);
124 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
125 window->engine()->addImportPath(QLatin1String(TEST_DIR));
126 window->engine()->addImportPath(QStringLiteral(TESTS_UTILS_MODULES_DIR));
128 return window;
131void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
132 qreal expectedValue)
134 int waitCount = 0;
135 while (actualFunc() != expectedValue && waitCount < 100) {
136 QTest::qWait(50);
137 ++waitCount;
138 }
139 QCOMPARE(actualFunc(), expectedValue);
142namespace {
143QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
145 QPointF localOrigin(0., 0.);
146 QPointF localDirection;
147 switch (edgeDragArea->direction()) {
148 case Direction::Upwards:
149 localDirection.rx() = 0.;
150 localDirection.ry() = -1.;
151 break;
152 case Direction::Downwards:
153 localDirection.rx() = 0.;
154 localDirection.ry() = 1;
155 break;
156 case Direction::Leftwards:
157 localDirection.rx() = -1.;
158 localDirection.ry() = 0.;
159 break;
160 default: // Direction::Rightwards:
161 localDirection.rx() = 1.;
162 localDirection.ry() = 0.;
163 break;
164 }
165 QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
166 QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
167 return sceneDirection - sceneOrigin;
171void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
172 qreal distance)
174 QPointF initialTouchPos = dragHandle->mapToScene(
175 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
176 QPointF touchPoint = initialTouchPos;
178 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
180 int numSteps = 10;
181 QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
182 drag(touchPoint, dragDirectionVector, distance, numSteps);
184 // Wait for quite a bit before finally releasing to make a very low flick/release
185 // speed.
186 m_fakeTimeSource->m_msecsSinceReference += 5000;
187 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
189 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
190 windowPrivate->flushDelayedTouchEvent();
193void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
194 int numSteps, qint64 timeMs)
196 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
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());
205 windowPrivate->flushDelayedTouchEvent();
206 }
209DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
211 DirectionalDragArea *dragHandle =
212 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
213 Q_ASSERT(dragHandle != 0);
214 dragHandle->d->setRecognitionTimer(m_fakeTimer);
215 dragHandle->d->setTimeSource(m_fakeTimeSource);
217 AxisVelocityCalculator *edgeDragEvaluator =
218 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
219 Q_ASSERT(edgeDragEvaluator != 0);
220 edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
222 return dragHandle;
225qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
227 AxisVelocityCalculator *edgeDragEvaluator =
228 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
229 Q_ASSERT(edgeDragEvaluator != 0);
231 return edgeDragEvaluator->property("dragThreshold").toReal();
234/*
235 Checks that ending a low-speed drag before dragThreshold results in the
236 Showable getting back to its original position, whereas ending after dragThreshold
237 results in Showable continuing until reaching its new states (shown or hidden)
238 */
239void tst_DragHandle::dragThreshold_horizontal()
241 QFETCH(qreal, rotation);
243 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
244 baseItem->setRotation(rotation);
246 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
247 QQuickItem *parentItem = dragHandle->parentItem();
249 qreal dragThreshold = fetchDragThreshold(dragHandle);
251 // end before the threshold
252 flickAndHold(dragHandle, dragThreshold * 0.7);
254 // should rollback
255 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
256 QCOMPARE(parentItem->property("shown").toBool(), false);
258 // end after the threshold
259 flickAndHold(dragHandle, dragThreshold * 1.2);
261 // should keep going until completion
262 tryCompare([&](){ return parentItem->x(); }, 0);
263 QCOMPARE(parentItem->property("shown").toBool(), true);
265 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
267 dragThreshold = fetchDragThreshold(dragHandle);
269 // end before the threshold
270 flickAndHold(dragHandle, dragThreshold * 0.7);
272 // should rollback
273 tryCompare([&](){ return parentItem->x(); }, 0);
274 QCOMPARE(parentItem->property("shown").toBool(), true);
276 // end after the threshold
277 flickAndHold(dragHandle, dragThreshold * 1.2);
279 // should keep going until completion
280 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
281 QCOMPARE(parentItem->property("shown").toBool(), false);
284void tst_DragHandle::dragThreshold_horizontal_data()
286 QTest::addColumn<qreal>("rotation");
288 QTest::newRow("not rotated") << 0.;
289 QTest::newRow("rotated 90") << 90.;
292void tst_DragHandle::dragThreshold_vertical()
294 QFETCH(qreal, rotation);
296 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
297 baseItem->setRotation(rotation);
299 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
301 qreal dragThreshold = fetchDragThreshold(dragHandle);
303 // end before the threshold
304 flickAndHold(dragHandle, dragThreshold * 0.7);
306 // should rollback
307 QQuickItem *parentItem = dragHandle->parentItem();
308 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
309 QCOMPARE(parentItem->property("shown").toBool(), false);
311 // end after the threshold
312 flickAndHold(dragHandle, dragThreshold * 1.2);
314 // should keep going until completion
315 tryCompare([&](){ return parentItem->y(); }, 0);
316 QCOMPARE(parentItem->property("shown").toBool(), true);
318 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
320 dragThreshold = fetchDragThreshold(dragHandle);
322 // end before the threshold
323 flickAndHold(dragHandle, dragThreshold * 0.7);
325 // should rollback
326 tryCompare([&](){ return parentItem->y(); }, 0);
327 QCOMPARE(parentItem->property("shown").toBool(), true);
329 // end after the threshold
330 flickAndHold(dragHandle, dragThreshold * 1.2);
332 // should keep going until completion
333 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
334 QCOMPARE(parentItem->property("shown").toBool(), false);
337void tst_DragHandle::dragThreshold_vertical_data()
339 QTest::addColumn<qreal>("rotation");
341 QTest::newRow("not rotated") << 0.;
342 QTest::newRow("rotated 90") << 90.;
345/*
346 Checks that when the stretch property is true, dragging the DragHandle increases
347 the width or height (depending on its direction) of its parent Showable
348 */
349void tst_DragHandle::stretch_horizontal()
351 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
352 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
353 QQuickItem *parentItem = dragHandle->parentItem();
355 // enable strech mode
356 m_view->rootObject()->setProperty("stretch", QVariant(true));
358 QCOMPARE(parentItem->width(), 0.0);
360 // flick all the way
361 flickAndHold(dragHandle, totalDragDistance);
363 // should keep going until completion
364 // Parent item should now have its full height
365 tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
366 QCOMPARE(parentItem->property("shown").toBool(), true);
368 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
370 // flick all the way
371 flickAndHold(dragHandle, totalDragDistance);
373 // should keep going until completion
374 // Parent item should now have its full height
375 tryCompare([&](){ return parentItem->width(); }, 0.0);
376 QCOMPARE(parentItem->property("shown").toBool(), false);
379void tst_DragHandle::stretch_vertical()
381 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
382 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
383 QQuickItem *parentItem = dragHandle->parentItem();
385 // enable strech mode
386 m_view->rootObject()->setProperty("stretch", QVariant(true));
388 QCOMPARE(parentItem->height(), 0.0);
390 // flick all the way
391 flickAndHold(dragHandle, totalDragDistance);
393 // should keep going until completion
394 // Parent item should now have its full height
395 tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
396 QCOMPARE(parentItem->property("shown").toBool(), true);
398 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
400 // flick all the way
401 flickAndHold(dragHandle, totalDragDistance);
403 // should keep going until completion
404 // Parent item should now have its full height
405 tryCompare([&](){ return parentItem->height(); }, 0.0);
406 QCOMPARE(parentItem->property("shown").toBool(), false);
409/*
410 Set DragHandle.hintDisplacement to a value bigger than zero.
411 Then lay a finger on the DragHandle.
412 The expected behavior is that it will move or strech its parent Showable
413 by hintDisplacement pixels.
414 */
415void tst_DragHandle::hintingAnimation()
417 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
418 QQuickItem *parentItem = dragHandle->parentItem();
419 qreal hintDisplacement = 100.0;
421 // enable hinting animations and stretch mode
422 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
423 m_view->rootObject()->setProperty("stretch", QVariant(true));
425 QCOMPARE(parentItem->height(), 0.0);
427 QPointF initialTouchPos = dragHandle->mapToScene(
428 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
429 QPointF touchPoint = initialTouchPos;
431 // Pressing causes the Showable to be stretched by hintDisplacement pixels
432 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
433 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
435 // Releasing causes the Showable to shrink back to 0 pixels.
436 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
437 tryCompare([&](){ return parentItem->height(); }, 0.0);
439 QCOMPARE(parentItem->property("shown").toBool(), false);
442/*
443 Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022
445 1) Click on handle.
446 2) wait for hint portion to appear
447 3) slowly drag handle, only a few pixels
449 Expected outcome:
450 Nothing happens
452 Actual outcome:
453 Handle will momentarily move back to zero position, then back down to the
454 hint displacement location.
455 */
456void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed()
458 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
459 QQuickItem *parentItem = dragHandle->parentItem();
460 qreal hintDisplacement = 100.0;
462 // enable hinting animations
463 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
464 m_view->rootObject()->setProperty("stretch", QVariant(true));
466 QCOMPARE(parentItem->height(), 0.0);
468 QPointF initialTouchPos = dragHandle->mapToScene(
469 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
470 QPointF touchPoint = initialTouchPos;
472 // Pressing causes the Showable to be stretched by hintDisplacement pixels
473 const int touchId = 0;
474 QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint());
475 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
478 QSignalSpy parentHeightChangedSpy(parentItem, SIGNAL(heightChanged()));
480 drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/);
482 // Give some time for animations to run, if any
483 QTest::qWait(300);
485 // parentItem height shouldn't have changed at all
486 QVERIFY(parentHeightChangedSpy.isEmpty());
489QTEST_MAIN(tst_DragHandle)
491#include "tst_DragHandle.moc"