on 7f16e39 help sign in
unity8/tests/qmltests/Components/tst_DragHandle.cpp
485 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 m_fakeTimer = new FakeTimer;
105 m_fakeTimeSource.reset(new FakeTimeSource);
108void tst_DragHandle::cleanup()
110 delete m_view;
111 m_view = 0;
113 delete m_touchRegistry;
114 m_touchRegistry = 0;
116 delete m_fakeTimer;
117 m_fakeTimer = 0;
119 m_fakeTimeSource.reset();
122QQuickView *tst_DragHandle::createView()
124 QQuickView *window = new QQuickView(0);
125 window->setResizeMode(QQuickView::SizeRootObjectToView);
126 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
127 window->engine()->addImportPath(QLatin1String(TEST_DIR));
129 return window;
132void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
133 qreal expectedValue)
135 int waitCount = 0;
136 while (actualFunc() != expectedValue && waitCount < 100) {
137 QTest::qWait(50);
138 ++waitCount;
139 }
140 QCOMPARE(actualFunc(), expectedValue);
143namespace {
144QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
146 QPointF localOrigin(0., 0.);
147 QPointF localDirection;
148 switch (edgeDragArea->direction()) {
149 case Direction::Upwards:
150 localDirection.rx() = 0.;
151 localDirection.ry() = -1.;
152 break;
153 case Direction::Downwards:
154 localDirection.rx() = 0.;
155 localDirection.ry() = 1;
156 break;
157 case Direction::Leftwards:
158 localDirection.rx() = -1.;
159 localDirection.ry() = 0.;
160 break;
161 default: // Direction::Rightwards:
162 localDirection.rx() = 1.;
163 localDirection.ry() = 0.;
164 break;
165 }
166 QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
167 QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
168 return sceneDirection - sceneOrigin;
172void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
173 qreal distance)
175 QPointF initialTouchPos = dragHandle->mapToScene(
176 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
177 QPointF touchPoint = initialTouchPos;
179 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
181 int numSteps = 10;
182 QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
183 drag(touchPoint, dragDirectionVector, distance, numSteps);
185 // Wait for quite a bit before finally releasing to make a very low flick/release
186 // speed.
187 m_fakeTimeSource->m_msecsSinceReference += 5000;
188 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
191void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
192 int numSteps, qint64 timeMs)
194 qint64 timeStep = timeMs / numSteps;
195 QPointF touchMovement = direction * (distance / (qreal)numSteps);
196 for (int i = 0; i < numSteps; ++i) {
197 touchPoint += touchMovement;
198 m_fakeTimeSource->m_msecsSinceReference += timeStep;
199 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
200 }
203DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
205 DirectionalDragArea *dragHandle =
206 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
207 Q_ASSERT(dragHandle != 0);
208 dragHandle->setRecognitionTimer(m_fakeTimer);
209 dragHandle->setTimeSource(m_fakeTimeSource);
211 AxisVelocityCalculator *edgeDragEvaluator =
212 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
213 Q_ASSERT(edgeDragEvaluator != 0);
214 edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
216 return dragHandle;
219qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
221 AxisVelocityCalculator *edgeDragEvaluator =
222 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
223 Q_ASSERT(edgeDragEvaluator != 0);
225 return edgeDragEvaluator->property("dragThreshold").toReal();
228/*
229 Checks that ending a low-speed drag before dragThreshold results in the
230 Showable getting back to its original position, whereas ending after dragThreshold
231 results in Showable continuing until reaching its new states (shown or hidden)
232 */
233void tst_DragHandle::dragThreshold_horizontal()
235 QFETCH(qreal, rotation);
237 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
238 baseItem->setRotation(rotation);
240 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
242 qreal dragThreshold = fetchDragThreshold(dragHandle);
244 // end before the threshold
245 flickAndHold(dragHandle, dragThreshold * 0.7);
247 // should rollback
248 QQuickItem *parentItem = dragHandle->parentItem();
249 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
250 QCOMPARE(parentItem->property("shown").toBool(), false);
252 // end after the threshold
253 flickAndHold(dragHandle, dragThreshold * 1.2);
255 // should keep going until completion
256 tryCompare([&](){ return parentItem->x(); }, 0);
257 QCOMPARE(parentItem->property("shown").toBool(), true);
259 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
261 dragThreshold = fetchDragThreshold(dragHandle);
263 // end before the threshold
264 flickAndHold(dragHandle, dragThreshold * 0.7);
266 // should rollback
267 tryCompare([&](){ return parentItem->x(); }, 0);
268 QCOMPARE(parentItem->property("shown").toBool(), true);
270 // end after the threshold
271 flickAndHold(dragHandle, dragThreshold * 1.2);
273 // should keep going until completion
274 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
275 QCOMPARE(parentItem->property("shown").toBool(), false);
278void tst_DragHandle::dragThreshold_horizontal_data()
280 QTest::addColumn<qreal>("rotation");
282 QTest::newRow("not rotated") << 0.;
283 QTest::newRow("rotated 90") << 90.;
286void tst_DragHandle::dragThreshold_vertical()
288 QFETCH(qreal, rotation);
290 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
291 baseItem->setRotation(rotation);
293 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
295 qreal dragThreshold = fetchDragThreshold(dragHandle);
297 // end before the threshold
298 flickAndHold(dragHandle, dragThreshold * 0.7);
300 // should rollback
301 QQuickItem *parentItem = dragHandle->parentItem();
302 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
303 QCOMPARE(parentItem->property("shown").toBool(), false);
305 // end after the threshold
306 flickAndHold(dragHandle, dragThreshold * 1.2);
308 // should keep going until completion
309 tryCompare([&](){ return parentItem->y(); }, 0);
310 QCOMPARE(parentItem->property("shown").toBool(), true);
312 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
314 dragThreshold = fetchDragThreshold(dragHandle);
316 // end before the threshold
317 flickAndHold(dragHandle, dragThreshold * 0.7);
319 // should rollback
320 tryCompare([&](){ return parentItem->y(); }, 0);
321 QCOMPARE(parentItem->property("shown").toBool(), true);
323 // end after the threshold
324 flickAndHold(dragHandle, dragThreshold * 1.2);
326 // should keep going until completion
327 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
328 QCOMPARE(parentItem->property("shown").toBool(), false);
331void tst_DragHandle::dragThreshold_vertical_data()
333 QTest::addColumn<qreal>("rotation");
335 QTest::newRow("not rotated") << 0.;
336 QTest::newRow("rotated 90") << 90.;
339/*
340 Checks that when the stretch property is true, dragging the DragHandle increases
341 the width or height (depending on its direction) of its parent Showable
342 */
343void tst_DragHandle::stretch_horizontal()
345 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
346 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
347 QQuickItem *parentItem = dragHandle->parentItem();
349 // enable strech mode
350 m_view->rootObject()->setProperty("stretch", QVariant(true));
352 QCOMPARE(parentItem->width(), 0.0);
354 // flick all the way
355 flickAndHold(dragHandle, totalDragDistance);
357 // should keep going until completion
358 // Parent item should now have its full height
359 tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
360 QCOMPARE(parentItem->property("shown").toBool(), true);
362 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
364 // flick all the way
365 flickAndHold(dragHandle, totalDragDistance);
367 // should keep going until completion
368 // Parent item should now have its full height
369 tryCompare([&](){ return parentItem->width(); }, 0.0);
370 QCOMPARE(parentItem->property("shown").toBool(), false);
373void tst_DragHandle::stretch_vertical()
375 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
376 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
377 QQuickItem *parentItem = dragHandle->parentItem();
379 // enable strech mode
380 m_view->rootObject()->setProperty("stretch", QVariant(true));
382 QCOMPARE(parentItem->height(), 0.0);
384 // flick all the way
385 flickAndHold(dragHandle, totalDragDistance);
387 // should keep going until completion
388 // Parent item should now have its full height
389 tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
390 QCOMPARE(parentItem->property("shown").toBool(), true);
392 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
394 // flick all the way
395 flickAndHold(dragHandle, totalDragDistance);
397 // should keep going until completion
398 // Parent item should now have its full height
399 tryCompare([&](){ return parentItem->height(); }, 0.0);
400 QCOMPARE(parentItem->property("shown").toBool(), false);
403/*
404 Set DragHandle.hintDisplacement to a value bigger than zero.
405 Then lay a finger on the DragHandle.
406 The expected behavior is that it will move or strech its parent Showable
407 by hintDisplacement pixels.
408 */
409void tst_DragHandle::hintingAnimation()
411 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
412 QQuickItem *parentItem = dragHandle->parentItem();
413 qreal hintDisplacement = 100.0;
415 // enable hinting animations and stretch mode
416 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
417 m_view->rootObject()->setProperty("stretch", QVariant(true));
419 QCOMPARE(parentItem->height(), 0.0);
421 QPointF initialTouchPos = dragHandle->mapToScene(
422 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
423 QPointF touchPoint = initialTouchPos;
425 // Pressing causes the Showable to be stretched by hintDisplacement pixels
426 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
427 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
429 // Releasing causes the Showable to shrink back to 0 pixels.
430 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
431 tryCompare([&](){ return parentItem->height(); }, 0.0);
433 QCOMPARE(parentItem->property("shown").toBool(), false);
436/*
437 Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022
439 1) Click on handle.
440 2) wait for hint portion to appear
441 3) slowly drag handle, only a few pixels
443 Expected outcome:
444 Nothing happens
446 Actual outcome:
447 Handle will momentarily move back to zero position, then back down to the
448 hint displacement location.
449 */
450void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed()
452 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
453 QQuickItem *parentItem = dragHandle->parentItem();
454 qreal hintDisplacement = 100.0;
456 // enable hinting animations
457 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
458 m_view->rootObject()->setProperty("stretch", QVariant(true));
460 QCOMPARE(parentItem->height(), 0.0);
462 QPointF initialTouchPos = dragHandle->mapToScene(
463 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
464 QPointF touchPoint = initialTouchPos;
466 // Pressing causes the Showable to be stretched by hintDisplacement pixels
467 const int touchId = 0;
468 QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint());
469 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
472 QSignalSpy parentHeightChangedSpy(parentItem, SIGNAL(heightChanged()));
474 drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/);
476 // Give some time for animations to run, if any
477 QTest::qWait(300);
479 // parentItem height shouldn't have changed at all
480 QVERIFY(parentHeightChangedSpy.isEmpty());
483QTEST_MAIN(tst_DragHandle)
485#include "tst_DragHandle.moc"