on bbce293 help sign in
unity8/tests/qmltests/Components/tst_DragHandle.cpp
481 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>
30using namespace UbuntuGestures;
32class tst_DragHandle: public QObject
34 Q_OBJECT
35public:
36 tst_DragHandle() : m_device(0) { }
37private Q_SLOTS:
38 void initTestCase(); // will be called before the first test function is executed
39 void cleanupTestCase(); // will be called after the last test function was executed.
41 void init(); // called right before each and every test function is executed
42 void cleanup(); // called right after each and every test function is executed
44 void dragThreshold_horizontal();
45 void dragThreshold_horizontal_data();
46 void dragThreshold_vertical();
47 void dragThreshold_vertical_data();
48 void stretch_horizontal();
49 void stretch_vertical();
50 void hintingAnimation();
51 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
53private:
54 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
55 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
56 int numSteps, qint64 timeMs = 500);
57 DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName);
58 qreal fetchDragThreshold(DirectionalDragArea *dragHandle);
59 void tryCompare(std::function<qreal ()> actualFunc, qreal expectedValue);
61 QQuickView *createView();
62 QQuickView *m_view;
63 QTouchDevice *m_device;
64 FakeTimer *m_fakeTimer;
65 QSharedPointer<FakeTimeSource> m_fakeTimeSource;
66};
69void tst_DragHandle::initTestCase()
71 if (!m_device) {
72 m_device = new QTouchDevice;
73 m_device->setType(QTouchDevice::TouchScreen);
74 QWindowSystemInterface::registerTouchDevice(m_device);
75 }
77 m_view = 0;
80void tst_DragHandle::cleanupTestCase()
84void tst_DragHandle::init()
86 m_view = createView();
87 m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
88 m_view->show();
89 QVERIFY(QTest::qWaitForWindowExposed(m_view));
90 QVERIFY(m_view->rootObject() != 0);
91 qApp->processEvents();
93 // Hide the controls to ensure we don't hit them accidentally
94 QQuickItem *controls = m_view->rootObject()->findChild<QQuickItem*>("controls");
95 controls->setVisible(false);
97 m_fakeTimeSource.reset(new FakeTimeSource);
98 m_fakeTimer = new FakeTimer(m_fakeTimeSource);
101void tst_DragHandle::cleanup()
103 delete m_view;
104 m_view = 0;
106 delete m_fakeTimer;
107 m_fakeTimer = 0;
109 m_fakeTimeSource.reset();
112QQuickView *tst_DragHandle::createView()
114 QQuickView *window = new QQuickView(0);
115 window->setResizeMode(QQuickView::SizeRootObjectToView);
116 window->engine()->addImportPath(QLatin1String(TEST_DIR));
118 return window;
121void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
122 qreal expectedValue)
124 int waitCount = 0;
125 while (actualFunc() != expectedValue && waitCount < 100) {
126 QTest::qWait(50);
127 ++waitCount;
128 }
129 QCOMPARE(actualFunc(), expectedValue);
132namespace {
133QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
135 QPointF localOrigin(0., 0.);
136 QPointF localDirection;
137 switch (edgeDragArea->direction()) {
138 case Direction::Upwards:
139 localDirection.rx() = 0.;
140 localDirection.ry() = -1.;
141 break;
142 case Direction::Downwards:
143 localDirection.rx() = 0.;
144 localDirection.ry() = 1;
145 break;
146 case Direction::Leftwards:
147 localDirection.rx() = -1.;
148 localDirection.ry() = 0.;
149 break;
150 default: // Direction::Rightwards:
151 localDirection.rx() = 1.;
152 localDirection.ry() = 0.;
153 break;
154 }
155 QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
156 QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
157 return sceneDirection - sceneOrigin;
161void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
162 qreal distance)
164 QPointF initialTouchPos = dragHandle->mapToScene(
165 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
166 QPointF touchPoint = initialTouchPos;
168 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
170 int numSteps = 10;
171 QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
172 drag(touchPoint, dragDirectionVector, distance, numSteps);
174 // Wait for quite a bit before finally releasing to make a very low flick/release
175 // speed.
176 m_fakeTimeSource->m_msecsSinceReference += 5000;
177 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
179 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
180 windowPrivate->flushDelayedTouchEvent();
183void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
184 int numSteps, qint64 timeMs)
186 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(m_view);
188 qint64 timeStep = timeMs / numSteps;
189 QPointF touchMovement = direction * (distance / (qreal)numSteps);
190 for (int i = 0; i < numSteps; ++i) {
191 touchPoint += touchMovement;
192 m_fakeTimeSource->m_msecsSinceReference += timeStep;
193 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
195 windowPrivate->flushDelayedTouchEvent();
196 }
199DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
201 DirectionalDragArea *dragHandle =
202 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
203 Q_ASSERT(dragHandle != 0);
204 dragHandle->d->setRecognitionTimer(m_fakeTimer);
205 dragHandle->d->setTimeSource(m_fakeTimeSource);
207 AxisVelocityCalculator *edgeDragEvaluator =
208 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
209 Q_ASSERT(edgeDragEvaluator != 0);
210 edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
212 return dragHandle;
215qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
217 AxisVelocityCalculator *edgeDragEvaluator =
218 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
219 Q_ASSERT(edgeDragEvaluator != 0);
221 return edgeDragEvaluator->property("dragThreshold").toReal();
224/*
225 Checks that ending a low-speed drag before dragThreshold results in the
226 Showable getting back to its original position, whereas ending after dragThreshold
227 results in Showable continuing until reaching its new states (shown or hidden)
228 */
229void tst_DragHandle::dragThreshold_horizontal()
231 QFETCH(qreal, rotation);
233 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
234 baseItem->setRotation(rotation);
236 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
237 QQuickItem *parentItem = dragHandle->parentItem();
239 qreal dragThreshold = fetchDragThreshold(dragHandle);
241 // end before the threshold
242 flickAndHold(dragHandle, dragThreshold * 0.7);
244 // should rollback
245 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
246 QCOMPARE(parentItem->property("shown").toBool(), false);
248 // end after the threshold
249 flickAndHold(dragHandle, dragThreshold * 1.2);
251 // should keep going until completion
252 tryCompare([&](){ return parentItem->x(); }, 0);
253 QCOMPARE(parentItem->property("shown").toBool(), true);
255 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
257 dragThreshold = fetchDragThreshold(dragHandle);
259 // end before the threshold
260 flickAndHold(dragHandle, dragThreshold * 0.7);
262 // should rollback
263 tryCompare([&](){ return parentItem->x(); }, 0);
264 QCOMPARE(parentItem->property("shown").toBool(), true);
266 // end after the threshold
267 flickAndHold(dragHandle, dragThreshold * 1.2);
269 // should keep going until completion
270 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
271 QCOMPARE(parentItem->property("shown").toBool(), false);
274void tst_DragHandle::dragThreshold_horizontal_data()
276 QTest::addColumn<qreal>("rotation");
278 QTest::newRow("not rotated") << 0.;
279 QTest::newRow("rotated 90") << 90.;
282void tst_DragHandle::dragThreshold_vertical()
284 QFETCH(qreal, rotation);
286 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
287 baseItem->setRotation(rotation);
289 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
291 qreal dragThreshold = fetchDragThreshold(dragHandle);
293 // end before the threshold
294 flickAndHold(dragHandle, dragThreshold * 0.7);
296 // should rollback
297 QQuickItem *parentItem = dragHandle->parentItem();
298 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
299 QCOMPARE(parentItem->property("shown").toBool(), false);
301 // end after the threshold
302 flickAndHold(dragHandle, dragThreshold * 1.2);
304 // should keep going until completion
305 tryCompare([&](){ return parentItem->y(); }, 0);
306 QCOMPARE(parentItem->property("shown").toBool(), true);
308 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
310 dragThreshold = fetchDragThreshold(dragHandle);
312 // end before the threshold
313 flickAndHold(dragHandle, dragThreshold * 0.7);
315 // should rollback
316 tryCompare([&](){ return parentItem->y(); }, 0);
317 QCOMPARE(parentItem->property("shown").toBool(), true);
319 // end after the threshold
320 flickAndHold(dragHandle, dragThreshold * 1.2);
322 // should keep going until completion
323 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
324 QCOMPARE(parentItem->property("shown").toBool(), false);
327void tst_DragHandle::dragThreshold_vertical_data()
329 QTest::addColumn<qreal>("rotation");
331 QTest::newRow("not rotated") << 0.;
332 QTest::newRow("rotated 90") << 90.;
335/*
336 Checks that when the stretch property is true, dragging the DragHandle increases
337 the width or height (depending on its direction) of its parent Showable
338 */
339void tst_DragHandle::stretch_horizontal()
341 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
342 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
343 QQuickItem *parentItem = dragHandle->parentItem();
345 // enable strech mode
346 m_view->rootObject()->setProperty("stretch", QVariant(true));
348 QCOMPARE(parentItem->width(), 0.0);
350 // flick all the way
351 flickAndHold(dragHandle, totalDragDistance);
353 // should keep going until completion
354 // Parent item should now have its full height
355 tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
356 QCOMPARE(parentItem->property("shown").toBool(), true);
358 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
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(); }, 0.0);
366 QCOMPARE(parentItem->property("shown").toBool(), false);
369void tst_DragHandle::stretch_vertical()
371 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
372 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
373 QQuickItem *parentItem = dragHandle->parentItem();
375 // enable strech mode
376 m_view->rootObject()->setProperty("stretch", QVariant(true));
378 QCOMPARE(parentItem->height(), 0.0);
380 // flick all the way
381 flickAndHold(dragHandle, totalDragDistance);
383 // should keep going until completion
384 // Parent item should now have its full height
385 tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
386 QCOMPARE(parentItem->property("shown").toBool(), true);
388 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
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(); }, 0.0);
396 QCOMPARE(parentItem->property("shown").toBool(), false);
399/*
400 Set DragHandle.hintDisplacement to a value bigger than zero.
401 Then lay a finger on the DragHandle.
402 The expected behavior is that it will move or strech its parent Showable
403 by hintDisplacement pixels.
404 */
405void tst_DragHandle::hintingAnimation()
407 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
408 QQuickItem *parentItem = dragHandle->parentItem();
409 qreal hintDisplacement = 100.0;
411 // enable hinting animations and stretch mode
412 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
413 m_view->rootObject()->setProperty("stretch", QVariant(true));
415 QCOMPARE(parentItem->height(), 0.0);
417 QPointF initialTouchPos = dragHandle->mapToScene(
418 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
419 QPointF touchPoint = initialTouchPos;
421 // Pressing causes the Showable to be stretched by hintDisplacement pixels
422 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
423 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
425 // Releasing causes the Showable to shrink back to 0 pixels.
426 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
427 tryCompare([&](){ return parentItem->height(); }, 0.0);
429 QCOMPARE(parentItem->property("shown").toBool(), false);
432/*
433 Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022
435 1) Click on handle.
436 2) wait for hint portion to appear
437 3) slowly drag handle, only a few pixels
439 Expected outcome:
440 Nothing happens
442 Actual outcome:
443 Handle will momentarily move back to zero position, then back down to the
444 hint displacement location.
445 */
446void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed()
448 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
449 QQuickItem *parentItem = dragHandle->parentItem();
450 qreal hintDisplacement = 100.0;
452 // enable hinting animations
453 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
454 m_view->rootObject()->setProperty("stretch", QVariant(true));
456 QCOMPARE(parentItem->height(), 0.0);
458 QPointF initialTouchPos = dragHandle->mapToScene(
459 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
460 QPointF touchPoint = initialTouchPos;
462 // Pressing causes the Showable to be stretched by hintDisplacement pixels
463 const int touchId = 0;
464 QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint());
465 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
468 QSignalSpy parentHeightChangedSpy(parentItem, &QQuickItem::heightChanged);
470 drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/);
472 // Give some time for animations to run, if any
473 QTest::qWait(300);
475 // parentItem height shouldn't have changed at all
476 QVERIFY(parentHeightChangedSpy.isEmpty());
479QTEST_MAIN(tst_DragHandle)
481#include "tst_DragHandle.moc"