on 08bf9db help sign in
unity8/tests/qmltests/Components/tst_DragHandle.cpp
488 lines 16 kB history raw
1/*
2 * Copyright (C) 2013 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>
26class FakeTimer : public UbuntuGestures::AbstractTimer
28 Q_OBJECT
29public:
30 FakeTimer(QObject *parent = 0)
31 : UbuntuGestures::AbstractTimer(parent)
32 {}
34 virtual int interval() const { return m_duration; }
35 virtual void setInterval(int msecs) { m_duration = msecs; }
36private:
37 int m_duration;
38};
40class FakeTimeSource : public UbuntuGestures::TimeSource
42public:
43 FakeTimeSource() { m_msecsSinceReference = 0; }
44 qint64 msecsSinceReference() override {return m_msecsSinceReference;}
45 qint64 m_msecsSinceReference;
46};
48class tst_DragHandle: public QObject
50 Q_OBJECT
51public:
52 tst_DragHandle() : m_device(0) { }
53private Q_SLOTS:
54 void initTestCase(); // will be called before the first test function is executed
55 void cleanupTestCase(); // will be called after the last test function was executed.
57 void init(); // called right before each and every test function is executed
58 void cleanup(); // called right after each and every test function is executed
60 void dragThreshold_horizontal();
61 void dragThreshold_horizontal_data();
62 void dragThreshold_vertical();
63 void dragThreshold_vertical_data();
64 void stretch_horizontal();
65 void stretch_vertical();
66 void hintingAnimation();
67 void hintingAnimation_dontRestartAfterFinishedAndStillPressed();
70private:
71 void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
72 void drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
73 int numSteps, qint64 timeMs = 500);
74 DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName);
75 qreal fetchDragThreshold(DirectionalDragArea *dragHandle);
76 void tryCompare(std::function<qreal ()> actualFunc, qreal expectedValue);
78 QQuickView *createView();
79 QQuickView *m_view;
80 QTouchDevice *m_device;
81 FakeTimer *m_fakeTimer;
82 QSharedPointer<FakeTimeSource> m_fakeTimeSource;
83};
86void tst_DragHandle::initTestCase()
88 if (!m_device) {
89 m_device = new QTouchDevice;
90 m_device->setType(QTouchDevice::TouchScreen);
91 QWindowSystemInterface::registerTouchDevice(m_device);
92 }
94 m_view = 0;
97void tst_DragHandle::cleanupTestCase()
101void tst_DragHandle::init()
103 m_view = createView();
104 m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
105 m_view->show();
106 QVERIFY(QTest::qWaitForWindowExposed(m_view));
107 QVERIFY(m_view->rootObject() != 0);
108 qApp->processEvents();
110 m_fakeTimer = new FakeTimer;
111 m_fakeTimeSource.reset(new FakeTimeSource);
114void tst_DragHandle::cleanup()
116 delete m_view;
117 m_view = 0;
119 delete m_fakeTimer;
120 m_fakeTimer = 0;
122 m_fakeTimeSource.reset();
125QQuickView *tst_DragHandle::createView()
127 QQuickView *window = new QQuickView(0);
128 window->setResizeMode(QQuickView::SizeRootObjectToView);
129 window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
130 window->engine()->addImportPath(QLatin1String(TEST_DIR));
132 return window;
135void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
136 qreal expectedValue)
138 int waitCount = 0;
139 while (actualFunc() != expectedValue && waitCount < 100) {
140 QTest::qWait(50);
141 ++waitCount;
142 }
143 QCOMPARE(actualFunc(), expectedValue);
146namespace {
147QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
149 QPointF localOrigin(0., 0.);
150 QPointF localDirection;
151 switch (edgeDragArea->direction()) {
152 case Direction::Upwards:
153 localDirection.rx() = 0.;
154 localDirection.ry() = -1.;
155 break;
156 case Direction::Downwards:
157 localDirection.rx() = 0.;
158 localDirection.ry() = 1;
159 break;
160 case Direction::Leftwards:
161 localDirection.rx() = -1.;
162 localDirection.ry() = 0.;
163 break;
164 default: // Direction::Rightwards:
165 localDirection.rx() = 1.;
166 localDirection.ry() = 0.;
167 break;
168 }
169 QPointF sceneOrigin = edgeDragArea->mapToScene(localOrigin);
170 QPointF sceneDirection = edgeDragArea->mapToScene(localDirection);
171 return sceneDirection - sceneOrigin;
175void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
176 qreal distance)
178 QPointF initialTouchPos = dragHandle->mapToScene(
179 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
180 QPointF touchPoint = initialTouchPos;
182 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
184 int numSteps = 10;
185 QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
186 drag(touchPoint, dragDirectionVector, distance, numSteps);
188 // Wait for quite a bit before finally releasing to make a very low flick/release
189 // speed.
190 m_fakeTimeSource->m_msecsSinceReference += 5000;
191 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
194void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance,
195 int numSteps, qint64 timeMs)
197 qint64 timeStep = timeMs / numSteps;
198 QPointF touchMovement = direction * (distance / (qreal)numSteps);
199 for (int i = 0; i < numSteps; ++i) {
200 touchPoint += touchMovement;
201 m_fakeTimeSource->m_msecsSinceReference += timeStep;
202 QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
203 }
206DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
208 DirectionalDragArea *dragHandle =
209 m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
210 Q_ASSERT(dragHandle != 0);
211 dragHandle->setRecognitionTimer(m_fakeTimer);
212 dragHandle->setTimeSource(m_fakeTimeSource);
214 AxisVelocityCalculator *edgeDragEvaluator =
215 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
216 Q_ASSERT(edgeDragEvaluator != 0);
217 edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
219 return dragHandle;
222qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
224 AxisVelocityCalculator *edgeDragEvaluator =
225 dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
226 Q_ASSERT(edgeDragEvaluator != 0);
228 return edgeDragEvaluator->property("dragThreshold").toReal();
231/*
232 Checks that ending a low-speed drag before dragThreshold results in the
233 Showable getting back to its original position, whereas ending after dragThreshold
234 results in Showable continuing until reaching its new states (shown or hidden)
235 */
236void tst_DragHandle::dragThreshold_horizontal()
238 QFETCH(qreal, rotation);
240 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
241 baseItem->setRotation(rotation);
243 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
245 qreal dragThreshold = fetchDragThreshold(dragHandle);
247 // end before the threshold
248 flickAndHold(dragHandle, dragThreshold * 0.7);
250 // should rollback
251 QQuickItem *parentItem = dragHandle->parentItem();
252 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
253 QCOMPARE(parentItem->property("shown").toBool(), false);
255 // end after the threshold
256 flickAndHold(dragHandle, dragThreshold * 1.2);
258 // should keep going until completion
259 tryCompare([&](){ return parentItem->x(); }, 0);
260 QCOMPARE(parentItem->property("shown").toBool(), true);
262 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
264 dragThreshold = fetchDragThreshold(dragHandle);
266 // end before the threshold
267 flickAndHold(dragHandle, dragThreshold * 0.7);
269 // should rollback
270 tryCompare([&](){ return parentItem->x(); }, 0);
271 QCOMPARE(parentItem->property("shown").toBool(), true);
273 // end after the threshold
274 flickAndHold(dragHandle, dragThreshold * 1.2);
276 // should keep going until completion
277 tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
278 QCOMPARE(parentItem->property("shown").toBool(), false);
281void tst_DragHandle::dragThreshold_horizontal_data()
283 QTest::addColumn<qreal>("rotation");
285 QTest::newRow("not rotated") << 0.;
286 QTest::newRow("rotated 90") << 90.;
289void tst_DragHandle::dragThreshold_vertical()
291 QFETCH(qreal, rotation);
293 QQuickItem *baseItem = m_view->rootObject()->findChild<QQuickItem*>("baseItem");
294 baseItem->setRotation(rotation);
296 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
298 qreal dragThreshold = fetchDragThreshold(dragHandle);
300 // end before the threshold
301 flickAndHold(dragHandle, dragThreshold * 0.7);
303 // should rollback
304 QQuickItem *parentItem = dragHandle->parentItem();
305 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
306 QCOMPARE(parentItem->property("shown").toBool(), false);
308 // end after the threshold
309 flickAndHold(dragHandle, dragThreshold * 1.2);
311 // should keep going until completion
312 tryCompare([&](){ return parentItem->y(); }, 0);
313 QCOMPARE(parentItem->property("shown").toBool(), true);
315 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
317 dragThreshold = fetchDragThreshold(dragHandle);
319 // end before the threshold
320 flickAndHold(dragHandle, dragThreshold * 0.7);
322 // should rollback
323 tryCompare([&](){ return parentItem->y(); }, 0);
324 QCOMPARE(parentItem->property("shown").toBool(), true);
326 // end after the threshold
327 flickAndHold(dragHandle, dragThreshold * 1.2);
329 // should keep going until completion
330 tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
331 QCOMPARE(parentItem->property("shown").toBool(), false);
334void tst_DragHandle::dragThreshold_vertical_data()
336 QTest::addColumn<qreal>("rotation");
338 QTest::newRow("not rotated") << 0.;
339 QTest::newRow("rotated 90") << 90.;
342/*
343 Checks that when the stretch property is true, dragging the DragHandle increases
344 the width or height (depending on its direction) of its parent Showable
345 */
346void tst_DragHandle::stretch_horizontal()
348 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
349 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
350 QQuickItem *parentItem = dragHandle->parentItem();
352 // enable strech mode
353 m_view->rootObject()->setProperty("stretch", QVariant(true));
355 QCOMPARE(parentItem->width(), 0.0);
357 // flick all the way
358 flickAndHold(dragHandle, totalDragDistance);
360 // should keep going until completion
361 // Parent item should now have its full height
362 tryCompare([&](){ return parentItem->width(); }, totalDragDistance);
363 QCOMPARE(parentItem->property("shown").toBool(), true);
365 dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
367 // flick all the way
368 flickAndHold(dragHandle, totalDragDistance);
370 // should keep going until completion
371 // Parent item should now have its full height
372 tryCompare([&](){ return parentItem->width(); }, 0.0);
373 QCOMPARE(parentItem->property("shown").toBool(), false);
376void tst_DragHandle::stretch_vertical()
378 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
379 qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal();
380 QQuickItem *parentItem = dragHandle->parentItem();
382 // enable strech mode
383 m_view->rootObject()->setProperty("stretch", QVariant(true));
385 QCOMPARE(parentItem->height(), 0.0);
387 // flick all the way
388 flickAndHold(dragHandle, totalDragDistance);
390 // should keep going until completion
391 // Parent item should now have its full height
392 tryCompare([&](){ return parentItem->height(); }, totalDragDistance);
393 QCOMPARE(parentItem->property("shown").toBool(), true);
395 dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
397 // flick all the way
398 flickAndHold(dragHandle, totalDragDistance);
400 // should keep going until completion
401 // Parent item should now have its full height
402 tryCompare([&](){ return parentItem->height(); }, 0.0);
403 QCOMPARE(parentItem->property("shown").toBool(), false);
406/*
407 Set DragHandle.hintDisplacement to a value bigger than zero.
408 Then lay a finger on the DragHandle.
409 The expected behavior is that it will move or strech its parent Showable
410 by hintDisplacement pixels.
411 */
412void tst_DragHandle::hintingAnimation()
414 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
415 QQuickItem *parentItem = dragHandle->parentItem();
416 qreal hintDisplacement = 100.0;
418 // enable hinting animations and stretch mode
419 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
420 m_view->rootObject()->setProperty("stretch", QVariant(true));
422 QCOMPARE(parentItem->height(), 0.0);
424 QPointF initialTouchPos = dragHandle->mapToScene(
425 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
426 QPointF touchPoint = initialTouchPos;
428 // Pressing causes the Showable to be stretched by hintDisplacement pixels
429 QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
430 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
432 // Releasing causes the Showable to shrink back to 0 pixels.
433 QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
434 tryCompare([&](){ return parentItem->height(); }, 0.0);
436 QCOMPARE(parentItem->property("shown").toBool(), false);
439/*
440 Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022
442 1) Click on handle.
443 2) wait for hint portion to appear
444 3) slowly drag handle, only a few pixels
446 Expected outcome:
447 Nothing happens
449 Actual outcome:
450 Handle will momentarily move back to zero position, then back down to the
451 hint displacement location.
452 */
453void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed()
455 DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
456 QQuickItem *parentItem = dragHandle->parentItem();
457 qreal hintDisplacement = 100.0;
459 // enable hinting animations
460 m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement));
461 m_view->rootObject()->setProperty("stretch", QVariant(true));
463 QCOMPARE(parentItem->height(), 0.0);
465 QPointF initialTouchPos = dragHandle->mapToScene(
466 QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
467 QPointF touchPoint = initialTouchPos;
469 // Pressing causes the Showable to be stretched by hintDisplacement pixels
470 const int touchId = 0;
471 QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint());
472 tryCompare([&](){ return parentItem->height(); }, hintDisplacement);
475 QSignalSpy parentHeightChangedSpy(parentItem, SIGNAL(heightChanged()));
477 drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/);
479 // Give some time for animations to run, if any
480 QTest::qWait(300);
482 // parentItem height shouldn't have changed at all
483 QVERIFY(parentHeightChangedSpy.isEmpty());
486QTEST_MAIN(tst_DragHandle)
488#include "tst_DragHandle.moc"