@@ -0,0 +1,295 @@
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+ */
16+
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+
24+#include <DirectionalDragArea.h>
25+
26+class FakeTimer : public UbuntuGestures::AbstractTimer
27+{
28+ Q_OBJECT
29+public:
30+ FakeTimer(QObject *parent = 0)
31+ : UbuntuGestures::AbstractTimer(parent)
32+ {}
33+
34+ virtual int interval() const { return m_duration; }
35+ virtual void setInterval(int msecs) { m_duration = msecs; }
36+private:
37+ int m_duration;
38+};
39+
40+class FakeTimeSource : public UbuntuGestures::TimeSource
41+{
42+public:
43+ FakeTimeSource() { m_msecsSinceReference = 0; }
44+ virtual qint64 msecsSinceReference() {return m_msecsSinceReference;}
45+ qint64 m_msecsSinceReference;
46+};
47+
48+class tst_DragHandle: public QObject
49+{
50+ Q_OBJECT
51+public:
52+ tst_DragHandle() : m_device(0) { }
53+private 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.
56+
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
59+
60+ void dragThreshold_horizontal();
61+ void dragThreshold_vertical();
62+
63+private:
64+ void flickAndHold(DirectionalDragArea *dragHandle, qreal distance);
65+ DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName);
66+ qreal fetchDragThreshold(DirectionalDragArea *dragHandle);
67+ void tryCompare(std::function<qreal ()> actualFunc, qreal expectedValue);
68+
69+ QQuickView *createView();
70+ QQuickView *m_view;
71+ QTouchDevice *m_device;
72+ FakeTimer *m_fakeTimer;
73+ FakeTimeSource *m_fakeTimeSource;
74+};
75+
76+
77+void tst_DragHandle::initTestCase()
78+{
79+ if (!m_device) {
80+ m_device = new QTouchDevice;
81+ m_device->setType(QTouchDevice::TouchScreen);
82+ QWindowSystemInterface::registerTouchDevice(m_device);
83+ }
84+
85+ m_view = 0;
86+}
87+
88+void tst_DragHandle::cleanupTestCase()
89+{
90+}
91+
92+void tst_DragHandle::init()
93+{
94+ m_view = createView();
95+ m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
96+ m_view->show();
97+ QVERIFY(QTest::qWaitForWindowExposed(m_view));
98+ QVERIFY(m_view->rootObject() != 0);
99+ qApp->processEvents();
100+
101+ m_fakeTimer = new FakeTimer;
102+ m_fakeTimeSource = new FakeTimeSource;
103+}
104+
105+void tst_DragHandle::cleanup()
106+{
107+ delete m_view;
108+ m_view = 0;
109+
110+ delete m_fakeTimer;
111+ m_fakeTimer = 0;
112+
113+ delete m_fakeTimeSource;
114+ m_fakeTimeSource = 0;
115+}
116+
117+QQuickView *tst_DragHandle::createView()
118+{
119+ QQuickView *window = new QQuickView(0);
120+ window->setResizeMode(QQuickView::SizeRootObjectToView);
121+ window->resize(600, 600);
122+ window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR));
123+ window->engine()->addImportPath(QLatin1String(TEST_DIR));
124+
125+ return window;
126+}
127+
128+void tst_DragHandle::tryCompare(std::function<qreal ()> actualFunc,
129+ qreal expectedValue)
130+{
131+ int waitCount = 0;
132+ while (actualFunc() != expectedValue && waitCount < 100) {
133+ QTest::qWait(50);
134+ ++waitCount;
135+ }
136+ QCOMPARE(actualFunc(), expectedValue);
137+}
138+
139+namespace {
140+QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea)
141+{
142+
143+ switch (edgeDragArea->direction()) {
144+ case Direction::Upwards:
145+ return QPointF(0, -1);
146+ case Direction::Downwards:
147+ return QPointF(0, 1);
148+ case Direction::Leftwards:
149+ return QPointF(-1, 0);
150+ default: // Direction::Rightwards:
151+ return QPointF(1, 0);
152+ }
153+}
154+}
155+
156+void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle,
157+ qreal distance)
158+{
159+ QPointF initialTouchPos = dragHandle->mapToScene(
160+ QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0));
161+ QPointF touchPoint = initialTouchPos;
162+ int numSteps = 10;
163+ qint64 flickTimeMs = 500;
164+ qint64 timeStep = flickTimeMs / numSteps;
165+
166+ QPointF dragDirectionVector = calculateDirectionVector(dragHandle);
167+ QPointF touchMovement = dragDirectionVector * (distance / (qreal)numSteps);
168+
169+ QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint());
170+
171+ for (int i = 0; i < numSteps; ++i) {
172+ touchPoint += touchMovement;
173+ m_fakeTimeSource->m_msecsSinceReference += timeStep;
174+ QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint());
175+ }
176+
177+ // Wait for quite a bit before finally releasing to make a very low flick/release
178+ // speed.
179+ m_fakeTimeSource->m_msecsSinceReference += 5000;
180+ QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint());
181+}
182+
183+DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
184+{
185+ DirectionalDragArea *dragHandle =
186+ m_view->rootObject()->findChild<DirectionalDragArea*>(objectName);
187+ Q_ASSERT(dragHandle != 0);
188+ dragHandle->setRecognitionTimer(m_fakeTimer);
189+ dragHandle->setTimeSource(m_fakeTimeSource);
190+
191+ AxisVelocityCalculator *edgeDragEvaluator =
192+ dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
193+ Q_ASSERT(edgeDragEvaluator != 0);
194+ edgeDragEvaluator->setTimeSource(m_fakeTimeSource);
195+
196+ return dragHandle;
197+}
198+
199+qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle)
200+{
201+ AxisVelocityCalculator *edgeDragEvaluator =
202+ dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
203+ Q_ASSERT(edgeDragEvaluator != 0);
204+
205+ return edgeDragEvaluator->property("dragThreshold").toReal();
206+}
207+
208+/*
209+ Checks that ending a low-speed drag before dragThreshold results in the
210+ Showable getting back to its original position, whereas ending after dragThreshold
211+ results in Showable continuing until reaching its new states (shown or hidden)
212+ */
213+void tst_DragHandle::dragThreshold_horizontal()
214+{
215+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
216+
217+ qreal dragThreshold = fetchDragThreshold(dragHandle);
218+
219+ // end before the threshold
220+ flickAndHold(dragHandle, dragThreshold * 0.7);
221+
222+ // should rollback
223+ QQuickItem *parentItem = dragHandle->parentItem();
224+ tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
225+ QCOMPARE(parentItem->property("shown").toBool(), false);
226+
227+ // end after the threshold
228+ flickAndHold(dragHandle, dragThreshold * 1.2);
229+
230+ // should keep going until completion
231+ tryCompare([&](){ return parentItem->x(); }, 0);
232+ QCOMPARE(parentItem->property("shown").toBool(), true);
233+
234+ dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
235+
236+ dragThreshold = fetchDragThreshold(dragHandle);
237+
238+ // end before the threshold
239+ flickAndHold(dragHandle, dragThreshold * 0.7);
240+
241+ // should rollback
242+ tryCompare([&](){ return parentItem->x(); }, 0);
243+ QCOMPARE(parentItem->property("shown").toBool(), true);
244+
245+ // end after the threshold
246+ flickAndHold(dragHandle, dragThreshold * 1.2);
247+
248+ // should keep going until completion
249+ tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
250+ QCOMPARE(parentItem->property("shown").toBool(), false);
251+}
252+
253+void tst_DragHandle::dragThreshold_vertical()
254+{
255+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
256+
257+ qreal dragThreshold = fetchDragThreshold(dragHandle);
258+
259+ // end before the threshold
260+ flickAndHold(dragHandle, dragThreshold * 0.7);
261+
262+ // should rollback
263+ QQuickItem *parentItem = dragHandle->parentItem();
264+ tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
265+ QCOMPARE(parentItem->property("shown").toBool(), false);
266+
267+ // end after the threshold
268+ flickAndHold(dragHandle, dragThreshold * 1.2);
269+
270+ // should keep going until completion
271+ tryCompare([&](){ return parentItem->y(); }, 0);
272+ QCOMPARE(parentItem->property("shown").toBool(), true);
273+
274+ dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
275+
276+ dragThreshold = fetchDragThreshold(dragHandle);
277+
278+ // end before the threshold
279+ flickAndHold(dragHandle, dragThreshold * 0.7);
280+
281+ // should rollback
282+ tryCompare([&](){ return parentItem->y(); }, 0);
283+ QCOMPARE(parentItem->property("shown").toBool(), true);
284+
285+ // end after the threshold
286+ flickAndHold(dragHandle, dragThreshold * 1.2);
287+
288+ // should keep going until completion
289+ tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
290+ QCOMPARE(parentItem->property("shown").toBool(), false);
291+}
292+
293+QTEST_MAIN(tst_DragHandle)
294+
295+#include "tst_DragHandle.moc"