tests/auto/qpropertyanimation/tst_qpropertyanimation.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/auto/qpropertyanimation/tst_qpropertyanimation.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1217 @@
+/****************************************************************************
+**
+** 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 <QtTest/QtTest>
+
+#include <QtCore/qpropertyanimation.h>
+#include <QtCore/qvariantanimation.h>
+#include <QtGui/qwidget.h>
+
+//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>("QAbstractAnimation::State");
+    qRegisterMetaType<QAbstractAnimation::DeletionPolicy>("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<int>("duration");
+    QTest::addColumn<int>("loopCount");
+    QTest::addColumn<int>("currentTime");
+    QTest::addColumn<int>("testCurrentTime");
+    QTest::addColumn<int>("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<bool>("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<QPropertyAnimation> 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<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->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<int> 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<QVariantAnimation> 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<QVariantAnimation> 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<QVariantAnimation> 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<int>("currentTime");
+    QTest::addColumn<int>("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>(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>(Number(42)));
+    QCOMPARE(qVariantValue<Number>(o.property("number")), Number(42));
+    {
+    qRegisterAnimationInterpolator<Number>(numberInterpolator);
+    QPropertyAnimation anim(&o, "number");
+    anim.setStartValue(qVariantFromValue<Number>(Number(0)));
+    anim.setEndValue(qVariantFromValue<Number>(Number(100)));
+    anim.setDuration(1000);
+    anim.start();
+    anim.pause();
+    anim.setCurrentTime(100);
+    Number t(qVariantValue<Number>(o.property("number")));
+    QCOMPARE(t, Number(10));
+    anim.setCurrentTime(500);
+    QCOMPARE(qVariantValue<Number>(o.property("number")), Number(50));
+    }
+    {
+    qRegisterAnimationInterpolator<QPointF>(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<QPointF>(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<QByteArray>("propertyName");
+    QTest::addColumn<QVariant>("initialValue");
+    QTest::addColumn<QVariant>("startValue");
+    QTest::addColumn<QVariant>("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<QAbstractAnimation::State>(firstChange.first()), QAbstractAnimation::Stopped);
+    //new state
+    QCOMPARE(qVariantValue<QAbstractAnimation::State>(firstChange.last()), QAbstractAnimation::Running);
+
+    //let's check the first state change
+    const QVariantList secondChange = spy.last();
+    //old state
+    QCOMPARE(qVariantValue<QAbstractAnimation::State>(secondChange.first()), QAbstractAnimation::Running);
+    //new state
+    QCOMPARE(qVariantValue<QAbstractAnimation::State>(secondChange.last()), QAbstractAnimation::Stopped);
+}
+
+#define Pause 1
+#define Start 2
+#define Resume 3
+#define Stop 4
+
+void tst_QPropertyAnimation::operationsInStates_data()
+{
+    QTest::addColumn<QAbstractAnimation::State>("originState");
+    QTest::addColumn<int>("operation");
+    QTest::addColumn<QString>("expectedWarning");
+    QTest::addColumn<QAbstractAnimation::State>("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>("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<QVariant>(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"