@@ -0,0 +1,296 @@
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() : 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 *view;
71+ QTouchDevice *device;
72+ FakeTimer *fakeTimer;
73+ FakeTimeSource *fakeTimeSource;
74+};
75+
76+
77+void tst_DragHandle::initTestCase()
78+{
79+ if (!device) {
80+ device = new QTouchDevice;
81+ device->setType(QTouchDevice::TouchScreen);
82+ QWindowSystemInterface::registerTouchDevice(device);
83+ }
84+
85+ view = 0;
86+}
87+
88+void tst_DragHandle::cleanupTestCase()
89+{
90+}
91+
92+void tst_DragHandle::init()
93+{
94+ view = createView();
95+ view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml"));
96+ view->show();
97+ QVERIFY(QTest::qWaitForWindowExposed(view));
98+ QVERIFY(view->rootObject() != 0);
99+ qApp->processEvents();
100+
101+ fakeTimer = new FakeTimer;
102+ fakeTimeSource = new FakeTimeSource;
103+}
104+
105+void tst_DragHandle::cleanup()
106+{
107+ delete view;
108+ view = 0;
109+
110+ delete fakeTimer;
111+ fakeTimer = 0;
112+
113+ delete fakeTimeSource;
114+ 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(view, device).press(0, touchPoint.toPoint());
170+
171+ for (int i = 0; i < numSteps; ++i) {
172+ touchPoint += touchMovement;
173+ fakeTimeSource->m_msecsSinceReference += timeStep;
174+ QTest::touchEvent(view, 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+ fakeTimeSource->m_msecsSinceReference += 5000;
180+ QTest::touchEvent(view, device).release(0, touchPoint.toPoint());
181+}
182+
183+DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName)
184+{
185+ DirectionalDragArea *dragHandle =
186+ view->rootObject()->findChild<DirectionalDragArea*>(objectName);
187+ Q_ASSERT(dragHandle != 0);
188+ dragHandle->setRecognitionTimer(fakeTimer);
189+ dragHandle->setTimeSource(fakeTimeSource);
190+
191+ AxisVelocityCalculator *edgeDragEvaluator =
192+ dragHandle->findChild<AxisVelocityCalculator*>("edgeDragEvaluator");
193+ Q_ASSERT(edgeDragEvaluator != 0);
194+ edgeDragEvaluator->setTimeSource(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+ edgeDragEvaluator->setTimeSource(fakeTimeSource);
205+
206+ return edgeDragEvaluator->property("dragThreshold").toReal();
207+}
208+
209+/*
210+ Checks that ending a low-speed drag before dragThreshold results in the
211+ Showable getting back to its original position, whereas ending after dragThreshold
212+ results in Showable continuing until reaching its new states (shown or hidden)
213+ */
214+void tst_DragHandle::dragThreshold_horizontal()
215+{
216+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle");
217+
218+ qreal dragThreshold = fetchDragThreshold(dragHandle);
219+
220+ // end before the threshold
221+ flickAndHold(dragHandle, dragThreshold * 0.7);
222+
223+ // should rollback
224+ QQuickItem *parentItem = dragHandle->parentItem();
225+ tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
226+ QCOMPARE(parentItem->property("shown").toBool(), false);
227+
228+ // end after the threshold
229+ flickAndHold(dragHandle, dragThreshold * 1.2);
230+
231+ // should keep going until completion
232+ tryCompare([&](){ return parentItem->x(); }, 0);
233+ QCOMPARE(parentItem->property("shown").toBool(), true);
234+
235+ dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle");
236+
237+ dragThreshold = fetchDragThreshold(dragHandle);
238+
239+ // end before the threshold
240+ flickAndHold(dragHandle, dragThreshold * 0.7);
241+
242+ // should rollback
243+ tryCompare([&](){ return parentItem->x(); }, 0);
244+ QCOMPARE(parentItem->property("shown").toBool(), true);
245+
246+ // end after the threshold
247+ flickAndHold(dragHandle, dragThreshold * 1.2);
248+
249+ // should keep going until completion
250+ tryCompare([&](){ return parentItem->x(); }, -parentItem->width());
251+ QCOMPARE(parentItem->property("shown").toBool(), false);
252+}
253+
254+void tst_DragHandle::dragThreshold_vertical()
255+{
256+ DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle");
257+
258+ qreal dragThreshold = fetchDragThreshold(dragHandle);
259+
260+ // end before the threshold
261+ flickAndHold(dragHandle, dragThreshold * 0.7);
262+
263+ // should rollback
264+ QQuickItem *parentItem = dragHandle->parentItem();
265+ tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
266+ QCOMPARE(parentItem->property("shown").toBool(), false);
267+
268+ // end after the threshold
269+ flickAndHold(dragHandle, dragThreshold * 1.2);
270+
271+ // should keep going until completion
272+ tryCompare([&](){ return parentItem->y(); }, 0);
273+ QCOMPARE(parentItem->property("shown").toBool(), true);
274+
275+ dragHandle = fetchAndSetupDragHandle("upwardsDragHandle");
276+
277+ dragThreshold = fetchDragThreshold(dragHandle);
278+
279+ // end before the threshold
280+ flickAndHold(dragHandle, dragThreshold * 0.7);
281+
282+ // should rollback
283+ tryCompare([&](){ return parentItem->y(); }, 0);
284+ QCOMPARE(parentItem->property("shown").toBool(), true);
285+
286+ // end after the threshold
287+ flickAndHold(dragHandle, dragThreshold * 1.2);
288+
289+ // should keep going until completion
290+ tryCompare([&](){ return parentItem->y(); }, -parentItem->height());
291+ QCOMPARE(parentItem->property("shown").toBool(), false);
292+}
293+
294+QTEST_MAIN(tst_DragHandle)
295+
296+#include "tst_DragHandle.moc"