/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include //TESTED_CLASS=QPropertyAnimation //TESTED_FILES= class UncontrolledAnimation : public QPropertyAnimation { Q_OBJECT public: int duration() const { return -1; /* not time driven */ } protected: void updateCurrentTime(int currentTime) { QPropertyAnimation::updateCurrentTime(currentTime); if (currentTime >= QPropertyAnimation::duration() || currentLoop() >= 1) stop(); } }; class MyObject : public QObject { Q_OBJECT Q_PROPERTY(qreal x READ x WRITE setX) public: MyObject() : m_x(0) { } qreal x() const { return m_x; } void setX(qreal x) { m_x = x; } private: qreal m_x; }; class DummyPropertyAnimation : public QPropertyAnimation { public: DummyPropertyAnimation(QObject *parent = 0) : QPropertyAnimation(parent) { setTargetObject(&o); this->setPropertyName("x"); setEndValue(100); } MyObject o; }; class tst_QPropertyAnimation : public QObject { Q_OBJECT public: tst_QPropertyAnimation(); virtual ~tst_QPropertyAnimation(); public Q_SLOTS: void init(); void cleanup(); private slots: void construction(); void setCurrentTime_data(); void setCurrentTime(); void statesAndSignals_data(); void statesAndSignals(); void deletion1(); void deletion2(); void deletion3(); void duration0(); void noStartValue(); void noStartValueWithLoop(); void startWhenAnotherIsRunning(); void easingcurve_data(); void easingcurve(); void startWithoutStartValue(); void startBackwardWithoutEndValue(); void playForwardBackward(); void interpolated(); void setStartEndValues_data(); void setStartEndValues(); void zeroDurationStart(); void operationsInStates_data(); void operationsInStates(); void oneKeyValue(); void updateOnSetKeyValues(); void restart(); void valueChanged(); void twoAnimations(); void deletedInUpdateCurrentTime(); void totalDuration(); }; tst_QPropertyAnimation::tst_QPropertyAnimation() { } tst_QPropertyAnimation::~tst_QPropertyAnimation() { } void tst_QPropertyAnimation::init() { qRegisterMetaType("QAbstractAnimation::State"); qRegisterMetaType("QAbstractAnimation::DeletionPolicy"); } void tst_QPropertyAnimation::cleanup() { } class AnimationObject : public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue) Q_PROPERTY(qreal realValue READ realValue WRITE setRealValue) public: AnimationObject(int startValue = 0) : v(startValue), rv(startValue) { } int value() const { return v; } void setValue(int value) { v = value; } qreal realValue() const { return rv; } void setRealValue(qreal value) { rv = value; } int v; qreal rv; }; void tst_QPropertyAnimation::construction() { QPropertyAnimation panimation; } void tst_QPropertyAnimation::setCurrentTime_data() { QTest::addColumn("duration"); QTest::addColumn("loopCount"); QTest::addColumn("currentTime"); QTest::addColumn("testCurrentTime"); QTest::addColumn("testCurrentLoop"); QTest::newRow("-1") << -1 << 1 << 0 << 0 << 0; QTest::newRow("0") << 0 << 1 << 0 << 0 << 0; QTest::newRow("1") << 0 << 1 << 1 << 0 << 0; QTest::newRow("2") << 0 << 2 << 1 << 0 << 0; QTest::newRow("3") << 1 << 1 << 0 << 0 << 0; QTest::newRow("4") << 1 << 1 << 1 << 1 << 0; QTest::newRow("5") << 1 << 2 << 1 << 0 << 1; QTest::newRow("6") << 1 << 2 << 2 << 1 << 1; QTest::newRow("7") << 1 << 2 << 3 << 1 << 1; QTest::newRow("8") << 1 << 3 << 2 << 0 << 2; QTest::newRow("9") << 1 << 3 << 3 << 1 << 2; QTest::newRow("a") << 10 << 1 << 0 << 0 << 0; QTest::newRow("b") << 10 << 1 << 1 << 1 << 0; QTest::newRow("c") << 10 << 1 << 10 << 10 << 0; QTest::newRow("d") << 10 << 2 << 10 << 0 << 1; QTest::newRow("e") << 10 << 2 << 11 << 1 << 1; QTest::newRow("f") << 10 << 2 << 20 << 10 << 1; QTest::newRow("g") << 10 << 2 << 21 << 10 << 1; QTest::newRow("negloop 0") << 10 << -1 << 0 << 0 << 0; QTest::newRow("negloop 1") << 10 << -1 << 10 << 0 << 1; QTest::newRow("negloop 2") << 10 << -1 << 15 << 5 << 1; QTest::newRow("negloop 3") << 10 << -1 << 20 << 0 << 2; QTest::newRow("negloop 4") << 10 << -1 << 30 << 0 << 3; } void tst_QPropertyAnimation::setCurrentTime() { QFETCH(int, duration); QFETCH(int, loopCount); QFETCH(int, currentTime); QFETCH(int, testCurrentTime); QFETCH(int, testCurrentLoop); QPropertyAnimation animation; if (duration < 0) QTest::ignoreMessage(QtWarningMsg, "QVariantAnimation::setDuration: cannot set a negative duration"); animation.setDuration(duration); animation.setLoopCount(loopCount); animation.setCurrentTime(currentTime); QCOMPARE(animation.currentTime(), testCurrentTime); QCOMPARE(animation.currentLoop(), testCurrentLoop); } void tst_QPropertyAnimation::statesAndSignals_data() { QTest::addColumn("uncontrolled"); QTest::newRow("normal animation") << false; QTest::newRow("animation with undefined duration") << true; } void tst_QPropertyAnimation::statesAndSignals() { QFETCH(bool, uncontrolled); QPropertyAnimation *anim; if (uncontrolled) anim = new UncontrolledAnimation; else anim = new DummyPropertyAnimation; anim->setDuration(100); QSignalSpy finishedSpy(anim, SIGNAL(finished())); QSignalSpy runningSpy(anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); QSignalSpy currentLoopSpy(anim, SIGNAL(currentLoopChanged(int))); anim->setCurrentTime(1); anim->setCurrentTime(100); QCOMPARE(finishedSpy.count(), 0); QCOMPARE(runningSpy.count(), 0); QCOMPARE(currentLoopSpy.count(), 0); QCOMPARE(anim->state(), QAnimationGroup::Stopped); anim->setLoopCount(3); anim->setCurrentTime(101); if (uncontrolled) QSKIP("Uncontrolled animations don't handle looping", SkipSingle); QCOMPARE(currentLoopSpy.count(), 1); QCOMPARE(anim->currentLoop(), 1); anim->setCurrentTime(0); QCOMPARE(currentLoopSpy.count(), 2); QCOMPARE(anim->currentLoop(), 0); anim->start(); QCOMPARE(anim->state(), QAnimationGroup::Running); QCOMPARE(runningSpy.count(), 1); //anim must have started QCOMPARE(anim->currentLoop(), 0); runningSpy.clear(); anim->stop(); QCOMPARE(anim->state(), QAnimationGroup::Stopped); QCOMPARE(runningSpy.count(), 1); //anim must have stopped QCOMPARE(finishedSpy.count(), 0); QCOMPARE(anim->currentTime(), 0); QCOMPARE(anim->currentLoop(), 0); QCOMPARE(currentLoopSpy.count(), 2); runningSpy.clear(); anim->start(); QTest::qWait(1000); QCOMPARE(anim->state(), QAnimationGroup::Stopped); QCOMPARE(runningSpy.count(), 2); //started and stopped again runningSpy.clear(); QCOMPARE(finishedSpy.count(), 1); QCOMPARE(anim->currentTime(), 100); QCOMPARE(anim->currentLoop(), 2); QCOMPARE(currentLoopSpy.count(), 4); anim->start(); // auto-rewinds QCOMPARE(anim->state(), QAnimationGroup::Running); QCOMPARE(anim->currentTime(), 0); QCOMPARE(anim->currentLoop(), 0); QCOMPARE(currentLoopSpy.count(), 5); QCOMPARE(runningSpy.count(), 1); // anim has started QCOMPARE(finishedSpy.count(), 1); QCOMPARE(anim->currentLoop(), 0); runningSpy.clear(); QTest::qWait(1000); QCOMPARE(currentLoopSpy.count(), 7); QCOMPARE(anim->state(), QAnimationGroup::Stopped); QCOMPARE(anim->currentLoop(), 2); QCOMPARE(runningSpy.count(), 1); // anim has stopped QCOMPARE(finishedSpy.count(), 2); QCOMPARE(anim->currentTime(), 100); delete anim; } void tst_QPropertyAnimation::deletion1() { QObject *object = new QWidget; QPointer anim = new QPropertyAnimation(object, "minimumWidth"); //test that the animation is deleted correctly depending of the deletion flag passed in start() QSignalSpy runningSpy(anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); QSignalSpy finishedSpy(anim, SIGNAL(finished())); anim->setStartValue(10); anim->setEndValue(20); anim->setDuration(200); anim->start(); QCOMPARE(runningSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); QVERIFY(anim); QCOMPARE(anim->state(), QAnimationGroup::Running); QTest::qWait(100); QVERIFY(anim); QCOMPARE(anim->state(), QAnimationGroup::Running); QTest::qWait(150); QVERIFY(anim); //The animation should not have been deleted QCOMPARE(anim->state(), QAnimationGroup::Stopped); QCOMPARE(runningSpy.count(), 2); QCOMPARE(finishedSpy.count(), 1); anim->start(QVariantAnimation::DeleteWhenStopped); QVERIFY(anim); QCOMPARE(anim->state(), QAnimationGroup::Running); QTest::qWait(100); QVERIFY(anim); QCOMPARE(anim->state(), QAnimationGroup::Running); QTest::qWait(150); QVERIFY(!anim); //The animation must have been deleted QCOMPARE(runningSpy.count(), 4); QCOMPARE(finishedSpy.count(), 2); delete object; } void tst_QPropertyAnimation::deletion2() { //test that the animation get deleted if the object is deleted QObject *object = new QWidget; QPointer anim = new QPropertyAnimation(object,"minimumWidth"); anim->setStartValue(10); anim->setEndValue(20); anim->setDuration(200); QSignalSpy runningSpy(anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); QSignalSpy finishedSpy(anim, SIGNAL(finished())); anim->setStartValue(10); anim->setEndValue(20); anim->setDuration(200); anim->start(); QTest::qWait(50); QVERIFY(anim); QCOMPARE(anim->state(), QAnimationGroup::Running); QCOMPARE(runningSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); //we can't call deletaLater directly because the delete would only happen in the next loop of _this_ event loop QTimer::singleShot(0, object, SLOT(deleteLater())); QTest::qWait(50); QVERIFY(anim->targetObject() == 0); } void tst_QPropertyAnimation::deletion3() { //test that the stopped signal is emit when the animation is destroyed QObject *object = new QWidget; QPropertyAnimation *anim = new QPropertyAnimation(object,"minimumWidth"); anim->setStartValue(10); anim->setEndValue(20); anim->setDuration(200); QSignalSpy runningSpy(anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); QSignalSpy finishedSpy(anim, SIGNAL(finished())); anim->start(); QTest::qWait(50); QCOMPARE(anim->state(), QAnimationGroup::Running); QCOMPARE(runningSpy.count(), 1); QCOMPARE(finishedSpy.count(), 0); delete anim; QCOMPARE(runningSpy.count(), 2); QCOMPARE(finishedSpy.count(), 0); } void tst_QPropertyAnimation::duration0() { QObject o; o.setProperty("ole", 42); QCOMPARE(o.property("ole").toInt(), 42); QPropertyAnimation animation(&o, "ole"); animation.setEndValue(43); QVERIFY(!animation.currentValue().isValid()); QCOMPARE(animation.currentValue().toInt(), 0); animation.setStartValue(42); QVERIFY(animation.currentValue().isValid()); QCOMPARE(animation.currentValue().toInt(), 42); QCOMPARE(o.property("ole").toInt(), 42); animation.setDuration(0); QCOMPARE(animation.currentValue().toInt(), 43); //it is at the end animation.start(); QCOMPARE(animation.state(), QAnimationGroup::Stopped); QCOMPARE(animation.currentTime(), 0); QCOMPARE(o.property("ole").toInt(), 43); } class StartValueTester : public QObject { Q_OBJECT Q_PROPERTY(int ole READ ole WRITE setOle) public: StartValueTester() : o(0) { } int ole() const { return o; } void setOle(int v) { o = v; values << v; } int o; QList values; }; void tst_QPropertyAnimation::noStartValue() { StartValueTester o; o.setProperty("ole", 42); o.values.clear(); QPropertyAnimation a(&o, "ole"); a.setEndValue(420); a.setDuration(250); a.start(); QTest::qWait(300); QCOMPARE(o.values.first(), 42); QCOMPARE(o.values.last(), 420); } void tst_QPropertyAnimation::noStartValueWithLoop() { StartValueTester o; o.setProperty("ole", 42); o.values.clear(); QPropertyAnimation a(&o, "ole"); a.setEndValue(420); a.setDuration(250); a.setLoopCount(2); a.start(); a.setCurrentTime(250); QCOMPARE(o.values.first(), 42); QCOMPARE(a.currentValue().toInt(), 42); QCOMPARE(o.values.last(), 42); a.setCurrentTime(500); QCOMPARE(a.currentValue().toInt(), 420); } void tst_QPropertyAnimation::startWhenAnotherIsRunning() { StartValueTester o; o.setProperty("ole", 42); o.values.clear(); { //normal case: the animation finishes and is deleted QPointer anim = new QPropertyAnimation(&o, "ole"); anim->setEndValue(100); QSignalSpy runningSpy(anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); anim->start(QVariantAnimation::DeleteWhenStopped); QTest::qWait(anim->duration() + 50); QCOMPARE(runningSpy.count(), 2); //started and then stopped QVERIFY(!anim); } { QPointer anim = new QPropertyAnimation(&o, "ole"); anim->setEndValue(100); QSignalSpy runningSpy(anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); anim->start(QVariantAnimation::DeleteWhenStopped); QTest::qWait(anim->duration()/2); QPointer anim2 = new QPropertyAnimation(&o, "ole"); anim2->setEndValue(100); QCOMPARE(runningSpy.count(), 1); QCOMPARE(anim->state(), QVariantAnimation::Running); //anim2 will interrupt anim1 QMetaObject::invokeMethod(anim2, "start", Qt::QueuedConnection, Q_ARG(QAbstractAnimation::DeletionPolicy, QVariantAnimation::DeleteWhenStopped)); QTest::qWait(50); QVERIFY(!anim); //anim should have been deleted QVERIFY(anim2); QTest::qWait(anim2->duration()); QVERIFY(!anim2); //anim2 is finished: it should have been deleted by now QVERIFY(!anim); } } // copy from easing.cpp in case that function changes definition static qreal easeInOutBack(qreal t) { qreal s = 1.70158; qreal t_adj = 2.0f * (qreal)t; if (t_adj < 1) { s *= 1.525f; return 1.0/2*(t_adj*t_adj*((s+1)*t_adj - s)); } else { t_adj -= 2; s *= 1.525f; return 1.0/2*(t_adj*t_adj*((s+1)*t_adj + s) + 2); } } void tst_QPropertyAnimation::easingcurve_data() { QTest::addColumn("currentTime"); QTest::addColumn("expectedvalue"); QTest::newRow("interpolation1") << 0 << 0; QTest::newRow("interpolation2") << 1000 << 1000; QTest::newRow("extrapolationbelow") << 250 << -99; QTest::newRow("extrapolationabove") << 750 << 1099; } void tst_QPropertyAnimation::easingcurve() { QFETCH(int, currentTime); QFETCH(int, expectedvalue); QObject o; o.setProperty("ole", 42); QCOMPARE(o.property("ole").toInt(), 42); QPropertyAnimation pAnimation(&o, "ole"); pAnimation.setStartValue(0); pAnimation.setEndValue(1000); pAnimation.setDuration(1000); // this easingcurve assumes that we extrapolate before startValue and after endValue QEasingCurve easingCurve; easingCurve.setCustomType(easeInOutBack); pAnimation.setEasingCurve(easingCurve); pAnimation.start(); pAnimation.pause(); pAnimation.setCurrentTime(currentTime); QCOMPARE(o.property("ole").toInt(), expectedvalue); } void tst_QPropertyAnimation::startWithoutStartValue() { QObject o; o.setProperty("ole", 42); QCOMPARE(o.property("ole").toInt(), 42); QPropertyAnimation anim(&o, "ole"); anim.setEndValue(100); anim.start(); QTest::qWait(100); int current = anim.currentValue().toInt(); //it is somewhere in the animation QVERIFY(current > 42); QVERIFY(current < 100); QTest::qWait(200); QCOMPARE(anim.state(), QVariantAnimation::Stopped); current = anim.currentValue().toInt(); QCOMPARE(current, 100); QCOMPARE(o.property("ole").toInt(), current); anim.setEndValue(110); anim.start(); current = anim.currentValue().toInt(); // the default start value will reevaluate the current property // and set it to the end value of the last iteration QCOMPARE(current, 100); QTest::qWait(100); current = anim.currentValue().toInt(); //it is somewhere in the animation QVERIFY(current >= 100); QVERIFY(current <= 110); } void tst_QPropertyAnimation::startBackwardWithoutEndValue() { QObject o; o.setProperty("ole", 42); QCOMPARE(o.property("ole").toInt(), 42); QPropertyAnimation anim(&o, "ole"); anim.setStartValue(100); anim.setDirection(QAbstractAnimation::Backward); //we start without an end value anim.start(); QCOMPARE(anim.state(), QAbstractAnimation::Running); QCOMPARE(o.property("ole").toInt(), 42); //the initial value QTest::qWait(100); int current = anim.currentValue().toInt(); //it is somewhere in the animation QVERIFY(current > 42); QVERIFY(current < 100); QTest::qWait(200); QCOMPARE(anim.state(), QVariantAnimation::Stopped); current = anim.currentValue().toInt(); QCOMPARE(current, 100); QCOMPARE(o.property("ole").toInt(), current); anim.setStartValue(110); anim.start(); current = anim.currentValue().toInt(); // the default start value will reevaluate the current property // and set it to the end value of the last iteration QCOMPARE(current, 100); QTest::qWait(100); current = anim.currentValue().toInt(); //it is somewhere in the animation QVERIFY(current >= 100); QVERIFY(current <= 110); } void tst_QPropertyAnimation::playForwardBackward() { QObject o; o.setProperty("ole", 0); QCOMPARE(o.property("ole").toInt(), 0); QPropertyAnimation anim(&o, "ole"); anim.setStartValue(0); anim.setEndValue(100); anim.start(); QTest::qWait(anim.duration() + 50); QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(anim.currentTime(), anim.duration()); //the animation is at the end anim.setDirection(QVariantAnimation::Backward); anim.start(); QCOMPARE(anim.state(), QAbstractAnimation::Running); QTest::qWait(anim.duration() + 50); QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(anim.currentTime(), 0); //the direction is backward //restarting should jump to the end anim.start(); QCOMPARE(anim.state(), QAbstractAnimation::Running); QCOMPARE(anim.currentTime(), anim.duration()); QTest::qWait(anim.duration() + 50); QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(anim.currentTime(), 0); } struct Number { Number() {} Number(int n) : n(n) {} Number(const Number &other) : n(other.n){} Number &operator=(const Number &other) { n = other.n; return *this; } bool operator==(const Number &other) const { return n == other.n; } int n; }; Q_DECLARE_METATYPE(Number) Q_DECLARE_METATYPE(QAbstractAnimation::State) QVariant numberInterpolator(const Number &f, const Number &t, qreal progress) { return qVariantFromValue(Number(f.n + (t.n - f.n)*progress)); } QVariant xaxisQPointInterpolator(const QPointF &f, const QPointF &t, qreal progress) { return QPointF(f.x() + (t.x() - f.x())*progress, f.y()); } void tst_QPropertyAnimation::interpolated() { QObject o; o.setProperty("point", QPointF()); //this will avoid warnings o.setProperty("number", qVariantFromValue(Number(42))); QCOMPARE(qVariantValue(o.property("number")), Number(42)); { qRegisterAnimationInterpolator(numberInterpolator); QPropertyAnimation anim(&o, "number"); anim.setStartValue(qVariantFromValue(Number(0))); anim.setEndValue(qVariantFromValue(Number(100))); anim.setDuration(1000); anim.start(); anim.pause(); anim.setCurrentTime(100); Number t(qVariantValue(o.property("number"))); QCOMPARE(t, Number(10)); anim.setCurrentTime(500); QCOMPARE(qVariantValue(o.property("number")), Number(50)); } { qRegisterAnimationInterpolator(xaxisQPointInterpolator); QPropertyAnimation anim(&o, "point"); anim.setStartValue(QPointF(0,0)); anim.setEndValue(QPointF(100, 100)); anim.setDuration(1000); anim.start(); anim.pause(); anim.setCurrentTime(100); QCOMPARE(o.property("point"), QVariant(QPointF(10, 0))); anim.setCurrentTime(500); QCOMPARE(o.property("point"), QVariant(QPointF(50, 0))); } { // unregister it and see if we get back the default behaviour qRegisterAnimationInterpolator(0); QPropertyAnimation anim(&o, "point"); anim.setStartValue(QPointF(0,0)); anim.setEndValue(QPointF(100, 100)); anim.setDuration(1000); anim.start(); anim.pause(); anim.setCurrentTime(100); QCOMPARE(o.property("point").toPointF(), QPointF(10, 10)); anim.setCurrentTime(500); QCOMPARE(o.property("point").toPointF(), QPointF(50, 50)); } { // Interpolate a qreal property with a int interpolator AnimationObject o1; o1.setRealValue(42.42); QPropertyAnimation anim(&o1, "realValue"); anim.setStartValue(0); anim.setEndValue(100); anim.start(); QCOMPARE(o1.realValue(), qreal(0)); anim.setCurrentTime(250); QCOMPARE(o1.realValue(), qreal(100)); } } Q_DECLARE_METATYPE(QVariant) void tst_QPropertyAnimation::setStartEndValues_data() { QTest::addColumn("propertyName"); QTest::addColumn("initialValue"); QTest::addColumn("startValue"); QTest::addColumn("endValue"); QTest::newRow("dynamic property") << QByteArray("ole") << QVariant(42) << QVariant(0) << QVariant(10); QTest::newRow("real property, with unmatching types") << QByteArray("x") << QVariant(42.) << QVariant(0) << QVariant(10.); } void tst_QPropertyAnimation::setStartEndValues() { MyObject object; QFETCH(QByteArray, propertyName); QFETCH(QVariant, initialValue); QFETCH(QVariant, startValue); QFETCH(QVariant, endValue); //this tests the start value, end value and default start value object.setProperty(propertyName, initialValue); QPropertyAnimation anim(&object, propertyName); QVariantAnimation::KeyValues values; QCOMPARE(anim.keyValues(), values); //let's add a start value anim.setStartValue(startValue); values << QVariantAnimation::KeyValue(0, startValue); QCOMPARE(anim.keyValues(), values); anim.setEndValue(endValue); values << QVariantAnimation::KeyValue(1, endValue); QCOMPARE(anim.keyValues(), values); //now we can play with objects QCOMPARE(object.property(propertyName).toDouble(), initialValue.toDouble()); anim.start(); QVERIFY(anim.startValue().isValid()); QCOMPARE(object.property(propertyName), anim.startValue()); anim.setCurrentTime(anim.duration()/2); QCOMPARE(object.property(propertyName).toDouble(), (startValue.toDouble() + endValue.toDouble())/2 ); //just in the middle of the animation anim.setCurrentTime(anim.duration()); //we go to the end of the animation QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped QVERIFY(anim.endValue().isValid()); QCOMPARE(object.property(propertyName), anim.endValue()); //end of the animations //now we remove the explicit start value and test the implicit one anim.stop(); object.setProperty(propertyName, initialValue); //let's reset the start value values.remove(0); anim.setStartValue(QVariant()); QCOMPARE(anim.keyValues(), values); QVERIFY(!anim.startValue().isValid()); anim.start(); QCOMPARE(object.property(propertyName), initialValue); anim.setCurrentTime(anim.duration()/2); QCOMPARE(object.property(propertyName).toDouble(), (initialValue.toDouble() + endValue.toDouble())/2 ); //just in the middle of the animation anim.setCurrentTime(anim.duration()); //we go to the end of the animation QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped QVERIFY(anim.endValue().isValid()); QCOMPARE(object.property(propertyName), anim.endValue()); //end of the animations //now we set back the startValue anim.setStartValue(startValue); QVERIFY(anim.startValue().isValid()); anim.start(); QCOMPARE(object.property(propertyName), startValue); } void tst_QPropertyAnimation::zeroDurationStart() { DummyPropertyAnimation anim; QSignalSpy spy(&anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); anim.setDuration(0); QCOMPARE(anim.state(), QAbstractAnimation::Stopped); anim.start(); //the animation stops immediately QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(spy.count(), 2); //let's check the first state change const QVariantList firstChange = spy.first(); //old state QCOMPARE(qVariantValue(firstChange.first()), QAbstractAnimation::Stopped); //new state QCOMPARE(qVariantValue(firstChange.last()), QAbstractAnimation::Running); //let's check the first state change const QVariantList secondChange = spy.last(); //old state QCOMPARE(qVariantValue(secondChange.first()), QAbstractAnimation::Running); //new state QCOMPARE(qVariantValue(secondChange.last()), QAbstractAnimation::Stopped); } #define Pause 1 #define Start 2 #define Resume 3 #define Stop 4 void tst_QPropertyAnimation::operationsInStates_data() { QTest::addColumn("originState"); QTest::addColumn("operation"); QTest::addColumn("expectedWarning"); QTest::addColumn("expectedState"); QString pauseWarn(QLatin1String("QAbstractAnimation::pause: Cannot pause a stopped animation")); QString resumeWarn(QLatin1String("QAbstractAnimation::resume: Cannot resume an animation that is not paused")); QTest::newRow("S-pause") << QAbstractAnimation::Stopped << Pause << pauseWarn << QAbstractAnimation::Stopped; QTest::newRow("S-start") << QAbstractAnimation::Stopped << Start << QString() << QAbstractAnimation::Running; QTest::newRow("S-resume") << QAbstractAnimation::Stopped << Resume << resumeWarn << QAbstractAnimation::Stopped; QTest::newRow("S-stop") << QAbstractAnimation::Stopped << Stop << QString() << QAbstractAnimation::Stopped; QTest::newRow("P-pause") << QAbstractAnimation::Paused << Pause << QString() << QAbstractAnimation::Paused; QTest::newRow("P-start") << QAbstractAnimation::Paused << Start << QString() << QAbstractAnimation::Running; QTest::newRow("P-resume") << QAbstractAnimation::Paused << Resume << QString() << QAbstractAnimation::Running; QTest::newRow("P-stop") << QAbstractAnimation::Paused << Stop << QString() << QAbstractAnimation::Stopped; QTest::newRow("R-pause") << QAbstractAnimation::Running << Pause << QString() << QAbstractAnimation::Paused; QTest::newRow("R-start") << QAbstractAnimation::Running << Start << QString() << QAbstractAnimation::Running; QTest::newRow("R-resume") << QAbstractAnimation::Running << Resume << resumeWarn << QAbstractAnimation::Running; QTest::newRow("R-stop") << QAbstractAnimation::Running << Stop << QString() << QAbstractAnimation::Stopped; } void tst_QPropertyAnimation::operationsInStates() { /** * | pause() |start() |resume() |stop() * ----------+------------+-----------+-----------+-------------------+ * Stopped | Stopped |Running |Stopped |Stopped | * _| qWarning |restart |qWarning | | * Paused | Paused |Running |Running |Stopped | * _| | | | | * Running | Paused |Running |Running |Stopped | * | |restart |qWarning | | * ----------+------------+-----------+-----------+-------------------+ **/ QFETCH(QAbstractAnimation::State, originState); QFETCH(int, operation); QFETCH(QString, expectedWarning); QFETCH(QAbstractAnimation::State, expectedState); QObject o; o.setProperty("ole", 42); QPropertyAnimation anim(&o, "ole"); anim.setEndValue(100); QSignalSpy spy(&anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); anim.stop(); switch (originState) { case QAbstractAnimation::Stopped: break; case QAbstractAnimation::Paused: anim.start(); anim.pause(); break; case QAbstractAnimation::Running: anim.start(); break; } if (!expectedWarning.isEmpty()) { QTest::ignoreMessage(QtWarningMsg, qPrintable(expectedWarning)); } QCOMPARE(anim.state(), originState); switch (operation) { case Pause: anim.pause(); break; case Start: anim.start(); break; case Resume: anim.resume(); break; case Stop: anim.stop(); break; } QCOMPARE(anim.state(), expectedState); } #undef Pause #undef Start #undef Resume #undef Stop void tst_QPropertyAnimation::oneKeyValue() { QObject o; o.setProperty("ole", 42); QCOMPARE(o.property("ole").toInt(), 42); QPropertyAnimation animation(&o, "ole"); animation.setStartValue(43); animation.setEndValue(44); animation.setDuration(100); animation.setCurrentTime(0); QVERIFY(animation.currentValue().isValid()); QCOMPARE(animation.currentValue().toInt(), 43); QCOMPARE(o.property("ole").toInt(), 42); // remove the last key value animation.setKeyValueAt(1.0, QVariant()); // we will neither interpolate, nor update the current value // since there is only one 1 key value defined animation.setCurrentTime(100); // the animation should not have been modified QVERIFY(animation.currentValue().isValid()); QCOMPARE(animation.currentValue().toInt(), 43); QCOMPARE(o.property("ole").toInt(), 42); } void tst_QPropertyAnimation::updateOnSetKeyValues() { QObject o; o.setProperty("ole", 100); QCOMPARE(o.property("ole").toInt(), 100); QPropertyAnimation animation(&o, "ole"); animation.setStartValue(100); animation.setEndValue(200); animation.setDuration(100); animation.setCurrentTime(50); QCOMPARE(animation.currentValue().toInt(), 150); animation.setKeyValueAt(0.0, 300); QCOMPARE(animation.currentValue().toInt(), 250); o.setProperty("ole", 100); QPropertyAnimation animation2(&o, "ole"); QVariantAnimation::KeyValues kValues; kValues << QVariantAnimation::KeyValue(0.0, 100) << QVariantAnimation::KeyValue(1.0, 200); animation2.setKeyValues(kValues); animation2.setDuration(100); animation2.setCurrentTime(50); QCOMPARE(animation2.currentValue().toInt(), 150); kValues.clear(); kValues << QVariantAnimation::KeyValue(0.0, 300) << QVariantAnimation::KeyValue(1.0, 200); animation2.setKeyValues(kValues); QCOMPARE(animation2.currentValue().toInt(), animation.currentValue().toInt()); } //this class will 'throw' an error in the test lib // if the property ole is set to ErrorValue class MyErrorObject : public QObject { Q_OBJECT Q_PROPERTY(int ole READ ole WRITE setOle) public: static const int ErrorValue = 10000; MyErrorObject() : m_ole(0) { } int ole() const { return m_ole; } void setOle(int o) { QVERIFY(o != ErrorValue); m_ole = o; } private: int m_ole; }; void tst_QPropertyAnimation::restart() { //here we check that be restarting an animation //it doesn't get an bogus intermediate value (end value) //because the time is not yet reset to 0 MyErrorObject o; o.setOle(100); QCOMPARE(o.property("ole").toInt(), 100); QPropertyAnimation anim(&o, "ole"); anim.setEndValue(200); anim.start(); anim.setCurrentTime(anim.duration()); QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(o.property("ole").toInt(), 200); //we'll check that the animation never gets a wrong value when starting it //after having changed the end value anim.setEndValue(MyErrorObject::ErrorValue); anim.start(); } void tst_QPropertyAnimation::valueChanged() { qRegisterMetaType("QVariant"); //we check that we receive the valueChanged signal MyErrorObject o; o.setOle(0); QCOMPARE(o.property("ole").toInt(), 0); QPropertyAnimation anim(&o, "ole"); anim.setEndValue(5); anim.setDuration(1000); QSignalSpy spy(&anim, SIGNAL(valueChanged(QVariant))); anim.start(); QTest::qWait(anim.duration() + 50); QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(anim.currentTime(), anim.duration()); //let's check that the values go forward QCOMPARE(spy.count(), 6); //we should have got everything from 0 to 5 for (int i = 0; i < spy.count(); ++i) { QCOMPARE(qvariant_cast(spy.at(i).first()).toInt(), i); } } //this class will help us make sure that 2 animations started //at the same time also end at the same time class MySyncObject : public MyErrorObject { Q_OBJECT public: MySyncObject() : anim(this, "ole") { anim.setEndValue(1000); } public slots: void checkAnimationFinished() { QCOMPARE(anim.state(), QAbstractAnimation::Stopped); QCOMPARE(ole(), 1000); } public: QPropertyAnimation anim; }; void tst_QPropertyAnimation::twoAnimations() { MySyncObject o1, o2; o1.setOle(0); o2.setOle(0); //when the animation in o1 is finished //the animation in o2 should stop around the same time //We use a queued connection to check just after the tick from the common timer //the other way is true too QObject::connect(&o1.anim, SIGNAL(finished()), &o2, SLOT(checkAnimationFinished()), Qt::QueuedConnection); QObject::connect(&o2.anim, SIGNAL(finished()), &o1, SLOT(checkAnimationFinished()), Qt::QueuedConnection); o1.anim.start(); o2.anim.start(); QTest::qWait(o1.anim.duration() + 50); QCOMPARE(o1.anim.state(), QAbstractAnimation::Stopped); QCOMPARE(o2.anim.state(), QAbstractAnimation::Stopped); QCOMPARE(o1.ole(), 1000); QCOMPARE(o2.ole(), 1000); } class MyComposedAnimation : public QPropertyAnimation { Q_OBJECT public: MyComposedAnimation(QObject *target, const QByteArray &propertyName, const QByteArray &innerPropertyName) : QPropertyAnimation(target, propertyName) { innerAnim = new QPropertyAnimation(target, innerPropertyName); this->setEndValue(1000); innerAnim->setEndValue(1000); innerAnim->setDuration(duration() + 100); } void start() { QPropertyAnimation::start(); innerAnim->start(); } void updateState(QAbstractAnimation::State oldState, QAbstractAnimation::State newState) { QPropertyAnimation::updateState(oldState, newState); if (newState == QAbstractAnimation::Stopped) delete innerAnim; } public: QPropertyAnimation *innerAnim; }; void tst_QPropertyAnimation::deletedInUpdateCurrentTime() { // this test case reproduces an animation being deleted in the updateCurrentTime of // another animation(was causing segfault). // the deleted animation must have been started after the animation that is deleting. AnimationObject o; o.setValue(0); o.setRealValue(0.0); MyComposedAnimation composedAnimation(&o, "value", "realValue"); composedAnimation.start(); QCOMPARE(composedAnimation.state(), QAbstractAnimation::Running); QTest::qWait(composedAnimation.duration() + 50); QCOMPARE(composedAnimation.state(), QAbstractAnimation::Stopped); QCOMPARE(o.value(), 1000); } void tst_QPropertyAnimation::totalDuration() { QPropertyAnimation anim; QCOMPARE(anim.totalDuration(), 250); anim.setLoopCount(2); QCOMPARE(anim.totalDuration(), 2*250); anim.setLoopCount(-1); QCOMPARE(anim.totalDuration(), -1); anim.setDuration(0); QCOMPARE(anim.totalDuration(), 0); } QTEST_MAIN(tst_QPropertyAnimation) #include "tst_qpropertyanimation.moc"