on dbb8563 help sign in
unity8/tests/qmltests/Components/tst_DragHandle.cpp
483 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>
30#include <paths.h>
32using namespace UbuntuGestures;
34class tst_DragHandle: public QObject
36 Q_OBJECT
37public:
38 tst_DragHandle() : m_device(0) { }
39private Q_SLOTS:
40 void initTestCase(); // will be called before the first test function is executed
41 void cleanupTestCase(); // will be called after the last test function was executed.
43 void init(); // called right before each and every test function is executed
44 void cleanup(); // called right after each and every test function is executed
46 void dragThreshold_horizontal();
47 void dragThreshold_horizontal_data();
48 void dragThreshold_vertical();
49 void dragThreshold_vertical_data();
50 void stretch_horizontal();
51 void stretch_vertical();
52 void hintingAnimation();
53 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
55private:
56 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
57 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
58 int numSteps, qint64 timeMs = 500);
59 DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName);
60 qreal fetchDragThreshold(DirectionalDragArea *dragHandle);
61 void tryCompare(std::function<qreal ()> actualFunc, qreal expectedValue);
63 QQuickView *createView();
64 QQuickView *m_view;
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_view = createView();
89 m_view->setSource(QUrl::fromLocalFile(testDataDir() + "/" TEST_DIR "/tst_DragHandle.qml"));
90 m_view->show();
91 QVERIFY(QTest::qWaitForWindowExposed(m_view));
92 QVERIFY(m_view->rootObject() != 0);
93 qApp->processEvents();
95 // Hide the controls to ensure we don't hit them accidentally
96 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
97 controls->setVisible(false);
99 m_fakeTimeSource.reset(new FakeTimeSource);
100 m_fakeTimer = new FakeTimer(m_fakeTimeSource);
103void tst_DragHandle::cleanup()
105 delete m_view;
106 m_view = 0;
108 delete m_fakeTimer;
109 m_fakeTimer = 0;
111 m_fakeTimeSource.reset();
114QQuickView *tst_DragHandle::createView()
116 QQuickView *window = new QQuickView(0);
117 window->setResizeMode(QQuickView::SizeRootObjectToView);
118 window->engine()->addImportPath(testDataDir() + "/" TEST_DIR);
120 return window;
123void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
124 qreal expectedValue)
126 int waitCount = 0;
127 while (actualFunc() != expectedValue && waitCount < 100) {
128 QTest::qWait(50);
129 ++waitCount;
130 }
131 QCOMPARE(actualFunc(), expectedValue);
134namespace {
135QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
137 QPointF localOrigin(0., 0.);
138 QPointF localDirection;
139 switch (edgeDragArea->direction()) {
140 case Direction::Upwards:
141 localDirection.rx() = 0.;
142 localDirection.ry() = -1.;
143 break;
144 case Direction::Downwards:
145 localDirection.rx() = 0.;
146 localDirection.ry() = 1;
147 break;
148 case Direction::Leftwards:
149 localDirection.rx() = -1.;
150 localDirection.ry() = 0.;
151 break;
152 default: // Direction::Rightwards:
153 localDirection.rx() = 1.;
154 localDirection.ry() = 0.;
155 break;
156 }
157 QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
158 QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
159 return sceneDirection - sceneOrigin;
163void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
164 qreal distance)
166 QPointF initialTouchPos = dragHandle->mapToScene(
167 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
168 QPointF touchPoint = initialTouchPos;
170 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
172 int numSteps = 10;
173 QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
174 drag(touchPoint, dragDirectionVector, distance, numSteps);
176 // Wait for quite a bit before finally releasing to make a very low flick/release
177 // speed.
178 m_fakeTimeSource->m_msecsSinceReference += 5000;
179 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
181 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
182 windowPrivate->flushDelayedTouchEvent();
185void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
186 int numSteps, qint64 timeMs)
188 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
190 qint64 timeStep = timeMs / numSteps;
191 QPointF touchMovement = direction * (distance / (qreal)numSteps);
192 for (int i = 0; i < numSteps; ++i) {
193 touchPoint += touchMovement;
194 m_fakeTimeSource->m_msecsSinceReference += timeStep;
195 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
197 windowPrivate->flushDelayedTouchEvent();
198 }
201DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
203 DirectionalDragArea *dragHandle =
204 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
205 Q_ASSERT(dragHandle != 0);
206 dragHandle->d->setRecognitionTimer(m_fakeTimer);
207 dragHandle->d->setTimeSource(m_fakeTimeSource);
209 AxisVelocityCalculator *edgeDragEvaluator =
210 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
211 Q_ASSERT(edgeDragEvaluator != 0);
212 edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
214 return dragHandle;
217qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
219 AxisVelocityCalculator *edgeDragEvaluator =
220 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
221 Q_ASSERT(edgeDragEvaluator != 0);
223 return edgeDragEvaluator->property("dragThreshold").toReal();
226/*
227 Checks that ending a low-speed drag before dragThreshold results in the
228 Showable getting back to its original position, whereas ending after dragThreshold
229 results in Showable continuing until reaching its new states (shown or hidden)
230 */
231void tst_DragHandle::dragThreshold_horizontal()
233 QFETCH(qreal, rotation);
235 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
236 baseItem->setRotation(rotation);
238 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
239 QQuickItem *parentItem = dragHandle->parentItem();
241 qreal dragThreshold = fetchDragThreshold(dragHandle);
243 // end before the threshold
244 flickAndHold(dragHandle, dragThreshold * 0.7);
246 // should rollback
247 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
248 QCOMPARE(parentItem->property("shown").toBool(), false);
250 // end after the threshold
251 flickAndHold(dragHandle, dragThreshold * 1.2);
253 // should keep going until completion
254 tryCompare([&](){ return parentItem->x(); }, 0);
255 QCOMPARE(parentItem->property("shown").toBool(), true);
257 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
259 dragThreshold = fetchDragThreshold(dragHandle);
261 // end before the threshold
262 flickAndHold(dragHandle, dragThreshold * 0.7);
264 // should rollback
265 tryCompare([&](){ return parentItem->x(); }, 0);
266 QCOMPARE(parentItem->property("shown").toBool(), true);
268 // end after the threshold
269 flickAndHold(dragHandle, dragThreshold * 1.2);
271 // should keep going until completion
272 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
273 QCOMPARE(parentItem->property("shown").toBool(), false);
276void tst_DragHandle::dragThreshold_horizontal_data()
278 QTest::addColumn<qreal>("rotation");
280 QTest::newRow("not rotated") << 0.;
281 QTest::newRow("rotated 90") << 90.;
284void tst_DragHandle::dragThreshold_vertical()
286 QFETCH(qreal, rotation);
288 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
289 baseItem->setRotation(rotation);
291 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
293 qreal dragThreshold = fetchDragThreshold(dragHandle);
295 // end before the threshold
296 flickAndHold(dragHandle, dragThreshold * 0.7);
298 // should rollback
299 QQuickItem *parentItem = dragHandle->parentItem();
300 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
301 QCOMPARE(parentItem->property("shown").toBool(), false);
303 // end after the threshold
304 flickAndHold(dragHandle, dragThreshold * 1.2);
306 // should keep going until completion
307 tryCompare([&](){ return parentItem->y(); }, 0);
308 QCOMPARE(parentItem->property("shown").toBool(), true);
310 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
312 dragThreshold = fetchDragThreshold(dragHandle);
314 // end before the threshold
315 flickAndHold(dragHandle, dragThreshold * 0.7);
317 // should rollback
318 tryCompare([&](){ return parentItem->y(); }, 0);
319 QCOMPARE(parentItem->property("shown").toBool(), true);
321 // end after the threshold
322 flickAndHold(dragHandle, dragThreshold * 1.2);
324 // should keep going until completion
325 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
326 QCOMPARE(parentItem->property("shown").toBool(), false);
329void tst_DragHandle::dragThreshold_vertical_data()
331 QTest::addColumn<qreal>("rotation");
333 QTest::newRow("not rotated") << 0.;
334 QTest::newRow("rotated 90") << 90.;
337/*
338 Checks that when the stretch property is true, dragging the DragHandle increases
339 the width or height (depending on its direction) of its parent Showable
340 */
341void tst_DragHandle::stretch_horizontal()
343 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
344 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
345 QQuickItem *parentItem = dragHandle->parentItem();
347 // enable strech mode
348 m_view->rootObject()->setProperty("stretch", QVariant(true));
350 QCOMPARE(parentItem->width(), 0.0);
352 // flick all the way
353 flickAndHold(dragHandle, totalDragDistance);
355 // should keep going until completion
356 // Parent item should now have its full height
357 tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
358 QCOMPARE(parentItem->property("shown").toBool(), true);
360 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
362 // flick all the way
363 flickAndHold(dragHandle, totalDragDistance);
365 // should keep going until completion
366 // Parent item should now have its full height
367 tryCompare([&](){ return parentItem->width(); }, 0.0);
368 QCOMPARE(parentItem->property("shown").toBool(), false);
371void tst_DragHandle::stretch_vertical()
373 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
374 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
375 QQuickItem *parentItem = dragHandle->parentItem();
377 // enable strech mode
378 m_view->rootObject()->setProperty("stretch", QVariant(true));
380 QCOMPARE(parentItem->height(), 0.0);
382 // flick all the way
383 flickAndHold(dragHandle, totalDragDistance);
385 // should keep going until completion
386 // Parent item should now have its full height
387 tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
388 QCOMPARE(parentItem->property("shown").toBool(), true);
390 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
392 // flick all the way
393 flickAndHold(dragHandle, totalDragDistance);
395 // should keep going until completion
396 // Parent item should now have its full height
397 tryCompare([&](){ return parentItem->height(); }, 0.0);
398 QCOMPARE(parentItem->property("shown").toBool(), false);
401/*
402 Set DragHandle.hintDisplacement to a value bigger than zero.
403 Then lay a finger on the DragHandle.
404 The expected behavior is that it will move or strech its parent Showable
405 by hintDisplacement pixels.
406 */
407void tst_DragHandle::hintingAnimation()
409 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
410 QQuickItem *parentItem = dragHandle->parentItem();
411 qreal hintDisplacement = 100.0;
413 // enable hinting animations and stretch mode
414 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
415 m_view->rootObject()->setProperty("stretch", QVariant(true));
417 QCOMPARE(parentItem->height(), 0.0);
419 QPointF initialTouchPos = dragHandle->mapToScene(
420 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
421 QPointF touchPoint = initialTouchPos;
423 // Pressing causes the Showable to be stretched by hintDisplacement pixels
424 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
425 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
427 // Releasing causes the Showable to shrink back to 0 pixels.
428 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
429 tryCompare([&](){ return parentItem->height(); }, 0.0);
431 QCOMPARE(parentItem->property("shown").toBool(), false);
434/*
435 Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022
437 1) Click on handle.
438 2) wait for hint portion to appear
439 3) slowly drag handle, only a few pixels
441 Expected outcome:
442 Nothing happens
444 Actual outcome:
445 Handle will momentarily move back to zero position, then back down to the
446 hint displacement location.
447 */
448void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed()
450 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
451 QQuickItem *parentItem = dragHandle->parentItem();
452 qreal hintDisplacement = 100.0;
454 // enable hinting animations
455 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
456 m_view->rootObject()->setProperty("stretch", QVariant(true));
458 QCOMPARE(parentItem->height(), 0.0);
460 QPointF initialTouchPos = dragHandle->mapToScene(
461 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
462 QPointF touchPoint = initialTouchPos;
464 // Pressing causes the Showable to be stretched by hintDisplacement pixels
465 const int touchId = 0;
466 QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint());
467 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
470 QSignalSpy parentHeightChangedSpy(parentItem, &QQuickItem::heightChanged);
472 drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/);
474 // Give some time for animations to run, if any
475 QTest::qWait(300);
477 // parentItem height shouldn't have changed at all
478 QVERIFY(parentHeightChangedSpy.isEmpty());
481QTEST_MAIN(tst_DragHandle)
483#include "tst_DragHandle.moc"