/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include class FakeTimer : public UbuntuGestures::AbstractTimer { Q_OBJECT public: FakeTimer(QObject *parent = 0) : UbuntuGestures::AbstractTimer(parent) {} virtual int interval() const { return m_duration; } virtual void setInterval(int msecs) { m_duration = msecs; } private: int m_duration; }; class FakeTimeSource : public UbuntuGestures::TimeSource { public: FakeTimeSource() { m_msecsSinceReference = 0; } qint64 msecsSinceReference() override {return m_msecsSinceReference;} qint64 m_msecsSinceReference; }; class tst_DragHandle: public QObject { Q_OBJECT public: tst_DragHandle() : m_device(0) { } private Q_SLOTS: void initTestCase(); // will be called before the first test function is executed void cleanupTestCase(); // will be called after the last test function was executed. void init(); // called right before each and every test function is executed void cleanup(); // called right after each and every test function is executed void dragThreshold_horizontal(); void dragThreshold_vertical(); void stretch_horizontal(); void stretch_vertical(); void hintingAnimation(); void hintingAnimation_dontRestartAfterFinishedAndStillPressed(); private: void flickAndHold(DirectionalDragArea *dragHandle, qreal distance); void drag(QPointF &touchPoint, const QPointF& direction, qreal distance, int numSteps, qint64 timeMs = 500); DirectionalDragArea *fetchAndSetupDragHandle(const char *objectName); qreal fetchDragThreshold(DirectionalDragArea *dragHandle); void tryCompare(std::function actualFunc, qreal expectedValue); QQuickView *createView(); QQuickView *m_view; QTouchDevice *m_device; FakeTimer *m_fakeTimer; QSharedPointer m_fakeTimeSource; }; void tst_DragHandle::initTestCase() { if (!m_device) { m_device = new QTouchDevice; m_device->setType(QTouchDevice::TouchScreen); QWindowSystemInterface::registerTouchDevice(m_device); } m_view = 0; } void tst_DragHandle::cleanupTestCase() { } void tst_DragHandle::init() { m_view = createView(); m_view->setSource(QUrl::fromLocalFile(TEST_DIR"/tst_DragHandle.qml")); m_view->show(); QVERIFY(QTest::qWaitForWindowExposed(m_view)); QVERIFY(m_view->rootObject() != 0); qApp->processEvents(); m_fakeTimer = new FakeTimer; m_fakeTimeSource.reset(new FakeTimeSource); } void tst_DragHandle::cleanup() { delete m_view; m_view = 0; delete m_fakeTimer; m_fakeTimer = 0; m_fakeTimeSource.reset(); } QQuickView *tst_DragHandle::createView() { QQuickView *window = new QQuickView(0); window->setResizeMode(QQuickView::SizeRootObjectToView); window->resize(600, 600); window->engine()->addImportPath(QLatin1String(UBUNTU_GESTURES_PLUGIN_DIR)); window->engine()->addImportPath(QLatin1String(TEST_DIR)); return window; } void tst_DragHandle::tryCompare(std::function actualFunc, qreal expectedValue) { int waitCount = 0; while (actualFunc() != expectedValue && waitCount < 100) { QTest::qWait(50); ++waitCount; } QCOMPARE(actualFunc(), expectedValue); } namespace { QPointF calculateDirectionVector(DirectionalDragArea *edgeDragArea) { switch (edgeDragArea->direction()) { case Direction::Upwards: return QPointF(0, -1); case Direction::Downwards: return QPointF(0, 1); case Direction::Leftwards: return QPointF(-1, 0); default: // Direction::Rightwards: return QPointF(1, 0); } } } void tst_DragHandle::flickAndHold(DirectionalDragArea *dragHandle, qreal distance) { QPointF initialTouchPos = dragHandle->mapToScene( QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0)); QPointF touchPoint = initialTouchPos; QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint()); int numSteps = 10; QPointF dragDirectionVector = calculateDirectionVector(dragHandle); drag(touchPoint, dragDirectionVector, distance, numSteps); // Wait for quite a bit before finally releasing to make a very low flick/release // speed. m_fakeTimeSource->m_msecsSinceReference += 5000; QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint()); } void tst_DragHandle::drag(QPointF &touchPoint, const QPointF& direction, qreal distance, int numSteps, qint64 timeMs) { qint64 timeStep = timeMs / numSteps; QPointF touchMovement = direction * (distance / (qreal)numSteps); for (int i = 0; i < numSteps; ++i) { touchPoint += touchMovement; m_fakeTimeSource->m_msecsSinceReference += timeStep; QTest::touchEvent(m_view, m_device).move(0, touchPoint.toPoint()); } } DirectionalDragArea *tst_DragHandle::fetchAndSetupDragHandle(const char *objectName) { DirectionalDragArea *dragHandle = m_view->rootObject()->findChild(objectName); Q_ASSERT(dragHandle != 0); dragHandle->setRecognitionTimer(m_fakeTimer); dragHandle->setTimeSource(m_fakeTimeSource); AxisVelocityCalculator *edgeDragEvaluator = dragHandle->findChild("edgeDragEvaluator"); Q_ASSERT(edgeDragEvaluator != 0); edgeDragEvaluator->setTimeSource(m_fakeTimeSource); return dragHandle; } qreal tst_DragHandle::fetchDragThreshold(DirectionalDragArea *dragHandle) { AxisVelocityCalculator *edgeDragEvaluator = dragHandle->findChild("edgeDragEvaluator"); Q_ASSERT(edgeDragEvaluator != 0); return edgeDragEvaluator->property("dragThreshold").toReal(); } /* Checks that ending a low-speed drag before dragThreshold results in the Showable getting back to its original position, whereas ending after dragThreshold results in Showable continuing until reaching its new states (shown or hidden) */ void tst_DragHandle::dragThreshold_horizontal() { DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle"); qreal dragThreshold = fetchDragThreshold(dragHandle); // end before the threshold flickAndHold(dragHandle, dragThreshold * 0.7); // should rollback QQuickItem *parentItem = dragHandle->parentItem(); tryCompare([&](){ return parentItem->x(); }, -parentItem->width()); QCOMPARE(parentItem->property("shown").toBool(), false); // end after the threshold flickAndHold(dragHandle, dragThreshold * 1.2); // should keep going until completion tryCompare([&](){ return parentItem->x(); }, 0); QCOMPARE(parentItem->property("shown").toBool(), true); dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle"); dragThreshold = fetchDragThreshold(dragHandle); // end before the threshold flickAndHold(dragHandle, dragThreshold * 0.7); // should rollback tryCompare([&](){ return parentItem->x(); }, 0); QCOMPARE(parentItem->property("shown").toBool(), true); // end after the threshold flickAndHold(dragHandle, dragThreshold * 1.2); // should keep going until completion tryCompare([&](){ return parentItem->x(); }, -parentItem->width()); QCOMPARE(parentItem->property("shown").toBool(), false); } void tst_DragHandle::dragThreshold_vertical() { DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle"); qreal dragThreshold = fetchDragThreshold(dragHandle); // end before the threshold flickAndHold(dragHandle, dragThreshold * 0.7); // should rollback QQuickItem *parentItem = dragHandle->parentItem(); tryCompare([&](){ return parentItem->y(); }, -parentItem->height()); QCOMPARE(parentItem->property("shown").toBool(), false); // end after the threshold flickAndHold(dragHandle, dragThreshold * 1.2); // should keep going until completion tryCompare([&](){ return parentItem->y(); }, 0); QCOMPARE(parentItem->property("shown").toBool(), true); dragHandle = fetchAndSetupDragHandle("upwardsDragHandle"); dragThreshold = fetchDragThreshold(dragHandle); // end before the threshold flickAndHold(dragHandle, dragThreshold * 0.7); // should rollback tryCompare([&](){ return parentItem->y(); }, 0); QCOMPARE(parentItem->property("shown").toBool(), true); // end after the threshold flickAndHold(dragHandle, dragThreshold * 1.2); // should keep going until completion tryCompare([&](){ return parentItem->y(); }, -parentItem->height()); QCOMPARE(parentItem->property("shown").toBool(), false); } /* Checks that when the stretch property is true, dragging the DragHandle increases the width or height (depending on its direction) of its parent Showable */ void tst_DragHandle::stretch_horizontal() { DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("rightwardsDragHandle"); qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal(); QQuickItem *parentItem = dragHandle->parentItem(); // enable strech mode m_view->rootObject()->setProperty("stretch", QVariant(true)); QCOMPARE(parentItem->width(), 0.0); // flick all the way flickAndHold(dragHandle, totalDragDistance); // should keep going until completion // Parent item should now have its full height tryCompare([&](){ return parentItem->width(); }, totalDragDistance); QCOMPARE(parentItem->property("shown").toBool(), true); dragHandle = fetchAndSetupDragHandle("leftwardsDragHandle"); // flick all the way flickAndHold(dragHandle, totalDragDistance); // should keep going until completion // Parent item should now have its full height tryCompare([&](){ return parentItem->width(); }, 0.0); QCOMPARE(parentItem->property("shown").toBool(), false); } void tst_DragHandle::stretch_vertical() { DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle"); qreal totalDragDistance = dragHandle->property("maxTotalDragDistance").toReal(); QQuickItem *parentItem = dragHandle->parentItem(); // enable strech mode m_view->rootObject()->setProperty("stretch", QVariant(true)); QCOMPARE(parentItem->height(), 0.0); // flick all the way flickAndHold(dragHandle, totalDragDistance); // should keep going until completion // Parent item should now have its full height tryCompare([&](){ return parentItem->height(); }, totalDragDistance); QCOMPARE(parentItem->property("shown").toBool(), true); dragHandle = fetchAndSetupDragHandle("upwardsDragHandle"); // flick all the way flickAndHold(dragHandle, totalDragDistance); // should keep going until completion // Parent item should now have its full height tryCompare([&](){ return parentItem->height(); }, 0.0); QCOMPARE(parentItem->property("shown").toBool(), false); } /* Set DragHandle.hintDisplacement to a value bigger than zero. Then lay a finger on the DragHandle. The expected behavior is that it will move or strech its parent Showable by hintDisplacement pixels. */ void tst_DragHandle::hintingAnimation() { DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle"); QQuickItem *parentItem = dragHandle->parentItem(); qreal hintDisplacement = 100.0; // enable hinting animations and stretch mode m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement)); m_view->rootObject()->setProperty("stretch", QVariant(true)); QCOMPARE(parentItem->height(), 0.0); QPointF initialTouchPos = dragHandle->mapToScene( QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0)); QPointF touchPoint = initialTouchPos; // Pressing causes the Showable to be stretched by hintDisplacement pixels QTest::touchEvent(m_view, m_device).press(0, touchPoint.toPoint()); tryCompare([&](){ return parentItem->height(); }, hintDisplacement); // Releasing causes the Showable to shrink back to 0 pixels. QTest::touchEvent(m_view, m_device).release(0, touchPoint.toPoint()); tryCompare([&](){ return parentItem->height(); }, 0.0); QCOMPARE(parentItem->property("shown").toBool(), false); } /* Regression test for LP#1269022: https://bugs.launchpad.net/unity8/+bug/1269022 1) Click on handle. 2) wait for hint portion to appear 3) slowly drag handle, only a few pixels Expected outcome: Nothing happens Actual outcome: Handle will momentarily move back to zero position, then back down to the hint displacement location. */ void tst_DragHandle::hintingAnimation_dontRestartAfterFinishedAndStillPressed() { DirectionalDragArea *dragHandle = fetchAndSetupDragHandle("downwardsDragHandle"); QQuickItem *parentItem = dragHandle->parentItem(); qreal hintDisplacement = 100.0; // enable hinting animations m_view->rootObject()->setProperty("hintDisplacement", QVariant(hintDisplacement)); m_view->rootObject()->setProperty("stretch", QVariant(true)); QCOMPARE(parentItem->height(), 0.0); QPointF initialTouchPos = dragHandle->mapToScene( QPointF(dragHandle->width() / 2.0, dragHandle->height() / 2.0)); QPointF touchPoint = initialTouchPos; // Pressing causes the Showable to be stretched by hintDisplacement pixels const int touchId = 0; QTest::touchEvent(m_view, m_device).press(touchId, touchPoint.toPoint()); tryCompare([&](){ return parentItem->height(); }, hintDisplacement); QSignalSpy parentHeightChangedSpy(parentItem, SIGNAL(heightChanged())); drag(touchPoint, QPointF(0.0, -1.0) /*dragDirectionVector*/, 15 /*distance*/, 3 /*numSteps*/); // Give some time for animations to run, if any QTest::qWait(300); // parentItem height shouldn't have changed at all QVERIFY(parentHeightChangedSpy.isEmpty()); } QTEST_MAIN(tst_DragHandle) #include "tst_DragHandle.moc"