diff options
Diffstat (limited to 'tests')
27 files changed, 8628 insertions, 66 deletions
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 714e19d..316a695 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -75,6 +75,7 @@ SUBDIRS += bic \ qaction \ qactiongroup \ qalgorithms \ + qanimationgroup \ qapplication \ qatomicint \ qatomicpointer \ @@ -214,6 +215,7 @@ SUBDIRS += bic \ qpainter \ qpainterpath \ qpalette \ + qparallelanimationgroup \ qpathclipper \ qpen \ qpicture \ @@ -229,6 +231,7 @@ SUBDIRS += bic \ qprocess \ qprogressbar \ qprogressdialog \ + qpropertyanimation \ qpushbutton \ qqueue \ qradiobutton \ @@ -256,6 +259,7 @@ SUBDIRS += bic \ qscrollarea \ qsemaphore \ qsharedpointer \ + qsequentialanimationgroup \ qset \ qsettings \ qshortcut \ @@ -289,6 +293,7 @@ SUBDIRS += bic \ qstackedwidget \ qstandarditem \ qstandarditemmodel \ + qstate \ qstatusbar \ qstl \ qstring \ @@ -342,6 +347,7 @@ SUBDIRS += bic \ qtranslator \ qtransform \ qtransformedscreen \ + qtransition \ qtreeview \ qtreewidget \ qtreewidgetitemiterator \ diff --git a/tests/auto/qanimationgroup/qanimationgroup.pro b/tests/auto/qanimationgroup/qanimationgroup.pro new file mode 100644 index 0000000..97d33dd --- /dev/null +++ b/tests/auto/qanimationgroup/qanimationgroup.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT = core gui +SOURCES += tst_qanimationgroup.cpp + + diff --git a/tests/auto/qanimationgroup/tst_qanimationgroup.cpp b/tests/auto/qanimationgroup/tst_qanimationgroup.cpp new file mode 100644 index 0000000..2952a39 --- /dev/null +++ b/tests/auto/qanimationgroup/tst_qanimationgroup.cpp @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtCore/qanimationgroup.h> +#include <QtCore/qsequentialanimationgroup.h> +#include <QtCore/qparallelanimationgroup.h> + +//TESTED_CLASS=QAnimationGroup +//TESTED_FILES= + +Q_DECLARE_METATYPE(QAbstractAnimation::State) + +class tst_QAnimationGroup : public QObject +{ + Q_OBJECT +public: + tst_QAnimationGroup(); + virtual ~tst_QAnimationGroup(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + void construction(); + void emptyGroup(); + void setCurrentTime(); + void statesAndSignals(); + void setParentAutoAdd(); + void beginNestedGroup(); + void addChildTwice(); + void loopWithoutStartValue(); +}; + +tst_QAnimationGroup::tst_QAnimationGroup() +{ +} + +tst_QAnimationGroup::~tst_QAnimationGroup() +{ +} + +void tst_QAnimationGroup::init() +{ + qRegisterMetaType<QAbstractAnimation::State>("QAbstractAnimation::State"); +} + +void tst_QAnimationGroup::cleanup() +{ +} + +void tst_QAnimationGroup::construction() +{ + QSequentialAnimationGroup animationgroup; +} + +class AnimationObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value WRITE setValue) +public: + AnimationObject(int startValue = 0) + : v(startValue) + { } + + int value() const { return v; } + void setValue(int value) { v = value; } + + int v; +}; + +class TestAnimation : public QVariantAnimation +{ + Q_OBJECT +public: + virtual void updateCurrentValue(const QVariant &value) { Q_UNUSED(value)}; + virtual void updateState(QAbstractAnimation::State oldState, + QAbstractAnimation::State newState) + { + Q_UNUSED(oldState) + Q_UNUSED(newState) + }; +}; + +class UncontrolledAnimation : public QPropertyAnimation +{ + Q_OBJECT +public: + UncontrolledAnimation(QObject *target, const QByteArray &propertyName, QObject *parent = 0) + : QPropertyAnimation(target, propertyName, parent), id(0) + { + setDuration(250); + } + + int duration() const { return -1; /* not time driven */ } + +protected: + void timerEvent(QTimerEvent *event) + { + if (event->timerId() == id) + stop(); + } + + void updateRunning(bool running) + { + if (running) { + id = startTimer(500); + } else { + killTimer(id); + id = 0; + } + } + +private: + int id; +}; + +void tst_QAnimationGroup::emptyGroup() +{ + QSequentialAnimationGroup group; + QSignalSpy groupStateChangedSpy(&group, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + group.start(); + + QCOMPARE(groupStateChangedSpy.count(), 2); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(groupStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(groupStateChangedSpy.at(1).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + + QTest::ignoreMessage(QtWarningMsg, "QAbstractAnimation::pause: Cannot pause a stopped animation"); + group.pause(); + + QCOMPARE(groupStateChangedSpy.count(), 2); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + + group.start(); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(groupStateChangedSpy.at(2).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(groupStateChangedSpy.at(3).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + + group.stop(); + + QCOMPARE(groupStateChangedSpy.count(), 4); + QCOMPARE(group.state(), QAnimationGroup::Stopped); +} + +void tst_QAnimationGroup::setCurrentTime() +{ + AnimationObject s_o1; + AnimationObject s_o2; + AnimationObject s_o3; + AnimationObject p_o1; + AnimationObject p_o2; + AnimationObject p_o3; + AnimationObject t_o1; + AnimationObject t_o2; + + // sequence operating on same object/property + QSequentialAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a2_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a3_s_o1 = new QPropertyAnimation(&s_o1, "value"); + a2_s_o1->setLoopCount(3); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a2_s_o1); + sequence->addAnimation(a3_s_o1); + + // sequence operating on different object/properties + QAnimationGroup *sequence2 = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o2 = new QPropertyAnimation(&s_o2, "value"); + QVariantAnimation *a1_s_o3 = new QPropertyAnimation(&s_o3, "value"); + sequence2->addAnimation(a1_s_o2); + sequence2->addAnimation(a1_s_o3); + + // parallel operating on different object/properties + QAnimationGroup *parallel = new QParallelAnimationGroup(); + QVariantAnimation *a1_p_o1 = new QPropertyAnimation(&p_o1, "value"); + QVariantAnimation *a1_p_o2 = new QPropertyAnimation(&p_o2, "value"); + QVariantAnimation *a1_p_o3 = new QPropertyAnimation(&p_o3, "value"); + a1_p_o2->setLoopCount(3); + parallel->addAnimation(a1_p_o1); + parallel->addAnimation(a1_p_o2); + parallel->addAnimation(a1_p_o3); + + UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation(&t_o1, "value"); + QCOMPARE(notTimeDriven->totalDuration(), -1); + + QVariantAnimation *loopsForever = new QPropertyAnimation(&t_o2, "value"); + loopsForever->setLoopCount(-1); + QCOMPARE(loopsForever->totalDuration(), -1); + + QParallelAnimationGroup group; + group.addAnimation(sequence); + group.addAnimation(sequence2); + group.addAnimation(parallel); + group.addAnimation(notTimeDriven); + group.addAnimation(loopsForever); + + // Current time = 1 + group.setCurrentTime(1); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(sequence2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(parallel->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_p_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_p_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_p_o3->state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven->state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever->state(), QAnimationGroup::Stopped); + + QCOMPARE(group.currentTime(), 1); + QCOMPARE(sequence->currentTime(), 1); + QCOMPARE(a1_s_o1->currentTime(), 1); + QCOMPARE(a2_s_o1->currentTime(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 1); + QCOMPARE(a1_s_o3->currentTime(), 0); + QCOMPARE(a1_p_o1->currentTime(), 1); + QCOMPARE(a1_p_o2->currentTime(), 1); + QCOMPARE(a1_p_o3->currentTime(), 1); + QCOMPARE(notTimeDriven->currentTime(), 1); + QCOMPARE(loopsForever->currentTime(), 1); + + // Current time = 250 + group.setCurrentTime(250); + QCOMPARE(group.currentTime(), 250); + QCOMPARE(sequence->currentTime(), 250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 0); + QCOMPARE(a1_p_o1->currentTime(), 250); + QCOMPARE(a1_p_o2->currentTime(), 0); + QCOMPARE(a1_p_o2->currentLoop(), 1); + QCOMPARE(a1_p_o3->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 250); + QCOMPARE(loopsForever->currentTime(), 0); + QCOMPARE(loopsForever->currentLoop(), 1); + QCOMPARE(sequence->currentAnimation(), a2_s_o1); + + // Current time = 251 + group.setCurrentTime(251); + QCOMPARE(group.currentTime(), 251); + QCOMPARE(sequence->currentTime(), 251); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 1); + QCOMPARE(a2_s_o1->currentLoop(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence2->currentTime(), 251); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 1); + QCOMPARE(a1_p_o1->currentTime(), 250); + QCOMPARE(a1_p_o2->currentTime(), 1); + QCOMPARE(a1_p_o2->currentLoop(), 1); + QCOMPARE(a1_p_o3->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 251); + QCOMPARE(loopsForever->currentTime(), 1); + QCOMPARE(sequence->currentAnimation(), a2_s_o1); +} + +void tst_QAnimationGroup::statesAndSignals() +{ +} + +void tst_QAnimationGroup::setParentAutoAdd() +{ + QParallelAnimationGroup group; + QVariantAnimation *animation = new QPropertyAnimation(&group); + QCOMPARE(animation->group(), &group); +} + +void tst_QAnimationGroup::beginNestedGroup() +{ + QAnimationGroup *subGroup; + QAnimationGroup *parent = new QParallelAnimationGroup(); + + for (int i = 0; i < 10; ++i) { + if (i & 1) + subGroup = new QParallelAnimationGroup(parent); + else + subGroup = new QSequentialAnimationGroup(parent); + + QCOMPARE(parent->animationCount(), 1); + QAnimationGroup *child = static_cast<QAnimationGroup *>(parent->animationAt(0)); + + QCOMPARE(child->parent(), static_cast<QObject *>(parent)); + if (i & 1) + QVERIFY(qobject_cast<QParallelAnimationGroup *> (child)); + else + QVERIFY(qobject_cast<QSequentialAnimationGroup *> (child)); + + parent = child; + } +} + +void tst_QAnimationGroup::addChildTwice() +{ + QPropertyAnimation *subGroup; + QPropertyAnimation *subGroup2; + QAnimationGroup *parent = new QSequentialAnimationGroup(); + + subGroup = new QPropertyAnimation(); + subGroup->setParent(parent); + parent->addAnimation(subGroup); + QCOMPARE(parent->animationCount(), 1); + + parent->clearAnimations(); + + QCOMPARE(parent->animationCount(), 0); + + // adding the same item twice to a group will remove the item from its current position + // and append it to the end + subGroup = new QPropertyAnimation(parent); + subGroup2 = new QPropertyAnimation(parent); + + QCOMPARE(parent->animationCount(), 2); + QCOMPARE(parent->animationAt(0), subGroup); + QCOMPARE(parent->animationAt(1), subGroup2); + + parent->addAnimation(subGroup); + + QCOMPARE(parent->animationCount(), 2); + QCOMPARE(parent->animationAt(0), subGroup2); + QCOMPARE(parent->animationAt(1), subGroup); + + delete parent; +} + +void tst_QAnimationGroup::loopWithoutStartValue() +{ + QAnimationGroup *parent = new QSequentialAnimationGroup(); + QObject o; + o.setProperty("ole", 0); + QCOMPARE(o.property("ole").toInt(), 0); + + QPropertyAnimation anim1(&o, "ole"); + anim1.setEndValue(-50); + anim1.setDuration(100); + + QPropertyAnimation anim2(&o, "ole"); + anim2.setEndValue(50); + anim2.setDuration(100); + + parent->addAnimation(&anim1); + parent->addAnimation(&anim2); + + parent->setLoopCount(-1); + parent->start(); + + QVERIFY(anim1.startValue().isNull()); + QCOMPARE(anim1.currentValue().toInt(), 0); + QCOMPARE(parent->currentLoop(), 0); + + parent->setCurrentTime(200); + QCOMPARE(parent->currentLoop(), 1); + QCOMPARE(anim1.currentValue().toInt(), 50); + parent->stop(); +} + +QTEST_MAIN(tst_QAnimationGroup) +#include "tst_qanimationgroup.moc" diff --git a/tests/auto/qeasingcurve/qeasingcurve.pro b/tests/auto/qeasingcurve/qeasingcurve.pro new file mode 100644 index 0000000..2b66081 --- /dev/null +++ b/tests/auto/qeasingcurve/qeasingcurve.pro @@ -0,0 +1,3 @@ +load(qttest_p4) +QT = core +SOURCES += tst_qeasingcurve.cpp diff --git a/tests/auto/qeasingcurve/tst_qeasingcurve.cpp b/tests/auto/qeasingcurve/tst_qeasingcurve.cpp new file mode 100644 index 0000000..8d42e5e --- /dev/null +++ b/tests/auto/qeasingcurve/tst_qeasingcurve.cpp @@ -0,0 +1,487 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#if QT_VERSION < 0x040200 +QTEST_NOOP_MAIN +#else + +#include <qeasingcurve.h> + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QEasingCurve : public QObject { + Q_OBJECT + +public: + tst_QEasingCurve(); + virtual ~tst_QEasingCurve(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + void type(); + void propertyDefaults(); + void valueForProgress_data(); + void valueForProgress(); + void setCustomType(); + void operators(); + +protected: +}; + +tst_QEasingCurve::tst_QEasingCurve() +{ +} + +tst_QEasingCurve::~tst_QEasingCurve() +{ +} + +void tst_QEasingCurve::init() +{ +} + +void tst_QEasingCurve::cleanup() +{ +} +#include <qdebug.h> + +void tst_QEasingCurve::type() +{ + { + QEasingCurve curve(QEasingCurve::Linear); + QCOMPARE(curve.period(), 0.3); + QCOMPARE(curve.amplitude(), 1.0); + + curve.setPeriod(5); + curve.setAmplitude(3); + QCOMPARE(curve.period(), 5.0); + QCOMPARE(curve.amplitude(), 3.0); + + curve.setType(QEasingCurve::InElastic); + QCOMPARE(curve.period(), 5.0); + QCOMPARE(curve.amplitude(), 3.0); + } + + { + QEasingCurve curve(QEasingCurve::InElastic); + QCOMPARE(curve.period(), 0.3); + QCOMPARE(curve.amplitude(), 1.0); + curve.setAmplitude(2); + QCOMPARE(curve.type(), QEasingCurve::InElastic); + curve.setType(QEasingCurve::Linear); + } + + { + // check bounaries + QEasingCurve curve(QEasingCurve::InCubic); + QTest::ignoreMessage(QtWarningMsg, "QEasingCurve: Invalid curve type 9999"); + curve.setType((QEasingCurve::Type)9999); + QCOMPARE(curve.type(), QEasingCurve::InCubic); + QTest::ignoreMessage(QtWarningMsg, "QEasingCurve: Invalid curve type -9999"); + curve.setType((QEasingCurve::Type)-9999); + QCOMPARE(curve.type(), QEasingCurve::InCubic); + QTest::ignoreMessage(QtWarningMsg, QString::fromAscii("QEasingCurve: Invalid curve type %1") + .arg(QEasingCurve::NCurveTypes).toLatin1().constData()); + curve.setType(QEasingCurve::NCurveTypes); + QCOMPARE(curve.type(), QEasingCurve::InCubic); + QTest::ignoreMessage(QtWarningMsg, QString::fromAscii("QEasingCurve: Invalid curve type %1") + .arg(QEasingCurve::Custom).toLatin1().constData()); + curve.setType(QEasingCurve::Custom); + QCOMPARE(curve.type(), QEasingCurve::InCubic); + QTest::ignoreMessage(QtWarningMsg, QString::fromAscii("QEasingCurve: Invalid curve type %1") + .arg(-1).toLatin1().constData()); + curve.setType((QEasingCurve::Type)-1); + QCOMPARE(curve.type(), QEasingCurve::InCubic); + curve.setType(QEasingCurve::Linear); + QCOMPARE(curve.type(), QEasingCurve::Linear); + curve.setType(QEasingCurve::CosineCurve); + QCOMPARE(curve.type(), QEasingCurve::CosineCurve); + } +} + +void tst_QEasingCurve::propertyDefaults() +{ + { + // checks if the defaults are correct, but also demonstrates a weakness with the API. + QEasingCurve curve(QEasingCurve::InElastic); + QCOMPARE(curve.period(), 0.3); + QCOMPARE(curve.amplitude(), 1.0); + QCOMPARE(curve.overshoot(), qreal(1.70158f)); + curve.setType(QEasingCurve::InBounce); + QCOMPARE(curve.period(), 0.3); + QCOMPARE(curve.amplitude(), 1.0); + QCOMPARE(curve.overshoot(), qreal(1.70158f)); + curve.setType(QEasingCurve::Linear); + QCOMPARE(curve.period(), 0.3); + QCOMPARE(curve.amplitude(), 1.0); + QCOMPARE(curve.overshoot(), qreal(1.70158f)); + curve.setType(QEasingCurve::InElastic); + QCOMPARE(curve.period(), 0.3); + QCOMPARE(curve.amplitude(), 1.0); + QCOMPARE(curve.overshoot(), qreal(1.70158f)); + curve.setPeriod(0.4); + curve.setAmplitude(0.6); + curve.setOvershoot(1.0); + curve.setType(QEasingCurve::Linear); + QCOMPARE(curve.period(), 0.4); + QCOMPARE(curve.amplitude(), 0.6); + QCOMPARE(curve.overshoot(), 1.0); + curve.setType(QEasingCurve::InElastic); + QCOMPARE(curve.period(), 0.4); + QCOMPARE(curve.amplitude(), 0.6); + QCOMPARE(curve.overshoot(), 1.0); + } +} + +typedef QList<int> IntList; +Q_DECLARE_METATYPE(IntList) + +void tst_QEasingCurve::valueForProgress_data() +{ + QTest::addColumn<int>("type"); + QTest::addColumn<IntList>("at"); + QTest::addColumn<IntList>("expected"); + // automatically generated. + // note that values are scaled from range [0,1] to range [0, 100] in order to store them as + // integer values and avoid fp inaccuracies + + QTest::newRow("Linear") << int(QEasingCurve::Linear) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100); + + QTest::newRow("InQuad") << int(QEasingCurve::InQuad) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 1 << 4 << 9 << 16 << 25 << 36 << 48 << 64 << 81 << 100); + + QTest::newRow("OutQuad") << int(QEasingCurve::OutQuad) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 19 << 36 << 51 << 64 << 75 << 84 << 90 << 96 << 99 << 100); + + QTest::newRow("InOutQuad") << int(QEasingCurve::InOutQuad) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 2 << 8 << 18 << 32 << 50 << 68 << 82 << 92 << 98 << 100); + + QTest::newRow("OutInQuad") << int(QEasingCurve::OutInQuad) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 18 << 32 << 42 << 48 << 50 << 52 << 57 << 68 << 82 << 100); + + QTest::newRow("InCubic") << int(QEasingCurve::InCubic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 2 << 6 << 12 << 21 << 34 << 51 << 72 << 100); + + QTest::newRow("OutCubic") << int(QEasingCurve::OutCubic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 27 << 48 << 65 << 78 << 87 << 93 << 97 << 99 << 99 << 100); + + QTest::newRow("InOutCubic") << int(QEasingCurve::InOutCubic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 3 << 10 << 25 << 50 << 74 << 89 << 96 << 99 << 100); + + QTest::newRow("OutInCubic") << int(QEasingCurve::OutInCubic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 24 << 39 << 46 << 49 << 50 << 50 << 53 << 60 << 75 << 100); + + QTest::newRow("InQuart") << int(QEasingCurve::InQuart) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 0 << 2 << 6 << 12 << 24 << 40 << 65 << 100); + + QTest::newRow("OutQuart") << int(QEasingCurve::OutQuart) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 34 << 59 << 75 << 87 << 93 << 97 << 99 << 99 << 99 << 100); + + QTest::newRow("InOutQuart") << int(QEasingCurve::InOutQuart) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 1 << 6 << 20 << 50 << 79 << 93 << 98 << 99 << 100); + + QTest::newRow("OutInQuart") << int(QEasingCurve::OutInQuart) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 29 << 43 << 48 << 49 << 50 << 50 << 51 << 56 << 70 << 100); + + QTest::newRow("InQuint") << int(QEasingCurve::InQuint) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 0 << 1 << 3 << 7 << 16 << 32 << 59 << 100); + + QTest::newRow("OutQuint") << int(QEasingCurve::OutQuint) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 40 << 67 << 83 << 92 << 96 << 98 << 99 << 99 << 99 << 100); + + QTest::newRow("InOutQuint") << int(QEasingCurve::InOutQuint) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 3 << 16 << 50 << 83 << 96 << 99 << 99 << 100); + + QTest::newRow("OutInQuint") << int(QEasingCurve::OutInQuint) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 33 << 46 << 49 << 49 << 50 << 50 << 50 << 53 << 66 << 100); + + QTest::newRow("InSine") << int(QEasingCurve::InSine) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 1 << 4 << 10 << 19 << 29 << 41 << 54 << 69 << 84 << 100); + + QTest::newRow("OutSine") << int(QEasingCurve::OutSine) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 15 << 30 << 45 << 58 << 70 << 80 << 89 << 95 << 98 << 100); + + QTest::newRow("InOutSine") << int(QEasingCurve::InOutSine) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 2 << 9 << 20 << 34 << 49 << 65 << 79 << 90 << 97 << 100); + + QTest::newRow("OutInSine") << int(QEasingCurve::OutInSine) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 15 << 29 << 40 << 47 << 50 << 52 << 59 << 70 << 84 << 100); + + QTest::newRow("InExpo") << int(QEasingCurve::InExpo) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 0 << 1 << 3 << 6 << 12 << 24 << 49 << 100); + + QTest::newRow("OutExpo") << int(QEasingCurve::OutExpo) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 50 << 75 << 87 << 93 << 96 << 98 << 99 << 99 << 99 << 100); + + QTest::newRow("InOutExpo") << int(QEasingCurve::InOutExpo) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 3 << 12 << 50 << 87 << 96 << 99 << 99 << 100); + + QTest::newRow("OutInExpo") << int(QEasingCurve::OutInExpo) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 37 << 46 << 49 << 49 << 50 << 50 << 50 << 53 << 62 << 100); + + QTest::newRow("InCirc") << int(QEasingCurve::InCirc) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 2 << 4 << 8 << 13 << 19 << 28 << 40 << 56 << 100); + + QTest::newRow("OutCirc") << int(QEasingCurve::OutCirc) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 43 << 59 << 71 << 80 << 86 << 91 << 95 << 97 << 99 << 100); + + QTest::newRow("InOutCirc") << int(QEasingCurve::InOutCirc) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 1 << 4 << 9 << 20 << 50 << 80 << 89 << 95 << 98 << 100); + + QTest::newRow("OutInCirc") << int(QEasingCurve::OutInCirc) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 29 << 40 << 45 << 48 << 50 << 51 << 54 << 60 << 70 << 100); + + QTest::newRow("InElastic") << int(QEasingCurve::InElastic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << 0 << 1 << -1 << -3 << 12 << -12 << -25 << 100); + + QTest::newRow("OutElastic") << int(QEasingCurve::OutElastic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 125 << 112 << 87 << 103 << 101 << 98 << 100 << 100 << 99 << 100); + + QTest::newRow("InOutElastic") << int(QEasingCurve::InOutElastic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 0 << 0 << -1 << -6 << 50 << 106 << 101 << 99 << 100 << 100); + + QTest::newRow("OutInElastic") << int(QEasingCurve::OutInElastic) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 37 << 56 << 49 << 49 << 50 << 49 << 50 << 53 << 24 << 100); + + QTest::newRow("InBack") << int(QEasingCurve::InBack) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << -1 << -4 << -8 << -9 << -8 << -2 << 9 << 29 << 59 << 100); + + QTest::newRow("OutBack") << int(QEasingCurve::OutBack) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 40 << 70 << 90 << 102 << 108 << 109 << 108 << 104 << 101 << 100); + + QTest::newRow("InOutBack") << int(QEasingCurve::InOutBack) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << -3 << -9 << -7 << 8 << 50 << 91 << 107 << 109 << 103 << 100); + + QTest::newRow("OutInBack") << int(QEasingCurve::OutInBack) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 35 << 51 << 54 << 52 << 50 << 47 << 45 << 48 << 64 << 100); + + QTest::newRow("InBounce") << int(QEasingCurve::InBounce) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 1 << 6 << 6 << 22 << 23 << 9 << 31 << 69 << 92 << 100); + + QTest::newRow("OutBounce") << int(QEasingCurve::OutBounce) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 7 << 30 << 68 << 90 << 76 << 77 << 93 << 94 << 98 << 100); + + QTest::newRow("InOutBounce") << int(QEasingCurve::InOutBounce) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 3 << 11 << 4 << 34 << 50 << 65 << 95 << 88 << 97 << 100); + + QTest::newRow("OutInBounce") << int(QEasingCurve::OutInBounce) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 15 << 40 << 27 << 43 << 50 << 56 << 72 << 58 << 84 << 100); + + QTest::newRow("InCurve") << int(QEasingCurve::InCurve) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 2 << 10 << 23 << 37 << 50 << 60 << 70 << 80 << 90 << 100); + + QTest::newRow("OutCurve") << int(QEasingCurve::OutCurve) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 10 << 20 << 30 << 39 << 50 << 62 << 76 << 89 << 97 << 100); + + QTest::newRow("SineCurve") << int(QEasingCurve::SineCurve) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 0 << 9 << 34 << 65 << 90 << 100 << 90 << 65 << 34 << 9 << 0); + + QTest::newRow("CosineCurve") << int(QEasingCurve::CosineCurve) + << (IntList() << 0 << 10 << 20 << 30 << 40 << 50 << 60 << 70 << 80 << 90 << 100) + << (IntList() << 50 << 79 << 97 << 97 << 79 << 50 << 20 << 2 << 2 << 20 << 49); + +} + + +void tst_QEasingCurve::valueForProgress() +{ +#if 0 + // used to generate data tables... + QFile out; + out.open(stdout, QIODevice::WriteOnly); + for (int c = QEasingCurve::Linear; c < QEasingCurve::NCurveTypes - 1; ++c) { + QEasingCurve curve((QEasingCurve::Type)c); + QMetaObject mo = QEasingCurve::staticMetaObject; + QString strCurve = QLatin1String(mo.enumerator(mo.indexOfEnumerator("Type")).key(c)); + QString strInputs; + QString strOutputs; + + for (int t = 0; t <= 100; t+= 10) { + qreal ease = curve.valueForProgress(t/qreal(100)); + strInputs += QString::fromAscii(" << %1").arg(t); + strOutputs += QString::fromAscii(" << %1").arg(int(100*ease)); + + } + QString str = QString::fromAscii(" QTest::newRow(\"%1\") << int(QEasingCurve::%2)\n" + "\t\t << (IntList() %3)\n" + "\t\t << (IntList() %4);\n\n") + .arg(strCurve) + .arg(strCurve) + .arg(strInputs) + .arg(strOutputs); + out.write(str.toLatin1().constData()); + } + out.close(); + exit(1); +#else + QFETCH(int, type); + QFETCH(IntList, at); + QFETCH(IntList, expected); + + QEasingCurve curve((QEasingCurve::Type)type); + for (int i = 0; i < at.count(); ++i) { + qreal ease = curve.valueForProgress(at.at(i)/qreal(100)); + int ex = expected.at(i); + QCOMPARE(int(100*ease), ex); + } +#endif +} + +static qreal discreteEase(qreal progress) +{ + return qFloor(progress * 10) / qreal(10.0); +} + +void tst_QEasingCurve::setCustomType() +{ + QEasingCurve curve; + curve.setCustomType(&discreteEase); + QCOMPARE(curve.type(), QEasingCurve::Custom); + QCOMPARE(curve.valueForProgress(0.0), 0.0); + QCOMPARE(curve.valueForProgress(0.05), 0.0); + QCOMPARE(curve.valueForProgress(0.10), 0.1); + QCOMPARE(curve.valueForProgress(0.15), 0.1); + QCOMPARE(curve.valueForProgress(0.20), 0.2); + QCOMPARE(curve.valueForProgress(0.25), 0.2); + QCOMPARE(curve.valueForProgress(0.30), 0.3); + QCOMPARE(curve.valueForProgress(0.35), 0.3); + QCOMPARE(curve.valueForProgress(0.999999), 0.9); + + curve.setType(QEasingCurve::Linear); + QCOMPARE(curve.type(), QEasingCurve::Linear); + QCOMPARE(curve.valueForProgress(0.0), 0.0); + QCOMPARE(curve.valueForProgress(0.1), 0.1); + QCOMPARE(curve.valueForProgress(0.5), 0.5); + QCOMPARE(curve.valueForProgress(0.99), 0.99); +} + +void tst_QEasingCurve::operators() +{ + // operator= + QEasingCurve curve; + QEasingCurve curve2; + curve.setCustomType(&discreteEase); + curve2 = curve; + QCOMPARE(curve2.type(), QEasingCurve::Custom); + QCOMPARE(curve2.valueForProgress(0.0), 0.0); + QCOMPARE(curve2.valueForProgress(0.05), 0.0); + QCOMPARE(curve2.valueForProgress(0.15), 0.1); + QCOMPARE(curve2.valueForProgress(0.25), 0.2); + QCOMPARE(curve2.valueForProgress(0.35), 0.3); + QCOMPARE(curve2.valueForProgress(0.999999), 0.9); + + // operator== + curve.setType(QEasingCurve::InBack); + curve2 = curve; + curve2.setOvershoot(qreal(1.70158f)); + QCOMPARE(curve.overshoot(), curve2.overshoot()); + QVERIFY(curve2 == curve); + + curve.setOvershoot(3.0); + QVERIFY(curve2 != curve); + curve2.setOvershoot(3.0); + QVERIFY(curve2 == curve); + + curve2.setType(QEasingCurve::Linear); + QCOMPARE(curve.overshoot(), curve2.overshoot()); + QVERIFY(curve2 != curve); + curve2.setType(QEasingCurve::InBack); + QCOMPARE(curve.overshoot(), curve2.overshoot()); + QVERIFY(curve2 == curve); +} + + +QTEST_MAIN(tst_QEasingCurve) +#include "tst_qeasingcurve.moc" + +#endif //QT_VERSION diff --git a/tests/auto/qmake/testdata/bundle-spaces/some-file b/tests/auto/qmake/testdata/bundle-spaces/some-file index e69de29..9975dba 100644 --- a/tests/auto/qmake/testdata/bundle-spaces/some-file +++ b/tests/auto/qmake/testdata/bundle-spaces/some-file @@ -0,0 +1,6 @@ +all: + C:\git\qt-kinetic-animations\bin\qmake qdir.pro -o Makefile -spec win32-msvc2008 + nmake -f Makefile +first: all +qmake: + C:\git\qt-kinetic-animations\bin\qmake qdir.pro -o Makefile -spec win32-msvc2008 diff --git a/tests/auto/qparallelanimationgroup/qparallelanimationgroup.pro b/tests/auto/qparallelanimationgroup/qparallelanimationgroup.pro new file mode 100644 index 0000000..f2cacd3 --- /dev/null +++ b/tests/auto/qparallelanimationgroup/qparallelanimationgroup.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT = core gui +SOURCES += tst_qparallelanimationgroup.cpp + + diff --git a/tests/auto/qparallelanimationgroup/tst_qparallelanimationgroup.cpp b/tests/auto/qparallelanimationgroup/tst_qparallelanimationgroup.cpp new file mode 100644 index 0000000..f2ab57a --- /dev/null +++ b/tests/auto/qparallelanimationgroup/tst_qparallelanimationgroup.cpp @@ -0,0 +1,834 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <QtCore/qparallelanimationgroup.h> + +//TESTED_CLASS=QParallelAnimationGroup +//TESTED_FILES= + +Q_DECLARE_METATYPE(QAbstractAnimation::State) + +class tst_QParallelAnimationGroup : public QObject +{ + Q_OBJECT +public: + tst_QParallelAnimationGroup(); + virtual ~tst_QParallelAnimationGroup(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + void construction(); + void setCurrentTime(); + void clearGroup(); + void propagateGroupUpdateToChildren(); + void updateChildrenWithRunningGroup(); + void deleteChildrenWithRunningGroup(); + void startChildrenWithStoppedGroup(); + void stopGroupWithRunningChild(); + void startGroupWithRunningChild(); + void zeroDurationAnimation(); + void stopUncontrolledAnimations(); + void loopCount_data(); + void loopCount(); + void autoAdd(); +}; + +tst_QParallelAnimationGroup::tst_QParallelAnimationGroup() +{ +} + +tst_QParallelAnimationGroup::~tst_QParallelAnimationGroup() +{ +} + +void tst_QParallelAnimationGroup::init() +{ + qRegisterMetaType<QAbstractAnimation::State>("QAbstractAnimation::State"); +} + +void tst_QParallelAnimationGroup::cleanup() +{ +} + +void tst_QParallelAnimationGroup::construction() +{ + QParallelAnimationGroup animationgroup; +} + +class AnimationObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value WRITE setValue) +public: + AnimationObject(int startValue = 0) + : v(startValue) + { } + + int value() const { return v; } + void setValue(int value) { v = value; } + + int v; +}; + +class TestAnimation : public QVariantAnimation +{ + Q_OBJECT +public: + virtual void updateCurrentValue(const QVariant &value) { Q_UNUSED(value)}; + virtual void updateState(QAbstractAnimation::State oldState, + QAbstractAnimation::State newState) + { + Q_UNUSED(oldState) + Q_UNUSED(newState) + }; +}; + +class TestAnimation2 : public QVariantAnimation +{ + Q_OBJECT +public: + TestAnimation2(QAbstractAnimation *animation) : QVariantAnimation(animation) {} + TestAnimation2(int duration, QAbstractAnimation *animation) : QVariantAnimation(animation), m_duration(duration) {} + + virtual void updateCurrentValue(const QVariant &value) { Q_UNUSED(value)}; + virtual void updateState(QAbstractAnimation::State oldState, + QAbstractAnimation::State newState) + { + Q_UNUSED(oldState) + Q_UNUSED(newState) + }; + + virtual int duration() const { + return m_duration; + } +private: + int m_duration; +}; + +class UncontrolledAnimation : public QPropertyAnimation +{ + Q_OBJECT +public: + UncontrolledAnimation(QObject *target, const QByteArray &propertyName, QObject *parent = 0) + : QPropertyAnimation(target, propertyName, parent), id(0) + { + setDuration(250); + } + + int duration() const { return -1; /* not time driven */ } + +protected: + void timerEvent(QTimerEvent *event) + { + if (event->timerId() == id) + stop(); + } + + void updateRunning(bool running) + { + if (running) { + id = startTimer(500); + } else { + killTimer(id); + id = 0; + } + } + +private: + int id; +}; + +void tst_QParallelAnimationGroup::setCurrentTime() +{ + AnimationObject p_o1; + AnimationObject p_o2; + AnimationObject p_o3; + AnimationObject t_o1; + AnimationObject t_o2; + + // parallel operating on different object/properties + QAnimationGroup *parallel = new QParallelAnimationGroup(); + QVariantAnimation *a1_p_o1 = new QPropertyAnimation(&p_o1, "value"); + QVariantAnimation *a1_p_o2 = new QPropertyAnimation(&p_o2, "value"); + QVariantAnimation *a1_p_o3 = new QPropertyAnimation(&p_o3, "value"); + a1_p_o2->setLoopCount(3); + parallel->addAnimation(a1_p_o1); + parallel->addAnimation(a1_p_o2); + parallel->addAnimation(a1_p_o3); + + UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation(&t_o1, "value"); + QCOMPARE(notTimeDriven->totalDuration(), -1); + + QVariantAnimation *loopsForever = new QPropertyAnimation(&t_o2, "value"); + loopsForever->setLoopCount(-1); + QCOMPARE(loopsForever->totalDuration(), -1); + + QParallelAnimationGroup group; + group.addAnimation(parallel); + group.addAnimation(notTimeDriven); + group.addAnimation(loopsForever); + + // Current time = 1 + group.setCurrentTime(1); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(parallel->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_p_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_p_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_p_o3->state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven->state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever->state(), QAnimationGroup::Stopped); + + QCOMPARE(group.currentTime(), 1); + QCOMPARE(a1_p_o1->currentTime(), 1); + QCOMPARE(a1_p_o2->currentTime(), 1); + QCOMPARE(a1_p_o3->currentTime(), 1); + QCOMPARE(notTimeDriven->currentTime(), 1); + QCOMPARE(loopsForever->currentTime(), 1); + + // Current time = 250 + group.setCurrentTime(250); + QCOMPARE(group.currentTime(), 250); + QCOMPARE(a1_p_o1->currentTime(), 250); + QCOMPARE(a1_p_o2->currentTime(), 0); + QCOMPARE(a1_p_o2->currentLoop(), 1); + QCOMPARE(a1_p_o3->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 250); + QCOMPARE(loopsForever->currentTime(), 0); + QCOMPARE(loopsForever->currentLoop(), 1); + + // Current time = 251 + group.setCurrentTime(251); + QCOMPARE(group.currentTime(), 251); + QCOMPARE(a1_p_o1->currentTime(), 250); + QCOMPARE(a1_p_o2->currentTime(), 1); + QCOMPARE(a1_p_o2->currentLoop(), 1); + QCOMPARE(a1_p_o3->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 251); + QCOMPARE(loopsForever->currentTime(), 1); +} + +void tst_QParallelAnimationGroup::clearGroup() +{ + QParallelAnimationGroup group; + + for (int i = 0; i < 10; ++i) { + new QParallelAnimationGroup(&group); + } + + QCOMPARE(group.animationCount(), 10); + + int count = group.animationCount(); + QPointer<QAbstractAnimation> *children = new QPointer<QAbstractAnimation>[count]; + for (int i = 0; i < count; ++i) { + QVERIFY(group.animationAt(i) != 0); + children[i] = group.animationAt(i); + } + + group.clearAnimations(); + QCOMPARE(group.animationCount(), 0); + QCOMPARE(group.currentTime(), 0); + for (int i = 0; i < count; ++i) + QCOMPARE(children[i], QPointer<QAbstractAnimation>()); + + delete[] children; +} + +void tst_QParallelAnimationGroup::propagateGroupUpdateToChildren() +{ + // this test verifies if group state changes are updating its children correctly + QParallelAnimationGroup group; + + QObject o; + o.setProperty("ole", 42); + QCOMPARE(o.property("ole").toInt(), 42); + + QPropertyAnimation anim1(&o, "ole"); + anim1.setEndValue(43); + anim1.setDuration(100); + QVERIFY(!anim1.currentValue().isValid()); + QCOMPARE(anim1.currentValue().toInt(), 0); + QCOMPARE(o.property("ole").toInt(), 42); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QVERIFY(anim2.currentValue().isValid()); + QCOMPARE(anim2.currentValue().toInt(), 0); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + group.start(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Running); + + group.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(anim1.state(), QAnimationGroup::Paused); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); +} + +void tst_QParallelAnimationGroup::updateChildrenWithRunningGroup() +{ + // assert that its possible to modify a child's state directly while their group is running + QParallelAnimationGroup group; + + TestAnimation anim; + anim.setStartValue(0); + anim.setEndValue(100); + anim.setDuration(200); + + QSignalSpy groupStateChangedSpy(&group, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy childStateChangedSpy(&anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QCOMPARE(groupStateChangedSpy.count(), 0); + QCOMPARE(childStateChangedSpy.count(), 0); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim); + + group.start(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Running); + + QCOMPARE(groupStateChangedSpy.count(), 1); + QCOMPARE(childStateChangedSpy.count(), 1); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(groupStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(childStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + + // starting directly a running child will not have any effect + anim.start(); + + QCOMPARE(groupStateChangedSpy.count(), 1); + QCOMPARE(childStateChangedSpy.count(), 1); + + anim.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Paused); + + // in the animation stops directly, the group will still be running + anim.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Stopped); +} + +void tst_QParallelAnimationGroup::deleteChildrenWithRunningGroup() +{ + // test if children can be activated when their group is stopped + QParallelAnimationGroup group; + + QVariantAnimation *anim1 = new TestAnimation; + anim1->setStartValue(0); + anim1->setEndValue(100); + anim1->setDuration(200); + group.addAnimation(anim1); + + QCOMPARE(group.duration(), anim1->duration()); + + group.start(); + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1->state(), QAnimationGroup::Running); + + QTest::qWait(50); + QVERIFY(group.currentTime() > 0); + + delete anim1; + QVERIFY(group.animationCount() == 0); + QCOMPARE(group.duration(), 0); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(group.currentTime(), 0); //that's the invariant +} + +void tst_QParallelAnimationGroup::startChildrenWithStoppedGroup() +{ + // test if children can be activated when their group is stopped + QParallelAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(200); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + anim1.start(); + anim2.start(); + anim2.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); +} + +void tst_QParallelAnimationGroup::stopGroupWithRunningChild() +{ + // children that started independently will not be affected by a group stop + QParallelAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(200); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + anim1.start(); + anim2.start(); + anim2.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); + + anim1.stop(); + anim2.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); +} + +void tst_QParallelAnimationGroup::startGroupWithRunningChild() +{ + // as the group has precedence over its children, starting a group will restart all the children + QParallelAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(200); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QSignalSpy stateChangedSpy1(&anim1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy stateChangedSpy2(&anim2, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QCOMPARE(stateChangedSpy1.count(), 0); + QCOMPARE(stateChangedSpy2.count(), 0); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + anim1.start(); + anim2.start(); + anim2.pause(); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy1.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy2.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy2.at(1).at(1)), + QAnimationGroup::Paused); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); + + group.start(); + + QCOMPARE(stateChangedSpy1.count(), 3); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy1.at(1).at(1)), + QAnimationGroup::Stopped); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy1.at(2).at(1)), + QAnimationGroup::Running); + + QCOMPARE(stateChangedSpy2.count(), 4); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy2.at(2).at(1)), + QAnimationGroup::Stopped); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy2.at(3).at(1)), + QAnimationGroup::Running); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Running); +} + +void tst_QParallelAnimationGroup::zeroDurationAnimation() +{ + QParallelAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(0); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(100); + + QSignalSpy stateChangedSpy1(&anim1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy finishedSpy1(&anim1, SIGNAL(finished())); + + QSignalSpy stateChangedSpy2(&anim2, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy finishedSpy2(&anim2, SIGNAL(finished())); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + QCOMPARE(stateChangedSpy1.count(), 0); + group.start(); + QCOMPARE(stateChangedSpy1.count(), 2); + QCOMPARE(finishedSpy1.count(), 1); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy1.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy1.at(1).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(stateChangedSpy2.count(), 1); + QCOMPARE(finishedSpy2.count(), 0); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy1.at(0).at(1)), + QAnimationGroup::Running); + + + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Running); + QCOMPARE(group.state(), QAnimationGroup::Running); + + + group.stop(); + group.setLoopCount(4); + stateChangedSpy1.clear(); + stateChangedSpy2.clear(); + + group.start(); + QCOMPARE(stateChangedSpy1.count(), 2); + QCOMPARE(stateChangedSpy2.count(), 1); + group.setCurrentTime(50); + QCOMPARE(stateChangedSpy1.count(), 2); + QCOMPARE(stateChangedSpy2.count(), 1); + group.setCurrentTime(150); + QCOMPARE(stateChangedSpy1.count(), 4); + QCOMPARE(stateChangedSpy2.count(), 3); + group.setCurrentTime(50); + QCOMPARE(stateChangedSpy1.count(), 6); + QCOMPARE(stateChangedSpy2.count(), 5); + +} + +void tst_QParallelAnimationGroup::stopUncontrolledAnimations() +{ + QParallelAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(0); + + AnimationObject o1; + UncontrolledAnimation notTimeDriven(&o1, "value"); + QCOMPARE(notTimeDriven.totalDuration(), -1); + + TestAnimation loopsForever; + loopsForever.setStartValue(0); + loopsForever.setEndValue(100); + loopsForever.setDuration(100); + loopsForever.setLoopCount(-1); + + QSignalSpy stateChangedSpy(&anim1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + group.addAnimation(&anim1); + group.addAnimation(¬TimeDriven); + group.addAnimation(&loopsForever); + + group.start(); + + QCOMPARE(stateChangedSpy.count(), 2); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy.at(1).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Running); + QCOMPARE(loopsForever.state(), QAnimationGroup::Running); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + + notTimeDriven.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever.state(), QAnimationGroup::Running); + + loopsForever.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever.state(), QAnimationGroup::Stopped); +} + +struct AnimState { + AnimState(int time = -1) : time(time), state(-1) {} + AnimState(int time, int state) : time(time), state(state) {} + int time; + int state; +}; + +#define Running QAbstractAnimation::Running +#define Stopped QAbstractAnimation::Stopped + +Q_DECLARE_METATYPE(AnimState) +void tst_QParallelAnimationGroup::loopCount_data() +{ + QTest::addColumn<bool>("directionBackward"); + QTest::addColumn<int>("setLoopCount"); + QTest::addColumn<int>("initialGroupTime"); + QTest::addColumn<int>("currentGroupTime"); + QTest::addColumn<AnimState>("expected1"); + QTest::addColumn<AnimState>("expected2"); + QTest::addColumn<AnimState>("expected3"); + + // D U R A T I O N + // 100 60*2 0 + // direction = Forward + QTest::newRow("50") << false << 3 << 0 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("100") << false << 3 << 0 << 100 << AnimState(100 ) << AnimState( 40, Running) << AnimState( 0, Stopped); + QTest::newRow("110") << false << 3 << 0 << 110 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("120") << false << 3 << 0 << 120 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped); + + QTest::newRow("170") << false << 3 << 0 << 170 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("220") << false << 3 << 0 << 220 << AnimState(100 ) << AnimState( 40, Running) << AnimState( 0, Stopped); + QTest::newRow("230") << false << 3 << 0 << 230 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("240") << false << 3 << 0 << 240 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped); + + QTest::newRow("290") << false << 3 << 0 << 290 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("340") << false << 3 << 0 << 340 << AnimState(100 ) << AnimState( 40, Running) << AnimState( 0, Stopped); + QTest::newRow("350") << false << 3 << 0 << 350 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("360") << false << 3 << 0 << 360 << AnimState(100, Stopped) << AnimState( 60 ) << AnimState( 0, Stopped); + + QTest::newRow("410") << false << 3 << 0 << 410 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped); + QTest::newRow("460") << false << 3 << 0 << 460 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped); + QTest::newRow("470") << false << 3 << 0 << 470 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped); + QTest::newRow("480") << false << 3 << 0 << 480 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped); + + // direction = Forward, rewind + QTest::newRow("120-110") << false << 3 << 120 << 110 << AnimState( 0, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("120-50") << false << 3 << 120 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("120-0") << false << 3 << 120 << 0 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped); + QTest::newRow("300-110") << false << 3 << 300 << 110 << AnimState( 0, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("300-50") << false << 3 << 300 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("300-0") << false << 3 << 300 << 0 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped); + QTest::newRow("115-105") << false << 3 << 115 << 105 << AnimState( 42, Stopped) << AnimState( 45, Running) << AnimState( 0, Stopped); + + // direction = Backward + QTest::newRow("b120-120") << true << 3 << 120 << 120 << AnimState( 42, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped); + QTest::newRow("b120-110") << true << 3 << 120 << 110 << AnimState( 42, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("b120-100") << true << 3 << 120 << 100 << AnimState(100, Running) << AnimState( 40, Running) << AnimState( 0, Stopped); + QTest::newRow("b120-50") << true << 3 << 120 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("b120-0") << true << 3 << 120 << 0 << AnimState( 0, Stopped) << AnimState( 0, Stopped) << AnimState( 0, Stopped); + QTest::newRow("b360-170") << true << 3 << 360 << 170 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("b360-220") << true << 3 << 360 << 220 << AnimState(100, Running) << AnimState( 40, Running) << AnimState( 0, Stopped); + QTest::newRow("b360-210") << true << 3 << 360 << 210 << AnimState( 90, Running) << AnimState( 30, Running) << AnimState( 0, Stopped); + QTest::newRow("b360-120") << true << 3 << 360 << 120 << AnimState( 0, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped); + + // rewind, direction = Backward + QTest::newRow("b50-110") << true << 3 << 50 << 110 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + QTest::newRow("b50-120") << true << 3 << 50 << 120 << AnimState(100, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped); + QTest::newRow("b50-140") << true << 3 << 50 << 140 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped); + QTest::newRow("b50-240") << true << 3 << 50 << 240 << AnimState(100, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped); + QTest::newRow("b50-260") << true << 3 << 50 << 260 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped); + QTest::newRow("b50-350") << true << 3 << 50 << 350 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + + // infinite looping + QTest::newRow("inf1220") << false << -1 << 0 << 1220 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped); + QTest::newRow("inf1310") << false << -1 << 0 << 1310 << AnimState( 100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + // infinite looping, direction = Backward (will only loop once) + QTest::newRow("b.inf120-120") << true << -1 << 120 << 120 << AnimState( 42, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped); + QTest::newRow("b.inf120-20") << true << -1 << 120 << 20 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped); + QTest::newRow("b.inf120-110") << true << -1 << 120 << 110 << AnimState( 42, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped); + + +} + +void tst_QParallelAnimationGroup::loopCount() +{ + QFETCH(bool, directionBackward); + QFETCH(int, setLoopCount); + QFETCH(int, initialGroupTime); + QFETCH(int, currentGroupTime); + QFETCH(AnimState, expected1); + QFETCH(AnimState, expected2); + QFETCH(AnimState, expected3); + + QParallelAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(100); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(60); //total 120 + anim2.setLoopCount(2); + + TestAnimation anim3; + anim3.setStartValue(0); + anim3.setEndValue(100); + anim3.setDuration(0); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + group.addAnimation(&anim3); + + group.setLoopCount(setLoopCount); + if (initialGroupTime >= 0) + group.setCurrentTime(initialGroupTime); + if (directionBackward) + group.setDirection(QAbstractAnimation::Backward); + + group.start(); + if (initialGroupTime >= 0) + group.setCurrentTime(initialGroupTime); + + anim1.setCurrentTime(42); // 42 is "untouched" + anim2.setCurrentTime(42); + + group.setCurrentTime(currentGroupTime); + + QCOMPARE(anim1.currentTime(), expected1.time); + QCOMPARE(anim2.currentTime(), expected2.time); + QCOMPARE(anim3.currentTime(), expected3.time); + + if (expected1.state >=0) + QCOMPARE(int(anim1.state()), expected1.state); + if (expected2.state >=0) + QCOMPARE(int(anim2.state()), expected2.state); + if (expected3.state >=0) + QCOMPARE(int(anim3.state()), expected3.state); + +} + +void tst_QParallelAnimationGroup::autoAdd() +{ + QParallelAnimationGroup group; + QCOMPARE(group.duration(), 0); + TestAnimation2 *test = new TestAnimation2(250, &group); // 0, duration = 250; + QCOMPARE(test->group(), &group); + QCOMPARE(test->duration(), 250); + QCOMPARE(group.duration(), 250); + + test = new TestAnimation2(750, &group); // 1 + QCOMPARE(test->group(), &group); + QCOMPARE(group.duration(), 750); + test = new TestAnimation2(500, &group); // 2 + QCOMPARE(test->group(), &group); + QCOMPARE(group.duration(), 750); + + delete group.animationAt(1); // remove the one with duration = 750 + QCOMPARE(group.duration(), 500); + + delete group.animationAt(1); // remove the one with duration = 500 + QCOMPARE(group.duration(), 250); + + test = static_cast<TestAnimation2*>(group.animationAt(0)); + test->setParent(0); // remove the last one (with duration = 250) + QCOMPARE(test->group(), static_cast<QAnimationGroup*>(0)); + QCOMPARE(group.duration(), 0); +} + +QTEST_MAIN(tst_QParallelAnimationGroup) +#include "tst_qparallelanimationgroup.moc" diff --git a/tests/auto/qpropertyanimation/qpropertyanimation.pro b/tests/auto/qpropertyanimation/qpropertyanimation.pro new file mode 100644 index 0000000..6d6ddbf --- /dev/null +++ b/tests/auto/qpropertyanimation/qpropertyanimation.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT = core gui +SOURCES += tst_qpropertyanimation.cpp + + diff --git a/tests/auto/qpropertyanimation/tst_qpropertyanimation.cpp b/tests/auto/qpropertyanimation/tst_qpropertyanimation.cpp new file mode 100644 index 0000000..7e910d4 --- /dev/null +++ b/tests/auto/qpropertyanimation/tst_qpropertyanimation.cpp @@ -0,0 +1,919 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@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 msecs) + { + QPropertyAnimation::updateCurrentTime(msecs); + if (msecs >= QPropertyAnimation::duration()) + stop(); + } +}; + +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 playForwardBackward(); + void interpolated(); + void setStartEndValues(); + void zeroDurationStart(); + void operationsInStates_data(); + void operationsInStates(); + void oneKeyValue(); + void updateOnSetKeyValues(); +}; + +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) + { } + + 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 = uncontrolled ? new UncontrolledAnimation : new QPropertyAnimation; + 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); + QCOMPARE(o.property("ole").toInt(), 42); + animation.setDuration(0); + 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"); + 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"); + 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"); + 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); + + 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::playForwardBackward() +{ + QObject o; + o.setProperty("ole", 0); + QCOMPARE(o.property("ole").toInt(), 0); + + QPropertyAnimation anim(&o, "ole"); + 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)); + } +} + +void tst_QPropertyAnimation::setStartEndValues() +{ + //this tests the start value, end value and default start value + QObject o; + o.setProperty("ole", 42); + QPropertyAnimation anim(&o, "ole"); + QVariantAnimation::KeyValues values; + QCOMPARE(anim.keyValues(), values); + + //let's add a start value + anim.setStartValue(0); + values << QVariantAnimation::KeyValue(0, 0); + QCOMPARE(anim.keyValues(), values); + + anim.setEndValue(10); + values << QVariantAnimation::KeyValue(1, 10); + QCOMPARE(anim.keyValues(), values); + + //now we can play with objects + QCOMPARE(o.property("ole").toInt(), 42); + QCOMPARE(o.property("ole").toInt(), 42); + anim.start(); + QVERIFY(anim.startValue().isValid()); + QCOMPARE(o.property("ole"), anim.startValue()); + + //now we remove the explicit start value and test the implicit one + anim.stop(); + o.setProperty("ole", 42); + values.remove(0); + anim.setStartValue(QVariant()); //reset the start value + QCOMPARE(anim.keyValues(), values); + QVERIFY(!anim.startValue().isValid()); + anim.start(); + QCOMPARE(o.property("ole").toInt(), 42); + anim.setCurrentTime(anim.duration()/2); + QCOMPARE(o.property("ole").toInt(), 26); //just in the middle of the animation + anim.setCurrentTime(anim.duration()); + QCOMPARE(anim.state(), QAnimationGroup::Stopped); //it should have stopped + QVERIFY(anim.endValue().isValid()); + QCOMPARE(o.property("ole"), anim.endValue()); //end of the animations + + //now we set back the startValue + anim.setStartValue(5); + QVERIFY(anim.startValue().isValid()); + anim.start(); + QCOMPARE(o.property("ole").toInt(), 5); +} + +void tst_QPropertyAnimation::zeroDurationStart() +{ + QPropertyAnimation 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"); + 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()); +} + +QTEST_MAIN(tst_QPropertyAnimation) +#include "tst_qpropertyanimation.moc" diff --git a/tests/auto/qsequentialanimationgroup/qsequentialanimationgroup.pro b/tests/auto/qsequentialanimationgroup/qsequentialanimationgroup.pro new file mode 100644 index 0000000..ad861c3 --- /dev/null +++ b/tests/auto/qsequentialanimationgroup/qsequentialanimationgroup.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT = core gui +SOURCES += tst_qsequentialanimationgroup.cpp + + diff --git a/tests/auto/qsequentialanimationgroup/tst_qsequentialanimationgroup.cpp b/tests/auto/qsequentialanimationgroup/tst_qsequentialanimationgroup.cpp new file mode 100644 index 0000000..0631343 --- /dev/null +++ b/tests/auto/qsequentialanimationgroup/tst_qsequentialanimationgroup.cpp @@ -0,0 +1,1649 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include "../../shared/util.h" + +#include <QtCore/qanimationgroup.h> +#include <QtCore/qsequentialanimationgroup.h> + +//TESTED_CLASS=QSequentialAnimationGroup +//TESTED_FILES= + +Q_DECLARE_METATYPE(QAbstractAnimation::State) +Q_DECLARE_METATYPE(QAbstractAnimation*) + +class tst_QSequentialAnimationGroup : public QObject +{ + Q_OBJECT +public: + tst_QSequentialAnimationGroup(); + virtual ~tst_QSequentialAnimationGroup(); + +public Q_SLOTS: + void init(); + void cleanup(); + +private slots: + void construction(); + void setCurrentTime(); + void setCurrentTimeWithUncontrolledAnimation(); + void seekingForwards(); + void seekingBackwards(); + void pauseAndResume(); + void restart(); + void looping(); + void startDelay(); + void clearGroup(); + void groupWithZeroDurationAnimations(); + void propagateGroupUpdateToChildren(); + void updateChildrenWithRunningGroup(); + void deleteChildrenWithRunningGroup(); + void startChildrenWithStoppedGroup(); + void stopGroupWithRunningChild(); + void startGroupWithRunningChild(); + void zeroDurationAnimation(); + void stopUncontrolledAnimations(); + void finishWithUncontrolledAnimation(); + void addRemoveAnimation(); + void currentAnimation(); + void currentAnimationWithZeroDuration(); + void insertAnimation(); + void clearAnimations(); +}; + +tst_QSequentialAnimationGroup::tst_QSequentialAnimationGroup() +{ +} + +tst_QSequentialAnimationGroup::~tst_QSequentialAnimationGroup() +{ +} + +void tst_QSequentialAnimationGroup::init() +{ + qRegisterMetaType<QAbstractAnimation::State>("QAbstractAnimation::State"); + qRegisterMetaType<QAbstractAnimation*>("QAbstractAnimation*"); +} + +void tst_QSequentialAnimationGroup::cleanup() +{ +} + +void tst_QSequentialAnimationGroup::construction() +{ + QSequentialAnimationGroup animationgroup; +} + +class AnimationObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(int value READ value WRITE setValue) +public: + AnimationObject(int startValue = 0) + : v(startValue) + { } + + int value() const { return v; } + void setValue(int value) { v = value; } + + int v; +}; + +class TestAnimation : public QVariantAnimation +{ + Q_OBJECT +public: + virtual void updateCurrentValue(const QVariant &value) { Q_UNUSED(value)}; + virtual void updateState(QAbstractAnimation::State oldState, + QAbstractAnimation::State newState) + { + Q_UNUSED(oldState) + Q_UNUSED(newState) + }; +}; + +class UncontrolledAnimation : public QPropertyAnimation +{ + Q_OBJECT +public: + UncontrolledAnimation(QObject *target, const QByteArray &propertyName, QObject *parent = 0) + : QPropertyAnimation(target, propertyName, parent) + { + setDuration(250); + } + + int duration() const { return -1; /* not time driven */ } + +protected: + void updateCurrentTime(int msecs) + { + QPropertyAnimation::updateCurrentTime(msecs); + if (msecs >= QPropertyAnimation::duration()) + stop(); + } +}; + +void tst_QSequentialAnimationGroup::setCurrentTime() +{ + AnimationObject s_o1; + AnimationObject s_o2; + AnimationObject s_o3; + + // sequence operating on same object/property + QAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a2_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a3_s_o1 = new QPropertyAnimation(&s_o1, "value"); + a2_s_o1->setLoopCount(3); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a2_s_o1); + sequence->addAnimation(a3_s_o1); + + // sequence operating on different object/properties + QAnimationGroup *sequence2 = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o2 = new QPropertyAnimation(&s_o2, "value"); + QVariantAnimation *a1_s_o3 = new QPropertyAnimation(&s_o3, "value"); + sequence2->addAnimation(a1_s_o2); + sequence2->addAnimation(a1_s_o3); + + QSequentialAnimationGroup group; + group.addAnimation(sequence); + group.addAnimation(sequence2); + + // Current time = 1 + group.setCurrentTime(1); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(sequence2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + + QCOMPARE(group.currentTime(), 1); + QCOMPARE(sequence->currentTime(), 1); + QCOMPARE(a1_s_o1->currentTime(), 1); + QCOMPARE(a2_s_o1->currentTime(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 250 + group.setCurrentTime(250); + QCOMPARE(group.currentTime(), 250); + QCOMPARE(sequence->currentTime(), 250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 251 + group.setCurrentTime(251); + QCOMPARE(group.currentTime(), 251); + QCOMPARE(sequence->currentTime(), 251); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 1); + QCOMPARE(a2_s_o1->currentLoop(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 750 + group.setCurrentTime(750); + QCOMPARE(group.currentTime(), 750); + QCOMPARE(sequence->currentTime(), 750); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 0); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 1000 + group.setCurrentTime(1000); + QCOMPARE(group.currentTime(), 1000); + QCOMPARE(sequence->currentTime(), 1000); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 1010 + group.setCurrentTime(1010); + QCOMPARE(group.currentTime(), 1010); + QCOMPARE(sequence->currentTime(), 1010); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 10); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 1250 + group.setCurrentTime(1250); + QCOMPARE(group.currentTime(), 1250); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 1500 + group.setCurrentTime(1500); + QCOMPARE(group.currentTime(), 1500); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 1750 + group.setCurrentTime(1750); + QCOMPARE(group.currentTime(), 1750); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 500); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 250); + + // Current time = 2000 + group.setCurrentTime(2000); + QCOMPARE(group.currentTime(), 1750); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 500); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 250); +} + +void tst_QSequentialAnimationGroup::setCurrentTimeWithUncontrolledAnimation() +{ + AnimationObject s_o1; + AnimationObject s_o2; + AnimationObject t_o1; + AnimationObject t_o2; + + // sequence operating on different object/properties + QAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a1_s_o2 = new QPropertyAnimation(&s_o2, "value"); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a1_s_o2); + + UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation(&t_o1, "value"); + QCOMPARE(notTimeDriven->totalDuration(), -1); + + QVariantAnimation *loopsForever = new QPropertyAnimation(&t_o2, "value"); + loopsForever->setLoopCount(-1); + QCOMPARE(loopsForever->totalDuration(), -1); + + QSequentialAnimationGroup group; + group.addAnimation(sequence); + group.addAnimation(notTimeDriven); + group.addAnimation(loopsForever); + group.start(); + group.pause(); // this allows the group to listen for the finish signal of its children + + // Current time = 1 + group.setCurrentTime(1); + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(sequence->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven->state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever->state(), QAnimationGroup::Stopped); + + QCOMPARE(group.currentTime(), 1); + QCOMPARE(sequence->currentTime(), 1); + QCOMPARE(a1_s_o1->currentTime(), 1); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(notTimeDriven->currentTime(), 0); + QCOMPARE(loopsForever->currentTime(), 0); + + // Current time = 250 + group.setCurrentTime(250); + QCOMPARE(group.currentTime(), 250); + QCOMPARE(sequence->currentTime(), 250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(notTimeDriven->currentTime(), 0); + QCOMPARE(loopsForever->currentTime(), 0); + + // Current time = 500 + group.setCurrentTime(500); + QCOMPARE(group.currentTime(), 500); + QCOMPARE(sequence->currentTime(), 500); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 0); + QCOMPARE(loopsForever->currentTime(), 0); + QCOMPARE(group.currentAnimation(), notTimeDriven); + + // Current time = 505 + group.setCurrentTime(505); + QCOMPARE(group.currentTime(), 505); + QCOMPARE(sequence->currentTime(), 500); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 5); + QCOMPARE(loopsForever->currentTime(), 0); + QCOMPARE(group.currentAnimation(), notTimeDriven); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven->state(), QAnimationGroup::Paused); + QCOMPARE(loopsForever->state(), QAnimationGroup::Stopped); + + // Current time = 750 (end of notTimeDriven animation) + group.setCurrentTime(750); + QCOMPARE(group.currentTime(), 750); + QCOMPARE(sequence->currentTime(), 500); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 250); + QCOMPARE(loopsForever->currentTime(), 0); + QCOMPARE(group.currentAnimation(), loopsForever); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven->state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever->state(), QAnimationGroup::Paused); + + // Current time = 800 (as notTimeDriven was finished at 750, loopsforever should still run) + group.setCurrentTime(800); + QCOMPARE(group.currentTime(), 800); + QCOMPARE(group.currentAnimation(), loopsForever); + QCOMPARE(sequence->currentTime(), 500); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(notTimeDriven->currentTime(), 250); + QCOMPARE(loopsForever->currentTime(), 50); + + loopsForever->stop(); // this should stop the group + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven->state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever->state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::seekingForwards() +{ + AnimationObject s_o1; + AnimationObject s_o2; + AnimationObject s_o3; + + // sequence operating on same object/property + QAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a2_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a3_s_o1 = new QPropertyAnimation(&s_o1, "value"); + a2_s_o1->setLoopCount(3); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a2_s_o1); + sequence->addAnimation(a3_s_o1); + + // sequence operating on different object/properties + QAnimationGroup *sequence2 = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o2 = new QPropertyAnimation(&s_o2, "value"); + QVariantAnimation *a1_s_o3 = new QPropertyAnimation(&s_o3, "value"); + sequence2->addAnimation(a1_s_o2); + sequence2->addAnimation(a1_s_o3); + + QSequentialAnimationGroup group; + group.addAnimation(sequence); + group.addAnimation(sequence2); + + // Current time = 1 + group.setCurrentTime(1); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(sequence2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o3->state(), QAnimationGroup::Stopped); + + QCOMPARE(group.currentTime(), 1); + QCOMPARE(sequence->currentTime(), 1); + QCOMPARE(a1_s_o1->currentTime(), 1); + QCOMPARE(a2_s_o1->currentTime(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // Current time = 1500 + group.setCurrentTime(1500); + QCOMPARE(group.currentTime(), 1500); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 250); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 0); + + // this will restart the group + group.start(); + group.pause(); + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(sequence->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Paused); + QCOMPARE(sequence2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o3->state(), QAnimationGroup::Stopped); + + // Current time = 1750 + group.setCurrentTime(1750); + QCOMPARE(group.currentTime(), 1750); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 500); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 250); +} + +void tst_QSequentialAnimationGroup::seekingBackwards() +{ + AnimationObject s_o1; + AnimationObject s_o2; + AnimationObject s_o3; + + // sequence operating on same object/property + QAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a2_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a3_s_o1 = new QPropertyAnimation(&s_o1, "value"); + a2_s_o1->setLoopCount(3); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a2_s_o1); + sequence->addAnimation(a3_s_o1); + + // sequence operating on different object/properties + QAnimationGroup *sequence2 = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o2 = new QPropertyAnimation(&s_o2, "value"); + QVariantAnimation *a1_s_o3 = new QPropertyAnimation(&s_o3, "value"); + sequence2->addAnimation(a1_s_o2); + sequence2->addAnimation(a1_s_o3); + + QSequentialAnimationGroup group; + group.addAnimation(sequence); + group.addAnimation(sequence2); + + group.start(); + + // Current time = 1600 + group.setCurrentTime(1600); + QCOMPARE(group.currentTime(), 1600); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 350); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 100); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(sequence2->state(), QAnimationGroup::Running); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o3->state(), QAnimationGroup::Running); + + // Seeking backwards, current time = 1 + group.setCurrentTime(1); + QCOMPARE(group.currentTime(), 1); + QCOMPARE(sequence->currentTime(), 1); + QCOMPARE(a1_s_o1->currentTime(), 1); + + QEXPECT_FAIL("", "rewinding in nested groups is considered as a restart from the children," + "hence they don't reset from their current animation", Continue); + QCOMPARE(a2_s_o1->currentTime(), 0); + QEXPECT_FAIL("", "rewinding in nested groups is considered as a restart from the children," + "hence they don't reset from their current animation", Continue); + QCOMPARE(a2_s_o1->currentLoop(), 0); + QEXPECT_FAIL("", "rewinding in nested groups is considered as a restart from the children," + "hence they don't reset from their current animation", Continue); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence2->currentTime(), 0); + QCOMPARE(a1_s_o2->currentTime(), 0); + QCOMPARE(a1_s_o3->currentTime(), 0); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(sequence->state(), QAnimationGroup::Running); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Running); + QCOMPARE(sequence2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o3->state(), QAnimationGroup::Stopped); + + // Current time = 2000 + group.setCurrentTime(2000); + QCOMPARE(group.currentTime(), 1750); + QCOMPARE(sequence->currentTime(), 1250); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 2); + QCOMPARE(a3_s_o1->currentTime(), 250); + QCOMPARE(sequence2->currentTime(), 500); + QCOMPARE(a1_s_o2->currentTime(), 250); + QCOMPARE(a1_s_o3->currentTime(), 250); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(sequence->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(sequence2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o2->state(), QAnimationGroup::Stopped); + QCOMPARE(a1_s_o3->state(), QAnimationGroup::Stopped); +} + +typedef QList<QAbstractAnimation::State> StateList; + +static bool compareStates(const QSignalSpy& spy, const StateList &expectedStates) +{ + bool equals = true; + for (int i = 0; i < qMax(expectedStates.count(), spy.count()); ++i) { + if (i >= spy.count() || i >= expectedStates.count()) { + equals = false; + break; + } + QList<QVariant> args = spy.at(i); + QAbstractAnimation::State st = expectedStates.at(i); + QAbstractAnimation::State actual = qVariantValue<QAbstractAnimation::State>(args.value(1)); + if (equals && actual != st) { + equals = false; + break; + } + } + if (!equals) { + const char *stateStrings[] = {"Stopped", "Paused", "Running"}; + QString e,a; + for (int i = 0; i < qMax(expectedStates.count(), spy.count()); ++i) { + if (i < expectedStates.count()) { + int exp = int(expectedStates.at(i)); + if (!e.isEmpty()) + e += QLatin1String(", "); + e += QLatin1String(stateStrings[exp]); + } + if (i < spy.count()) { + QList<QVariant> args = spy.at(i); + QAbstractAnimation::State actual = qVariantValue<QAbstractAnimation::State>(args.value(1)); + if (!a.isEmpty()) + a += QLatin1String(", "); + if (int(actual) >= 0 && int(actual) <= 2) { + a += QLatin1String(stateStrings[int(actual)]); + } else { + a += QLatin1String("NaN"); + } + } + + } + qDebug("\n" + "expected (count == %d): %s\n" + "actual (count == %d): %s\n", expectedStates.count(), qPrintable(e), spy.count(), qPrintable(a)); + } + return equals; +} + +void tst_QSequentialAnimationGroup::pauseAndResume() +{ + AnimationObject s_o1; + + // sequence operating on same object/property + QAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a2_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a3_s_o1 = new QPropertyAnimation(&s_o1, "value"); + a2_s_o1->setLoopCount(2); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a2_s_o1); + sequence->addAnimation(a3_s_o1); + sequence->setLoopCount(2); + + QSignalSpy a1StateChangedSpy(a1_s_o1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy seqStateChangedSpy(sequence, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QSequentialAnimationGroup group; + group.addAnimation(sequence); + + group.start(); + group.pause(); + + // Current time = 1751 + group.setCurrentTime(1751); + QCOMPARE(group.currentTime(), 1751); + QCOMPARE(sequence->currentTime(), 751); + QCOMPARE(sequence->currentLoop(), 1); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 1); + QCOMPARE(a3_s_o1->currentLoop(), 0); + QCOMPARE(a3_s_o1->currentTime(), 1); + + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(sequence->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a2_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a3_s_o1->state(), QAnimationGroup::Paused); + + QCOMPARE(a1StateChangedSpy.count(), 5); // Running,Paused,Stopped,Running,Stopped + QCOMPARE(seqStateChangedSpy.count(), 2); // Running,Paused + + QVERIFY(compareStates(a1StateChangedSpy, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Paused + << QAbstractAnimation::Stopped + << QAbstractAnimation::Running + << QAbstractAnimation::Stopped))); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(a1StateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(a1StateChangedSpy.at(1).at(1)), + QAnimationGroup::Paused); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(a1StateChangedSpy.at(2).at(1)), + QAnimationGroup::Stopped); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(a1StateChangedSpy.at(3).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(a1StateChangedSpy.at(4).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(1).at(1)), + QAnimationGroup::Paused); + + group.resume(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(sequence->state(), QAnimationGroup::Running); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a2_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a3_s_o1->state(), QAnimationGroup::Running); + + QVERIFY(group.currentTime() >= 1751); + QVERIFY(sequence->currentTime() >= 751); + QCOMPARE(sequence->currentLoop(), 1); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 1); + QCOMPARE(a3_s_o1->currentLoop(), 0); + QVERIFY(a3_s_o1->currentTime() >= 1); + + QCOMPARE(seqStateChangedSpy.count(), 3); // Running,Paused,Running + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(2).at(1)), + QAnimationGroup::Running); + + group.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(sequence->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a2_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a3_s_o1->state(), QAnimationGroup::Paused); + + QVERIFY(group.currentTime() >= 1751); + QVERIFY(sequence->currentTime() >= 751); + QCOMPARE(sequence->currentLoop(), 1); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 1); + QCOMPARE(a3_s_o1->currentLoop(), 0); + QVERIFY(a3_s_o1->currentTime() >= 1); + + QCOMPARE(seqStateChangedSpy.count(), 4); // Running,Paused,Running,Paused + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(3).at(1)), + QAnimationGroup::Paused); + + group.stop(); + + QCOMPARE(seqStateChangedSpy.count(), 5); // Running,Paused,Running,Paused,Stopped + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(4).at(1)), + QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::restart() +{ + AnimationObject s_o1; + + // sequence operating on same object/property + QAnimationGroup *sequence = new QSequentialAnimationGroup(); + QSignalSpy seqCurrentAnimChangedSpy(sequence, SIGNAL(currentAnimationChanged(QAbstractAnimation*))); + QSignalSpy seqStateChangedSpy(sequence, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QVariantAnimation *anims[3]; + QSignalSpy *animsStateChanged[3]; + + for (int i = 0; i < 3; i++) { + anims[i] = new QPropertyAnimation(&s_o1, "value"); + anims[i]->setDuration(100); + animsStateChanged[i] = new QSignalSpy(anims[i], SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + } + + anims[1]->setLoopCount(2); + sequence->addAnimation(anims[0]); + sequence->addAnimation(anims[1]); + sequence->addAnimation(anims[2]); + sequence->setLoopCount(2); + + QSequentialAnimationGroup group; + group.addAnimation(sequence); + + group.start(); + + QTest::qWait(500); + + QCOMPARE(group.state(), QAnimationGroup::Running); + + QTest::qWait(300); + QTRY_COMPARE(group.state(), QAnimationGroup::Stopped); + + for (int i = 0; i < 3; i++) { + QCOMPARE(animsStateChanged[i]->count(), 4); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(animsStateChanged[i]->at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(animsStateChanged[i]->at(1).at(1)), + QAnimationGroup::Stopped); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(animsStateChanged[i]->at(2).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(animsStateChanged[i]->at(3).at(1)), + QAnimationGroup::Stopped); + } + + QCOMPARE(seqStateChangedSpy.count(), 2); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(seqStateChangedSpy.at(1).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(seqCurrentAnimChangedSpy.count(), 6); + for(int i=0; i<seqCurrentAnimChangedSpy.count(); i++) + QCOMPARE(anims[i%3], qVariantValue<QAbstractAnimation*>(seqCurrentAnimChangedSpy.at(i).at(0))); + + group.start(); + + QCOMPARE(animsStateChanged[0]->count(), 5); + QCOMPARE(animsStateChanged[1]->count(), 4); + QCOMPARE(animsStateChanged[2]->count(), 4); + QCOMPARE(seqStateChangedSpy.count(), 3); +} + +void tst_QSequentialAnimationGroup::looping() +{ + AnimationObject s_o1; + AnimationObject s_o2; + AnimationObject s_o3; + + // sequence operating on same object/property + QSequentialAnimationGroup *sequence = new QSequentialAnimationGroup(); + QVariantAnimation *a1_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a2_s_o1 = new QPropertyAnimation(&s_o1, "value"); + QVariantAnimation *a3_s_o1 = new QPropertyAnimation(&s_o1, "value"); + + QSignalSpy a1Spy(a1_s_o1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy a2Spy(a2_s_o1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy a3Spy(a3_s_o1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy seqSpy(sequence, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + a2_s_o1->setLoopCount(2); + sequence->addAnimation(a1_s_o1); + sequence->addAnimation(a2_s_o1); + sequence->addAnimation(a3_s_o1); + sequence->setLoopCount(2); + + QSequentialAnimationGroup group; + QSignalSpy groupSpy(&group, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + group.addAnimation(sequence); + group.setLoopCount(2); + + group.start(); + group.pause(); + + // Current time = 1750 + group.setCurrentTime(1750); + QCOMPARE(group.currentTime(), 1750); + QCOMPARE(sequence->currentTime(), 750); + QCOMPARE(sequence->currentLoop(), 1); + QCOMPARE(a1_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 1); + // this animation is at the beginning because it is the current one inside sequence + QCOMPARE(a3_s_o1->currentLoop(), 0); + QCOMPARE(a3_s_o1->currentTime(), 0); + QCOMPARE(sequence->currentAnimation(), a3_s_o1); + + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(sequence->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a2_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a3_s_o1->state(), QAnimationGroup::Paused); + + QCOMPARE(a1Spy.count(), 5); // Running,Paused,Stopped,Running,Stopped + QVERIFY(compareStates(a1Spy, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Paused + << QAbstractAnimation::Stopped + << QAbstractAnimation::Running + << QAbstractAnimation::Stopped))); + + QCOMPARE(a2Spy.count(), 4); // Running,Stopped,Running,Stopped + QVERIFY(compareStates(a3Spy, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Stopped + << QAbstractAnimation::Running + << QAbstractAnimation::Paused))); + + QCOMPARE(seqSpy.count(), 2); // Running,Paused + QCOMPARE(groupSpy.count(), 2); // Running,Paused + + // Looping, current time = duration + 1 + group.setCurrentTime(group.duration() + 1); + QCOMPARE(group.currentTime(), 1); + QCOMPARE(group.currentLoop(), 1); + QCOMPARE(sequence->currentTime(), 1); + QCOMPARE(sequence->currentLoop(), 0); + QCOMPARE(a1_s_o1->currentTime(), 1); + QCOMPARE(a2_s_o1->currentTime(), 250); + QCOMPARE(a2_s_o1->currentLoop(), 1); + // this animation is at the end because it was run on the previous loop + QCOMPARE(a3_s_o1->currentLoop(), 0); + QCOMPARE(a3_s_o1->currentTime(), 250); + + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(sequence->state(), QAnimationGroup::Paused); + QCOMPARE(a1_s_o1->state(), QAnimationGroup::Paused); + QCOMPARE(a2_s_o1->state(), QAnimationGroup::Stopped); + QCOMPARE(a3_s_o1->state(), QAnimationGroup::Stopped); + + QCOMPARE(a1Spy.count(), 7); // Running,Paused,Stopped,Running,Stopped,Running,Stopped + QCOMPARE(a2Spy.count(), 4); // Running, Stopped, Running, Stopped + QVERIFY(compareStates(a3Spy, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Stopped + << QAbstractAnimation::Running + << QAbstractAnimation::Paused + << QAbstractAnimation::Stopped))); + QVERIFY(compareStates(seqSpy, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Paused + << QAbstractAnimation::Stopped + << QAbstractAnimation::Running + << QAbstractAnimation::Paused))); + QCOMPARE(groupSpy.count(), 2); +} + +void tst_QSequentialAnimationGroup::startDelay() +{ + QSequentialAnimationGroup group; + group.addPause(250); + group.addPause(125); + QCOMPARE(group.totalDuration(), 375); + + QEventLoop loop; + QObject::connect(&group, SIGNAL(finished()), &loop, SLOT(quit())); + + QTime time; + time.start(); + group.start(); + loop.exec(); + + QVERIFY(time.elapsed() >= 375); + QVERIFY(time.elapsed() < 1000); +} + +void tst_QSequentialAnimationGroup::clearGroup() +{ + QSequentialAnimationGroup group; + + for (int i = 0; i < 10; ++i) { + QSequentialAnimationGroup *subGroup = new QSequentialAnimationGroup(&group); + group.addPause(100); + subGroup->addPause(10); + } + + QCOMPARE(group.animationCount(), 20); + + int count = group.animationCount(); + QPointer<QAbstractAnimation> *children = new QPointer<QAbstractAnimation>[count]; + for (int i = 0; i < count; ++i) { + QVERIFY(group.animationAt(i) != 0); + children[i] = group.animationAt(i); + } + + group.clearAnimations(); + QCOMPARE(group.animationCount(), 0); + QCOMPARE(group.currentTime(), 0); + for (int i = 0; i < count; ++i) + QCOMPARE(children[i], QPointer<QAbstractAnimation>()); + + delete[] children; +} + +void tst_QSequentialAnimationGroup::groupWithZeroDurationAnimations() +{ + QObject o; + QObject o2; + + o.setProperty("myProperty", 42); + o.setProperty("myOtherProperty", 13); + o2.setProperty("myProperty", 42); + o2.setProperty("myOtherProperty", 13); + + QSequentialAnimationGroup group; + + QVariantAnimation *a1 = new QPropertyAnimation(&o, "myProperty"); + a1->setDuration(0); + a1->setEndValue(43); + group.addAnimation(a1); + + //this should just run fine and change nothing + group.setCurrentTime(0); + QCOMPARE(group.currentAnimation(), a1); + + QVariantAnimation *a2 = new QPropertyAnimation(&o2, "myOtherProperty"); + a2->setDuration(500); + a2->setEndValue(31); + group.addAnimation(a2); + + QVariantAnimation *a3 = new QPropertyAnimation(&o, "myProperty"); + a3->setDuration(0); + a3->setEndValue(44); + group.addAnimation(a3); + + QVariantAnimation *a4 = new QPropertyAnimation(&o, "myOtherProperty"); + a4->setDuration(250); + a4->setEndValue(75); + group.addAnimation(a4); + + QVariantAnimation *a5 = new QPropertyAnimation(&o2, "myProperty"); + a5->setDuration(0); + a5->setEndValue(12); + group.addAnimation(a5); + + QCOMPARE(o.property("myProperty").toInt(), 42); + QCOMPARE(o.property("myOtherProperty").toInt(), 13); + QCOMPARE(o2.property("myProperty").toInt(), 42); + QCOMPARE(o2.property("myOtherProperty").toInt(), 13); + + + group.start(); + + QCOMPARE(o.property("myProperty").toInt(), 43); + QCOMPARE(o.property("myOtherProperty").toInt(), 13); + QCOMPARE(o2.property("myProperty").toInt(), 42); + QCOMPARE(o2.property("myOtherProperty").toInt(), 13); + + QTest::qWait(50); + + int o2val = o2.property("myOtherProperty").toInt(); + QVERIFY(o2val > 13); + QVERIFY(o2val < 31); + QCOMPARE(o.property("myProperty").toInt(), 43); + QCOMPARE(o.property("myOtherProperty").toInt(), 13); + + QTest::qWait(500); + + QCOMPARE(o.property("myProperty").toInt(), 44); + QCOMPARE(o2.property("myProperty").toInt(), 42); + QCOMPARE(o2.property("myOtherProperty").toInt(), 31); + QCOMPARE(a1->state(), QAnimationGroup::Stopped); + QCOMPARE(a2->state(), QAnimationGroup::Stopped); + QCOMPARE(a3->state(), QAnimationGroup::Stopped); + QCOMPARE(a4->state(), QAnimationGroup::Running); + QCOMPARE(a5->state(), QAnimationGroup::Stopped); + QCOMPARE(group.state(), QAnimationGroup::Running); + QTest::qWait(500); + + QTRY_COMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(o.property("myProperty").toInt(), 44); + QCOMPARE(o.property("myOtherProperty").toInt(), 75); + QCOMPARE(o2.property("myProperty").toInt(), 12); + QCOMPARE(o2.property("myOtherProperty").toInt(), 31); + QCOMPARE(a1->state(), QAnimationGroup::Stopped); + QCOMPARE(a2->state(), QAnimationGroup::Stopped); + QCOMPARE(a3->state(), QAnimationGroup::Stopped); + QCOMPARE(a4->state(), QAnimationGroup::Stopped); + QCOMPARE(a5->state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::propagateGroupUpdateToChildren() +{ + // this test verifies if group state changes are updating its children correctly + QSequentialAnimationGroup group; + + QObject o; + o.setProperty("ole", 42); + QCOMPARE(o.property("ole").toInt(), 42); + + QPropertyAnimation anim1(&o, "ole"); + anim1.setEndValue(43); + anim1.setDuration(100); + QVERIFY(!anim1.currentValue().isValid()); + QCOMPARE(anim1.currentValue().toInt(), 0); + QCOMPARE(o.property("ole").toInt(), 42); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QVERIFY(anim2.currentValue().isValid()); + QCOMPARE(anim2.currentValue().toInt(), 0); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + group.start(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Paused); + QCOMPARE(anim1.state(), QAnimationGroup::Paused); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::updateChildrenWithRunningGroup() +{ + // assert that its possible to modify a child's state directly while their group is running + QSequentialAnimationGroup group; + + TestAnimation anim; + anim.setStartValue(0); + anim.setEndValue(100); + anim.setDuration(200); + + QSignalSpy groupStateChangedSpy(&group, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy childStateChangedSpy(&anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QCOMPARE(groupStateChangedSpy.count(), 0); + QCOMPARE(childStateChangedSpy.count(), 0); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim); + + group.start(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Running); + + QCOMPARE(groupStateChangedSpy.count(), 1); + QCOMPARE(childStateChangedSpy.count(), 1); + + QCOMPARE(qVariantValue<QAbstractAnimation::State>(groupStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(childStateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + + // starting directly a running child will not have any effect + anim.start(); + + QCOMPARE(groupStateChangedSpy.count(), 1); + QCOMPARE(childStateChangedSpy.count(), 1); + + anim.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Paused); + + // in the animation stops directly, the group will still be running + anim.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::deleteChildrenWithRunningGroup() +{ + // test if children can be activated when their group is stopped + QSequentialAnimationGroup group; + + QVariantAnimation *anim1 = new TestAnimation; + anim1->setStartValue(0); + anim1->setEndValue(100); + anim1->setDuration(200); + group.addAnimation(anim1); + + QCOMPARE(group.duration(), anim1->duration()); + + group.start(); + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1->state(), QAnimationGroup::Running); + + QTest::qWait(50); + QVERIFY(group.currentTime() > 0); + + delete anim1; + QCOMPARE(group.animationCount(), 0); + QCOMPARE(group.duration(), 0); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(group.currentTime(), 0); //that's the invariant +} + +void tst_QSequentialAnimationGroup::startChildrenWithStoppedGroup() +{ + // test if children can be activated when their group is stopped + QSequentialAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(200); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + anim1.start(); + anim2.start(); + anim2.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); +} + +void tst_QSequentialAnimationGroup::stopGroupWithRunningChild() +{ + // children that started independently will not be affected by a group stop + QSequentialAnimationGroup group; + + TestAnimation anim1; + anim1.setStartValue(0); + anim1.setEndValue(100); + anim1.setDuration(200); + + TestAnimation anim2; + anim2.setStartValue(0); + anim2.setEndValue(100); + anim2.setDuration(200); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); + + group.addAnimation(&anim1); + group.addAnimation(&anim2); + + anim1.start(); + anim2.start(); + anim2.pause(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Running); + QCOMPARE(anim2.state(), QAnimationGroup::Paused); + + anim1.stop(); + anim2.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1.state(), QAnimationGroup::Stopped); + QCOMPARE(anim2.state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::startGroupWithRunningChild() +{ + // as the group has precedence over its children, starting a group will restart all the children + QSequentialAnimationGroup group; + + TestAnimation *anim1 = new TestAnimation(); + anim1->setStartValue(0); + anim1->setEndValue(100); + anim1->setDuration(200); + + TestAnimation *anim2 = new TestAnimation(); + anim2->setStartValue(0); + anim2->setEndValue(100); + anim2->setDuration(200); + + QSignalSpy stateChangedSpy1(anim1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + QSignalSpy stateChangedSpy2(anim2, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + QCOMPARE(stateChangedSpy1.count(), 0); + QCOMPARE(stateChangedSpy2.count(), 0); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1->state(), QAnimationGroup::Stopped); + QCOMPARE(anim2->state(), QAnimationGroup::Stopped); + + group.addAnimation(anim1); + group.addAnimation(anim2); + + anim1->start(); + anim2->start(); + anim2->pause(); + + QVERIFY(compareStates(stateChangedSpy1, (StateList() << QAbstractAnimation::Running))); + + QVERIFY(compareStates(stateChangedSpy2, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Paused))); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1->state(), QAnimationGroup::Running); + QCOMPARE(anim2->state(), QAnimationGroup::Paused); + + group.start(); + + QVERIFY(compareStates(stateChangedSpy1, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Stopped + << QAbstractAnimation::Running))); + QVERIFY(compareStates(stateChangedSpy2, (StateList() << QAbstractAnimation::Running + << QAbstractAnimation::Paused))); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1->state(), QAnimationGroup::Running); + QCOMPARE(anim2->state(), QAnimationGroup::Paused); + + QTest::qWait(300); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim1->state(), QAnimationGroup::Stopped); + QCOMPARE(anim2->state(), QAnimationGroup::Running); + + QCOMPARE(stateChangedSpy2.count(), 4); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy2.at(2).at(1)), + QAnimationGroup::Stopped); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy2.at(3).at(1)), + QAnimationGroup::Running); + + group.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(anim1->state(), QAnimationGroup::Stopped); + QCOMPARE(anim2->state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::zeroDurationAnimation() +{ + QSequentialAnimationGroup group; + + TestAnimation *anim1 = new TestAnimation(); + anim1->setStartValue(0); + anim1->setEndValue(100); + anim1->setDuration(0); + + TestAnimation *anim2 = new TestAnimation(); + anim2->setStartValue(0); + anim2->setEndValue(100); + anim2->setDuration(100); + + AnimationObject o1; + QPropertyAnimation *anim3 = new QPropertyAnimation(&o1, "value"); + anim3->setEndValue(100); + anim3->setDuration(0); + + QSignalSpy stateChangedSpy(anim1, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + group.addAnimation(anim1); + group.addAnimation(anim2); + group.addAnimation(anim3); + group.setLoopCount(2); + group.start(); + + QCOMPARE(stateChangedSpy.count(), 2); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy.at(0).at(1)), + QAnimationGroup::Running); + QCOMPARE(qVariantValue<QAbstractAnimation::State>(stateChangedSpy.at(1).at(1)), + QAnimationGroup::Stopped); + + QCOMPARE(anim1->state(), QAnimationGroup::Stopped); + QCOMPARE(anim2->state(), QAnimationGroup::Running); + QCOMPARE(group.state(), QAnimationGroup::Running); + + //now let's try to seek to the next loop + group.setCurrentTime(group.duration() + 1); + QCOMPARE(anim1->state(), QAnimationGroup::Stopped); + QCOMPARE(anim2->state(), QAnimationGroup::Running); + QCOMPARE(anim3->state(), QAnimationGroup::Stopped); + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(o1.value(), 100); //anim3 should have been run +} + +void tst_QSequentialAnimationGroup::stopUncontrolledAnimations() +{ + QSequentialAnimationGroup group; + + AnimationObject o1; + UncontrolledAnimation notTimeDriven(&o1, "value"); + QCOMPARE(notTimeDriven.totalDuration(), -1); + + TestAnimation loopsForever; + loopsForever.setStartValue(0); + loopsForever.setEndValue(100); + loopsForever.setDuration(100); + loopsForever.setLoopCount(-1); + + group.addAnimation(¬TimeDriven); + group.addAnimation(&loopsForever); + + group.start(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Running); + QCOMPARE(loopsForever.state(), QAnimationGroup::Stopped); + + notTimeDriven.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever.state(), QAnimationGroup::Running); + + loopsForever.stop(); + + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Stopped); + QCOMPARE(loopsForever.state(), QAnimationGroup::Stopped); +} + +void tst_QSequentialAnimationGroup::finishWithUncontrolledAnimation() +{ + AnimationObject o1; + + //1st case: + //first we test a group with one uncontrolled animation + QSequentialAnimationGroup group; + UncontrolledAnimation notTimeDriven(&o1, "value", &group); + QSignalSpy spy(&group, SIGNAL(finished())); + + group.start(); + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Running); + QCOMPARE(group.currentTime(), 0); + QCOMPARE(notTimeDriven.currentTime(), 0); + + QTest::qWait(300); //wait for the end of notTimeDriven + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Stopped); + const int actualDuration = notTimeDriven.currentTime(); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(group.currentTime(), actualDuration); + QCOMPARE(spy.count(), 1); + + //2nd case: + // lets make sure the seeking will work again + spy.clear(); + QPropertyAnimation anim(&group); + QSignalSpy animStateChangedSpy(&anim, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + + group.setCurrentTime(300); + QCOMPARE(group.state(), QAnimationGroup::Stopped); + QCOMPARE(notTimeDriven.currentTime(), actualDuration); + QCOMPARE(group.currentAnimation(), &anim); + + //3rd case: + //now let's add a perfectly defined animation at the end + QCOMPARE(animStateChangedSpy.count(), 0); + group.start(); + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Running); + QCOMPARE(group.currentTime(), 0); + QCOMPARE(notTimeDriven.currentTime(), 0); + + QCOMPARE(animStateChangedSpy.count(), 0); + + QTest::qWait(300); //wait for the end of notTimeDriven + QCOMPARE(notTimeDriven.state(), QAnimationGroup::Stopped); + QCOMPARE(group.state(), QAnimationGroup::Running); + QCOMPARE(anim.state(), QAnimationGroup::Running); + QCOMPARE(group.currentAnimation(), &anim); + QCOMPARE(animStateChangedSpy.count(), 1); + QTest::qWait(300); //wait for the end of anim + + QCOMPARE(anim.state(), QAnimationGroup::Stopped); + QCOMPARE(anim.currentTime(), anim.duration()); + + //we should simply be at the end + QCOMPARE(spy.count(), 1); + QCOMPARE(animStateChangedSpy.count(), 2); + QCOMPARE(group.currentTime(), notTimeDriven.currentTime() + anim.currentTime()); +} + +void tst_QSequentialAnimationGroup::addRemoveAnimation() +{ + //this test is specific to the sequential animation group + QSequentialAnimationGroup group; + + QCOMPARE(group.duration(), 0); + QCOMPARE(group.currentTime(), 0); + QVariantAnimation *anim1 = new QPropertyAnimation; + group.addAnimation(anim1); + QCOMPARE(group.duration(), 250); + QCOMPARE(group.currentTime(), 0); + QCOMPARE(group.currentAnimation(), anim1); + + //let's append an animation + QVariantAnimation *anim2 = new QPropertyAnimation; + group.addAnimation(anim2); + QCOMPARE(group.duration(), 500); + QCOMPARE(group.currentTime(), 0); + QCOMPARE(group.currentAnimation(), anim1); + + //let's prepend an animation + QVariantAnimation *anim0 = new QPropertyAnimation; + group.insertAnimationAt(0, anim0); + QCOMPARE(group.duration(), 750); + QCOMPARE(group.currentTime(), 0); + QCOMPARE(group.currentAnimation(), anim0); //anim0 has become the new currentAnimation + + group.setCurrentTime(300); //anim0 | anim1 | anim2 + QCOMPARE(group.currentTime(), 300); + QCOMPARE(group.currentAnimation(), anim1); + QCOMPARE(anim1->currentTime(), 50); + + group.removeAnimation(anim0); //anim1 | anim2 + QCOMPARE(group.currentTime(), 50); + QCOMPARE(group.currentAnimation(), anim1); + QCOMPARE(anim1->currentTime(), 50); + + group.setCurrentTime(0); + group.insertAnimationAt(0, anim0); //anim0 | anim1 | anim2 + group.setCurrentTime(300); + QCOMPARE(group.currentTime(), 300); + QCOMPARE(group.currentAnimation(), anim1); + QCOMPARE(anim1->currentTime(), 50); + + group.removeAnimation(anim1); //anim0 | anim2 + QCOMPARE(group.currentTime(), 250); + QCOMPARE(group.currentAnimation(), anim2); + QCOMPARE(anim0->currentTime(), 250); +} + +void tst_QSequentialAnimationGroup::currentAnimation() +{ + QSequentialAnimationGroup group; + QVERIFY(group.currentAnimation() == 0); + + QPropertyAnimation anim; + anim.setDuration(0); + group.addAnimation(&anim); + QCOMPARE(group.currentAnimation(), &anim); +} + +void tst_QSequentialAnimationGroup::currentAnimationWithZeroDuration() +{ + QSequentialAnimationGroup group; + QVERIFY(group.currentAnimation() == 0); + + QPropertyAnimation zero1; + zero1.setDuration(0); + QPropertyAnimation zero2; + zero2.setDuration(0); + + QPropertyAnimation anim; + + QPropertyAnimation zero3; + zero3.setDuration(0); + QPropertyAnimation zero4; + zero4.setDuration(0); + + + group.addAnimation(&zero1); + group.addAnimation(&zero2); + group.addAnimation(&anim); + group.addAnimation(&zero3); + group.addAnimation(&zero4); + + QCOMPARE(group.currentAnimation(), &zero1); + + group.setCurrentTime(0); + QCOMPARE(group.currentAnimation(), &anim); + + group.setCurrentTime(group.duration()); + QCOMPARE(group.currentAnimation(), &zero4); + + group.setDirection(QAbstractAnimation::Backward); + + group.setCurrentTime(0); + QCOMPARE(group.currentAnimation(), &zero1); + + group.setCurrentTime(group.duration()); + QCOMPARE(group.currentAnimation(), &anim); +} + +void tst_QSequentialAnimationGroup::insertAnimation() +{ + QSequentialAnimationGroup group; + group.setLoopCount(2); + QPropertyAnimation *anim = new QPropertyAnimation(&group); + QCOMPARE(group.duration(), anim->duration()); + group.setCurrentTime(300); + QCOMPARE(group.currentLoop(), 1); + + //this will crash if the sequential group calls duration on the created animation + new QPropertyAnimation(&group); +} + + +class SequentialAnimationGroup : public QSequentialAnimationGroup +{ + Q_OBJECT +public slots: + void clearAnimations() + { + QSequentialAnimationGroup::clearAnimations(); + } + + void refill() + { + stop(); + clearAnimations(); + new QPropertyAnimation(this); + start(); + } + +}; + + +void tst_QSequentialAnimationGroup::clearAnimations() +{ + SequentialAnimationGroup group; + QPointer<QAbstractAnimation> anim1 = new QPropertyAnimation(&group); + group.connect(anim1, SIGNAL(finished()), SLOT(clearAnimations())); + new QPropertyAnimation(&group); + QCOMPARE(group.animationCount(), 2); + + group.start(); + QTest::qWait(anim1->duration() + 100); + QCOMPARE(group.animationCount(), 0); + QCOMPARE(group.state(), QAbstractAnimation::Stopped); + QCOMPARE(group.currentTime(), 0); + + anim1 = new QPropertyAnimation(&group); + group.connect(anim1, SIGNAL(finished()), SLOT(refill())); + group.start(); + QTest::qWait(anim1->duration() + 100); + QVERIFY(anim1 == 0); //anim1 should have been deleted + QCOMPARE(group.state(), QAbstractAnimation::Running); +} + +QTEST_MAIN(tst_QSequentialAnimationGroup) +#include "tst_qsequentialanimationgroup.moc" diff --git a/tests/auto/qstate/qstate.pro b/tests/auto/qstate/qstate.pro new file mode 100644 index 0000000..9131fa8 --- /dev/null +++ b/tests/auto/qstate/qstate.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT = core +SOURCES += tst_qstate.cpp + + diff --git a/tests/auto/qstate/tst_qstate.cpp b/tests/auto/qstate/tst_qstate.cpp new file mode 100644 index 0000000..75b1905 --- /dev/null +++ b/tests/auto/qstate/tst_qstate.cpp @@ -0,0 +1,340 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include "qstate.h" +#include "qstatemachine.h" +#include "qsignaltransition.h" + +// Will try to wait for the condition while allowing event processing +#define QTRY_COMPARE(__expr, __expected) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ((__expr) != (__expected)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QCOMPARE(__expr, __expected); \ + } while(0) + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QState : public QObject +{ + Q_OBJECT + +public: + tst_QState(); + virtual ~tst_QState(); + +private slots: +#if 0 + void test(); +#endif + void assignProperty(); + void assignPropertyTwice(); + void historyInitialState(); + +private: + bool functionCalled; +}; + +tst_QState::tst_QState() : functionCalled(false) +{ +} + +tst_QState::~tst_QState() +{ +} + +#if 0 +void tst_QState::test() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + + QCOMPARE(s1->machine(), &machine); + QCOMPARE(s1->parentState(), machine.rootState()); + QCOMPARE(s1->initialState(), (QState*)0); + QVERIFY(s1->childStates().isEmpty()); + QVERIFY(s1->transitions().isEmpty()); + + QCOMPARE(s1->isFinal(), false); + s1->setFinal(true); + QCOMPARE(s1->isFinal(), true); + s1->setFinal(false); + QCOMPARE(s1->isFinal(), false); + + QCOMPARE(s1->isParallel(), false); + s1->setParallel(true); + QCOMPARE(s1->isParallel(), true); + s1->setParallel(false); + QCOMPARE(s1->isParallel(), false); + + QCOMPARE(s1->isAtomic(), true); + QCOMPARE(s1->isCompound(), false); + QCOMPARE(s1->isComplex(), false); + + QState *s11 = new QState(s1); + QCOMPARE(s11->parentState(), s1); + QCOMPARE(s11->isAtomic(), true); + QCOMPARE(s11->isCompound(), false); + QCOMPARE(s11->isComplex(), false); + QCOMPARE(s11->machine(), s1->machine()); + QVERIFY(s11->isDescendantOf(s1)); + + QCOMPARE(s1->initialState(), (QState*)0); + QCOMPARE(s1->childStates().size(), 1); + QCOMPARE(s1->childStates().at(0), s11); + + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), true); + QCOMPARE(s1->isComplex(), true); + + s1->setParallel(true); + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), false); + QCOMPARE(s1->isComplex(), true); + + QState *s12 = new QState(s1); + QCOMPARE(s12->parentState(), s1); + QCOMPARE(s12->isAtomic(), true); + QCOMPARE(s12->isCompound(), false); + QCOMPARE(s12->isComplex(), false); + QCOMPARE(s12->machine(), s1->machine()); + QVERIFY(s12->isDescendantOf(s1)); + QVERIFY(!s12->isDescendantOf(s11)); + + QCOMPARE(s1->initialState(), (QState*)0); + QCOMPARE(s1->childStates().size(), 2); + QCOMPARE(s1->childStates().at(0), s11); + QCOMPARE(s1->childStates().at(1), s12); + + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), false); + QCOMPARE(s1->isComplex(), true); + + s1->setParallel(false); + QCOMPARE(s1->isAtomic(), false); + QCOMPARE(s1->isCompound(), true); + QCOMPARE(s1->isComplex(), true); + + s1->setInitialState(s11); + QCOMPARE(s1->initialState(), s11); + + s1->setInitialState(0); + QCOMPARE(s1->initialState(), (QState*)0); + + s1->setInitialState(s12); + QCOMPARE(s1->initialState(), s12); + + QState *s13 = new QState(); + s1->setInitialState(s13); + QCOMPARE(s13->parentState(), s1); + QCOMPARE(s1->childStates().size(), 3); + QCOMPARE(s1->childStates().at(0), s11); + QCOMPARE(s1->childStates().at(1), s12); + QCOMPARE(s1->childStates().at(2), s13); + QVERIFY(s13->isDescendantOf(s1)); + + QVERIFY(s12->childStates().isEmpty()); + + QState *s121 = new QState(s12); + QCOMPARE(s121->parentState(), s12); + QCOMPARE(s121->isAtomic(), true); + QCOMPARE(s121->isCompound(), false); + QCOMPARE(s121->isComplex(), false); + QCOMPARE(s121->machine(), s12->machine()); + QVERIFY(s121->isDescendantOf(s12)); + QVERIFY(s121->isDescendantOf(s1)); + QVERIFY(!s121->isDescendantOf(s11)); + + QCOMPARE(s12->childStates().size(), 1); + QCOMPARE(s12->childStates().at(0), (QState*)s121); + + QCOMPARE(s1->childStates().size(), 3); + QCOMPARE(s1->childStates().at(0), s11); + QCOMPARE(s1->childStates().at(1), s12); + QCOMPARE(s1->childStates().at(2), s13); + + s11->addTransition(s12); + QCOMPARE(s11->transitions().size(), 1); + QCOMPARE(s11->transitions().at(0)->sourceState(), s11); + QCOMPARE(s11->transitions().at(0)->targetStates().size(), 1); + QCOMPARE(s11->transitions().at(0)->targetStates().at(0), s12); + QCOMPARE(s11->transitions().at(0)->eventType(), QEvent::None); + + QState *s14 = new QState(); + s12->addTransition(QList<QState*>() << s13 << s14); + QCOMPARE(s12->transitions().size(), 1); + QCOMPARE(s12->transitions().at(0)->sourceState(), s12); + QCOMPARE(s12->transitions().at(0)->targetStates().size(), 2); + QCOMPARE(s12->transitions().at(0)->targetStates().at(0), s13); + QCOMPARE(s12->transitions().at(0)->targetStates().at(1), s14); + QCOMPARE(s12->transitions().at(0)->eventType(), QEvent::None); + + s13->addTransition(this, SIGNAL(destroyed()), s14); + QCOMPARE(s13->transitions().size(), 1); + QCOMPARE(s13->transitions().at(0)->sourceState(), s13); + QCOMPARE(s13->transitions().at(0)->targetStates().size(), 1); + QCOMPARE(s13->transitions().at(0)->targetStates().at(0), s14); + QCOMPARE(s13->transitions().at(0)->eventType(), QEvent::Signal); + QVERIFY(qobject_cast<QSignalTransition*>(s13->transitions().at(0)) != 0); + + delete s13->transitions().at(0); + QCOMPARE(s13->transitions().size(), 0); + + s12->addTransition(this, SIGNAL(destroyed()), s11); + QCOMPARE(s12->transitions().size(), 2); +} +#endif + +class TestClass: public QObject +{ + Q_OBJECT +public: + TestClass() : called(false) {} + bool called; + +public slots: + void slot() { called = true; } + + +}; + +void tst_QState::assignProperty() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("fooBar", 10); + + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(object, "fooBar", 20); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("fooBar").toInt(), 20); +} + +void tst_QState::assignPropertyTwice() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("fooBar", 10); + + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(object, "fooBar", 20); + s1->assignProperty(object, "fooBar", 30); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("fooBar").toInt(), 30); +} + +class EventTestTransition: public QAbstractTransition +{ +public: + EventTestTransition(QEvent::Type type, QState *targetState) + : QAbstractTransition(QList<QAbstractState*>() << targetState), m_type(type) + { + } + +protected: + bool eventTest(QEvent *e) + { + return e->type() == m_type; + } + + void onTransition(QEvent *) {} + +private: + QEvent::Type m_type; + +}; + +void tst_QState::historyInitialState() +{ + QStateMachine machine; + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + QHistoryState *h1 = new QHistoryState(s2); + + s2->setInitialState(h1); + + QState *s3 = new QState(s2); + h1->setDefaultState(s3); + + QState *s4 = new QState(s2); + + s1->addTransition(new EventTestTransition(QEvent::User, s2)); + s2->addTransition(new EventTestTransition(QEvent::User, s1)); + s3->addTransition(new EventTestTransition(QEvent::Type(QEvent::User+1), s4)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s3)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s3)); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User+1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s4)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s4)); +} + + +QTEST_MAIN(tst_QState) +#include "tst_qstate.moc" diff --git a/tests/auto/qstatemachine/qstatemachine.pro b/tests/auto/qstatemachine/qstatemachine.pro new file mode 100644 index 0000000..e5b32b5 --- /dev/null +++ b/tests/auto/qstatemachine/qstatemachine.pro @@ -0,0 +1,4 @@ +load(qttest_p4) +QT = core gui +SOURCES += tst_qstatemachine.cpp + diff --git a/tests/auto/qstatemachine/tst_qstatemachine.cpp b/tests/auto/qstatemachine/tst_qstatemachine.cpp new file mode 100644 index 0000000..40c01bd --- /dev/null +++ b/tests/auto/qstatemachine/tst_qstatemachine.cpp @@ -0,0 +1,3548 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtCore/QCoreApplication> +#include <QtGui/QPushButton> + +#include "qstatemachine.h" +#include "qstate.h" +#include "qhistorystate.h" +#include "qkeyeventtransition.h" +#include "qmouseeventtransition.h" +#include "private/qstate_p.h" +#include "private/qstatemachine_p.h" + +// Will try to wait for the condition while allowing event processing +#define QTRY_COMPARE(__expr, __expected) \ + do { \ + const int __step = 50; \ + const int __timeout = 5000; \ + if ((__expr) != (__expected)) { \ + QTest::qWait(0); \ + } \ + for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \ + QTest::qWait(__step); \ + } \ + QCOMPARE(__expr, __expected); \ + } while(0) + +//TESTED_CLASS= +//TESTED_FILES= + +static int globalTick; + +// Run exec for a maximum of TIMEOUT msecs +#define QCOREAPPLICATION_EXEC(TIMEOUT) \ +{ \ + QTimer timer; \ + timer.setSingleShot(true); \ + timer.setInterval(TIMEOUT); \ + timer.start(); \ + connect(&timer, SIGNAL(timeout()), QCoreApplication::instance(), SLOT(quit())); \ + QCoreApplication::exec(); \ +} + +class tst_QStateMachine : public QObject +{ + Q_OBJECT +public: + tst_QStateMachine(); + virtual ~tst_QStateMachine(); + +private slots: + void init(); + void cleanup(); + + void rootState(); + void addAndRemoveState(); + void stateEntryAndExit(); + void assignProperty(); + void assignPropertyWithAnimation(); + void postEvent(); + void stateFinished(); + void parallelStates(); + void allSourceToTargetConfigurations(); + void signalTransitions(); + void eventTransitions(); + void historyStates(); + void startAndStop(); + void targetStateWithNoParent(); + void targetStateDeleted(); + void transitionToRootState(); + void transitionEntersParent(); + + void defaultErrorState(); + void customGlobalErrorState(); + void customLocalErrorStateInBrokenState(); + void customLocalErrorStateInOtherState(); + void customLocalErrorStateInParentOfBrokenState(); + void customLocalErrorStateOverridesParent(); + void errorStateHasChildren(); + void errorStateHasErrors(); + void errorStateIsRootState(); + void errorStateEntersParentFirst(); + void customErrorStateIsNull(); + void clearError(); + void historyStateHasNowhereToGo(); + void historyStateAsInitialState(); + void brokenStateIsNeverEntered(); + void customErrorStateNotInGraph(); + void transitionToStateNotInGraph(); + void restoreProperties(); + + void defaultGlobalRestorePolicy(); + void globalRestorePolicySetToRestore(); + void globalRestorePolicySetToDoNotRestore(); + + //void restorePolicyNotInherited(); + //void mixedRestoreProperties(); + //void setRestorePolicyToDoNotRestore(); + //void setGlobalRestorePolicyToGlobalRestore(); + //void restorePolicyOnChildState(); + + void transitionWithParent(); + void transitionsFromParallelStateWithNoChildren(); + void parallelStateTransition(); + void parallelStateAssignmentsDone(); + void nestedRestoreProperties(); + void nestedRestoreProperties2(); + + void simpleAnimation(); + void twoAnimations(); + void twoAnimatedTransitions(); + void playAnimationTwice(); + void nestedTargetStateForAnimation(); + void animatedGlobalRestoreProperty(); + void specificTargetValueOfAnimation(); + + void addDefaultAnimation(); + void addDefaultAnimationWithUnusedAnimation(); + void removeDefaultAnimation(); + void overrideDefaultAnimationWithSpecific(); + +// void addDefaultAnimationForSource(); +// void addDefaultAnimationForTarget(); +// void removeDefaultAnimationForSource(); +// void removeDefaultAnimationForTarget(); +// void overrideDefaultAnimationWithSource(); +// void overrideDefaultAnimationWithTarget(); +// void overrideDefaultSourceAnimationWithSpecific(); +// void overrideDefaultTargetAnimationWithSpecific(); +// void overrideDefaultTargetAnimationWithSource(); +}; + +tst_QStateMachine::tst_QStateMachine() +{ +} + +tst_QStateMachine::~tst_QStateMachine() +{ +} + +class TestState : public QState +{ +public: + enum Event { + Entry, + Exit + }; + TestState(QState *parent) + : QState(parent) {} + QList<QPair<int, Event> > events; +protected: + virtual void onEntry(QEvent *) { + events.append(qMakePair(globalTick++, Entry)); + } + virtual void onExit(QEvent *) { + events.append(qMakePair(globalTick++, Exit)); + } +}; + +class TestTransition : public QAbstractTransition +{ +public: + TestTransition(QAbstractState *target) + : QAbstractTransition(QList<QAbstractState*>() << target) {} + QList<int> triggers; +protected: + virtual bool eventTest(QEvent *) { + return true; + } + virtual void onTransition(QEvent *) { + triggers.append(globalTick++); + } +}; + +void tst_QStateMachine::init() +{ +} + +void tst_QStateMachine::cleanup() +{ +} + +class EventTransition : public QAbstractTransition +{ +public: + EventTransition(QEvent::Type type, QAbstractState *target, QState *parent = 0) + : QAbstractTransition(QList<QAbstractState*>() << target, parent), m_type(type) {} +protected: + virtual bool eventTest(QEvent *e) { + return (e->type() == m_type); + } + virtual void onTransition(QEvent *) {} +private: + QEvent::Type m_type; +}; + +void tst_QStateMachine::transitionToRootState() +{ + QStateMachine machine; + + QState *initialState = new QState(); + machine.addState(initialState); + machine.setInitialState(initialState); + + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::setTargetStates: root state cannot be target of transition"); + initialState->addTransition(new EventTransition(QEvent::User, machine.rootState())); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); +} + +void tst_QStateMachine::transitionEntersParent() +{ + QStateMachine machine; + + QObject *entryController = new QObject(&machine); + entryController->setObjectName("entryController"); + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("stateEntered", false); + + QState *greatGrandParent = new QState(); + greatGrandParent->setObjectName("grandParent"); + greatGrandParent->assignProperty(entryController, "greatGrandParentEntered", true); + machine.addState(greatGrandParent); + machine.setInitialState(greatGrandParent); + + QState *grandParent = new QState(greatGrandParent); + grandParent->setObjectName("grandParent"); + grandParent->assignProperty(entryController, "grandParentEntered", true); + + QState *parent = new QState(grandParent); + parent->setObjectName("parent"); + parent->assignProperty(entryController, "parentEntered", true); + + QState *state = new QState(parent); + state->setObjectName("state"); + state->assignProperty(entryController, "stateEntered", true); + + QState *initialStateOfGreatGrandParent = new QState(greatGrandParent); + initialStateOfGreatGrandParent->setObjectName("initialStateOfGreatGrandParent"); + greatGrandParent->setInitialState(initialStateOfGreatGrandParent); + + initialStateOfGreatGrandParent->addTransition(new EventTransition(QEvent::User, state)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), true); + QCOMPARE(entryController->property("grandParentEntered").toBool(), false); + QCOMPARE(entryController->property("parentEntered").toBool(), false); + QCOMPARE(entryController->property("stateEntered").toBool(), false); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(initialStateOfGreatGrandParent)); + + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("stateEntered", false); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), false); + QCOMPARE(entryController->property("grandParentEntered").toBool(), true); + QCOMPARE(entryController->property("parentEntered").toBool(), true); + QCOMPARE(entryController->property("stateEntered").toBool(), true); + QCOMPARE(machine.configuration().count(), 4); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(grandParent)); + QVERIFY(machine.configuration().contains(parent)); + QVERIFY(machine.configuration().contains(state)); +} + +void tst_QStateMachine::defaultErrorState() +{ + QStateMachine machine; + QVERIFY(machine.errorState() != 0); + + QState *brokenState = new QState(); + brokenState->setObjectName("MyInitialState"); + + machine.addState(brokenState); + machine.setInitialState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'MyInitialState'"); + + // initialState has no initial state + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'MyInitialState'")); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); +} + +class CustomErrorState: public QState +{ +public: + CustomErrorState(QStateMachine *machine, QState *parent = 0) + : QState(parent), error(QStateMachine::NoError), m_machine(machine) + { + } + + void onEntry(QEvent *) + { + error = m_machine->error(); + errorString = m_machine->errorString(); + } + + QStateMachine::Error error; + QString errorString; + +private: + QStateMachine *m_machine; +}; + +void tst_QStateMachine::customGlobalErrorState() +{ + QStateMachine machine; + + CustomErrorState *customErrorState = new CustomErrorState(&machine); + customErrorState->setObjectName("customErrorState"); + machine.addState(customErrorState); + machine.setErrorState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.errorState(), customErrorState); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(initialState)); + + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorState)); + QCOMPARE(customErrorState->error, QStateMachine::NoInitialStateError); + QCOMPARE(customErrorState->errorString, QString::fromLatin1("Missing initial state in compound state 'brokenState'")); + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'")); +} + +void tst_QStateMachine::customLocalErrorStateInBrokenState() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + machine.addState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + brokenState->setErrorState(customErrorState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorState)); + QCOMPARE(customErrorState->error, QStateMachine::NoInitialStateError); +} + +void tst_QStateMachine::customLocalErrorStateInOtherState() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + machine.addState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + QTest::ignoreMessage(QtWarningMsg, "QState::setErrorState: error state cannot belong to a different state machine"); + initialState->setErrorState(customErrorState); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'brokenState'"); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); +} + +void tst_QStateMachine::customLocalErrorStateInParentOfBrokenState() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + machine.addState(customErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *parentOfBrokenState = new QState(); + machine.addState(parentOfBrokenState); + parentOfBrokenState->setObjectName("parentOfBrokenState"); + parentOfBrokenState->setErrorState(customErrorState); + + QState *brokenState = new QState(parentOfBrokenState); + brokenState->setObjectName("brokenState"); + parentOfBrokenState->setInitialState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorState)); +} + +void tst_QStateMachine::customLocalErrorStateOverridesParent() +{ + QStateMachine machine; + CustomErrorState *customErrorStateForParent = new CustomErrorState(&machine); + machine.addState(customErrorStateForParent); + + CustomErrorState *customErrorStateForBrokenState = new CustomErrorState(&machine); + machine.addState(customErrorStateForBrokenState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *parentOfBrokenState = new QState(); + machine.addState(parentOfBrokenState); + parentOfBrokenState->setObjectName("parentOfBrokenState"); + parentOfBrokenState->setErrorState(customErrorStateForParent); + + QState *brokenState = new QState(parentOfBrokenState); + brokenState->setObjectName("brokenState"); + brokenState->setErrorState(customErrorStateForBrokenState); + parentOfBrokenState->setInitialState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(customErrorStateForBrokenState)); + QCOMPARE(customErrorStateForBrokenState->error, QStateMachine::NoInitialStateError); + QCOMPARE(customErrorStateForParent->error, QStateMachine::NoError); +} + +void tst_QStateMachine::errorStateHasChildren() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + customErrorState->setObjectName("customErrorState"); + machine.addState(customErrorState); + + machine.setErrorState(customErrorState); + + QState *childOfErrorState = new QState(customErrorState); + childOfErrorState->setObjectName("childOfErrorState"); + customErrorState->setInitialState(childOfErrorState); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(customErrorState)); + QVERIFY(machine.configuration().contains(childOfErrorState)); +} + + +void tst_QStateMachine::errorStateHasErrors() +{ + QStateMachine machine; + CustomErrorState *customErrorState = new CustomErrorState(&machine); + customErrorState->setObjectName("customErrorState"); + machine.addState(customErrorState); + + QAbstractState *oldErrorState = machine.errorState(); + machine.setErrorState(customErrorState); + + QState *childOfErrorState = new QState(customErrorState); + childOfErrorState->setObjectName("childOfErrorState"); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'customErrorState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(oldErrorState)); // Fall back to default + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'customErrorState'")); +} + +void tst_QStateMachine::errorStateIsRootState() +{ + QStateMachine machine; + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::setErrorState: root state cannot be error state"); + machine.setErrorState(machine.rootState()); + + QState *initialState = new QState(); + initialState->setObjectName("initialState"); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + brokenState->setObjectName("brokenState"); + machine.addState(brokenState); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialState->addTransition(new EventTransition(QEvent::Type(QEvent::User + 1), brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::Type(QEvent::User + 1))); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'brokenState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); +} + +void tst_QStateMachine::errorStateEntersParentFirst() +{ + QStateMachine machine; + + QObject *entryController = new QObject(&machine); + entryController->setObjectName("entryController"); + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("errorStateEntered", false); + + QState *greatGrandParent = new QState(); + greatGrandParent->setObjectName("greatGrandParent"); + greatGrandParent->assignProperty(entryController, "greatGrandParentEntered", true); + machine.addState(greatGrandParent); + machine.setInitialState(greatGrandParent); + + QState *grandParent = new QState(greatGrandParent); + grandParent->setObjectName("grandParent"); + grandParent->assignProperty(entryController, "grandParentEntered", true); + + QState *parent = new QState(grandParent); + parent->setObjectName("parent"); + parent->assignProperty(entryController, "parentEntered", true); + + QState *errorState = new QState(parent); + errorState->setObjectName("errorState"); + errorState->assignProperty(entryController, "errorStateEntered", true); + machine.setErrorState(errorState); + + QState *initialStateOfGreatGrandParent = new QState(greatGrandParent); + initialStateOfGreatGrandParent->setObjectName("initialStateOfGreatGrandParent"); + greatGrandParent->setInitialState(initialStateOfGreatGrandParent); + + QState *brokenState = new QState(greatGrandParent); + brokenState->setObjectName("brokenState"); + + QState *childState = new QState(brokenState); + childState->setObjectName("childState"); + + initialStateOfGreatGrandParent->addTransition(new EventTransition(QEvent::User, brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), true); + QCOMPARE(entryController->property("grandParentEntered").toBool(), false); + QCOMPARE(entryController->property("parentEntered").toBool(), false); + QCOMPARE(entryController->property("errorStateEntered").toBool(), false); + QCOMPARE(machine.configuration().count(), 2); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(initialStateOfGreatGrandParent)); + + entryController->setProperty("greatGrandParentEntered", false); + entryController->setProperty("grandParentEntered", false); + entryController->setProperty("parentEntered", false); + entryController->setProperty("errorStateEntered", false); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("greatGrandParentEntered").toBool(), false); + QCOMPARE(entryController->property("grandParentEntered").toBool(), true); + QCOMPARE(entryController->property("parentEntered").toBool(), true); + QCOMPARE(entryController->property("errorStateEntered").toBool(), true); + QCOMPARE(machine.configuration().count(), 4); + QVERIFY(machine.configuration().contains(greatGrandParent)); + QVERIFY(machine.configuration().contains(grandParent)); + QVERIFY(machine.configuration().contains(parent)); + QVERIFY(machine.configuration().contains(errorState)); +} + +void tst_QStateMachine::customErrorStateIsNull() +{ + QStateMachine machine; + QAbstractState *oldErrorState = machine.errorState(); + machine.rootState()->setErrorState(0); + + QState *initialState = new QState(); + machine.addState(initialState); + machine.setInitialState(initialState); + + QState *brokenState = new QState(); + machine.addState(brokenState); + + new QState(brokenState); + initialState->addTransition(new EventTransition(QEvent::User, brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state ''"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.errorState(), reinterpret_cast<void *>(0)); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(oldErrorState)); +} + +void tst_QStateMachine::clearError() +{ + QStateMachine machine; + machine.setErrorState(new QState(machine.rootState())); // avoid warnings + + QState *brokenState = new QState(machine.rootState()); + brokenState->setObjectName("brokenState"); + machine.setInitialState(brokenState); + new QState(brokenState); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.error(), QStateMachine::NoInitialStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing initial state in compound state 'brokenState'")); + + machine.clearError(); + + QCOMPARE(machine.error(), QStateMachine::NoError); + QVERIFY(machine.errorString().isEmpty()); +} + +void tst_QStateMachine::historyStateAsInitialState() +{ + QStateMachine machine; + + QHistoryState *hs = new QHistoryState(machine.rootState()); + machine.setInitialState(hs); + + QState *s1 = new QState(machine.rootState()); + hs->setDefaultState(s1); + + QState *s2 = new QState(machine.rootState()); + + QHistoryState *s2h = new QHistoryState(s2); + s2->setInitialState(s2h); + + QState *s21 = new QState(s2); + s2h->setDefaultState(s21); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); +} + +void tst_QStateMachine::historyStateHasNowhereToGo() +{ + QStateMachine machine; + + QState *initialState = new QState(machine.rootState()); + machine.setInitialState(initialState); + machine.setErrorState(new QState(machine.rootState())); // avoid warnings + + QState *brokenState = new QState(machine.rootState()); + brokenState->setObjectName("brokenState"); + brokenState->setInitialState(new QState(brokenState)); + + QHistoryState *historyState = new QHistoryState(brokenState); + historyState->setObjectName("historyState"); + initialState->addTransition(new EventTransition(QEvent::User, historyState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); + QCOMPARE(machine.error(), QStateMachine::NoDefaultStateInHistoryStateError); + QCOMPARE(machine.errorString(), QString::fromLatin1("Missing default state in history state 'historyState'")); +} + +void tst_QStateMachine::brokenStateIsNeverEntered() +{ + QStateMachine machine; + + QObject *entryController = new QObject(&machine); + entryController->setProperty("brokenStateEntered", false); + entryController->setProperty("childStateEntered", false); + entryController->setProperty("errorStateEntered", false); + + QState *initialState = new QState(machine.rootState()); + machine.setInitialState(initialState); + + QState *errorState = new QState(machine.rootState()); + errorState->assignProperty(entryController, "errorStateEntered", true); + machine.setErrorState(errorState); + + QState *brokenState = new QState(machine.rootState()); + brokenState->assignProperty(entryController, "brokenStateEntered", true); + brokenState->setObjectName("brokenState"); + + QState *childState = new QState(brokenState); + childState->assignProperty(entryController, "childStateEntered", true); + + initialState->addTransition(new EventTransition(QEvent::User, brokenState)); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(entryController->property("errorStateEntered").toBool(), true); + QCOMPARE(entryController->property("brokenStateEntered").toBool(), false); + QCOMPARE(entryController->property("childStateEntered").toBool(), false); +} + +void tst_QStateMachine::transitionToStateNotInGraph() +{ + QStateMachine machine; + + QState *initialState = new QState(machine.rootState()); + initialState->setObjectName("initialState"); + machine.setInitialState(initialState); + + QState independentState; + independentState.setObjectName("independentState"); + initialState->addTransition(&independentState); + + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 'initialState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(qobject_cast<QState*>(machine.rootState())->errorState())); +} + +void tst_QStateMachine::customErrorStateNotInGraph() +{ + QStateMachine machine; + + QState errorState; + errorState.setObjectName("errorState"); + QTest::ignoreMessage(QtWarningMsg, "QState::setErrorState: error state cannot belong to a different state machine"); + machine.setErrorState(&errorState); + QVERIFY(&errorState != machine.errorState()); + + QState *initialBrokenState = new QState(machine.rootState()); + initialBrokenState->setObjectName("initialBrokenState"); + machine.setInitialState(initialBrokenState); + new QState(initialBrokenState); + + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: Missing initial state in compound state 'initialBrokenState'"); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); +} + +void tst_QStateMachine::restoreProperties() +{ + QStateMachine machine; + QCOMPARE(machine.globalRestorePolicy(), QStateMachine::DoNotRestoreProperties); + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *object = new QObject(&machine); + object->setProperty("a", 1); + object->setProperty("b", 2); + + QState *S1 = new QState(); + S1->setObjectName("S1"); + S1->assignProperty(object, "a", 3); + machine.addState(S1); + + QState *S2 = new QState(); + S2->setObjectName("S2"); + S2->assignProperty(object, "b", 5); + machine.addState(S2); + + QState *S3 = new QState(); + S3->setObjectName("S3"); + machine.addState(S3); + + QFinalState *S4 = new QFinalState(); + machine.addState(S4); + + S1->addTransition(new EventTransition(QEvent::User, S2)); + S2->addTransition(new EventTransition(QEvent::User, S3)); + S3->addTransition(S4); + + machine.setInitialState(S1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 1); + QCOMPARE(object->property("b").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 1); + QCOMPARE(object->property("b").toInt(), 2); +} + +void tst_QStateMachine::rootState() +{ + QStateMachine machine; + QVERIFY(machine.rootState() != 0); + QVERIFY(qobject_cast<QState*>(machine.rootState()) != 0); + QCOMPARE(qobject_cast<QState*>(machine.rootState())->parentState(), (QState*)0); + QCOMPARE(machine.rootState()->parent(), (QObject*)&machine); + QCOMPARE(machine.rootState()->machine(), &machine); + + QState *s1 = new QState(machine.rootState()); + QCOMPARE(s1->parentState(), machine.rootState()); + + QState *s2 = new QState(); + s2->setParent(&machine); + QCOMPARE(s2->parentState(), machine.rootState()); +} + +void tst_QStateMachine::addAndRemoveState() +{ + QStateMachine machine; + QStatePrivate *root_d = QStatePrivate::get(machine.rootState()); + QCOMPARE(root_d->childStates().size(), 1); // the error state + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)machine.errorState()); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: cannot add null state"); + machine.addState(0); + + QState *s1 = new QState(); + QCOMPARE(s1->parentState(), (QState*)0); + QCOMPARE(s1->machine(), (QStateMachine*)0); + machine.addState(s1); + QCOMPARE(s1->machine(), &machine); + QCOMPARE(s1->parentState(), machine.rootState()); + QCOMPARE(root_d->childStates().size(), 2); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)machine.errorState()); + QCOMPARE(root_d->childStates().at(1), (QAbstractState*)s1); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: state has already been added to this machine"); + machine.addState(s1); + + QState *s2 = new QState(); + QCOMPARE(s2->parentState(), (QState*)0); + machine.addState(s2); + QCOMPARE(s2->parentState(), machine.rootState()); + QCOMPARE(root_d->childStates().size(), 3); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)machine.errorState()); + QCOMPARE(root_d->childStates().at(1), (QAbstractState*)s1); + QCOMPARE(root_d->childStates().at(2), (QAbstractState*)s2); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::addState: state has already been added to this machine"); + machine.addState(s2); + + machine.removeState(s1); + QCOMPARE(s1->parentState(), (QState*)0); + QCOMPARE(root_d->childStates().size(), 2); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)machine.errorState()); + QCOMPARE(root_d->childStates().at(1), (QAbstractState*)s2); + + machine.removeState(s2); + QCOMPARE(s2->parentState(), (QState*)0); + QCOMPARE(root_d->childStates().size(), 1); + QCOMPARE(root_d->childStates().at(0), (QAbstractState*)machine.errorState()); + + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::removeState: cannot remove null state"); + machine.removeState(0); + + delete s1; + delete s2; + // ### how to deal with this? + // machine.removeState(machine.errorState()); +} + +void tst_QStateMachine::stateEntryAndExit() +{ + // Two top-level states + { + QStateMachine machine; + + TestState *s1 = new TestState(machine.rootState()); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add transition to null state"); + s1->addTransition((QAbstractState*)0); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add null transition"); + s1->addTransition((QAbstractTransition*)0); + QTest::ignoreMessage(QtWarningMsg, "QState::removeTransition: cannot remove null transition"); + s1->removeTransition((QAbstractTransition*)0); + + TestState *s2 = new TestState(machine.rootState()); + QFinalState *s3 = new QFinalState(machine.rootState()); + + TestTransition *t = new TestTransition(s2); + QCOMPARE(t->machine(), (QStateMachine*)0); + QCOMPARE(t->sourceState(), (QState*)0); + QCOMPARE(t->targetState(), s2); + QCOMPARE(t->targetStates().size(), 1); + QCOMPARE(t->targetStates().at(0), s2); + t->setTargetState(0); + QCOMPARE(t->targetState(), (QState*)0); + QVERIFY(t->targetStates().isEmpty()); + t->setTargetState(s2); + QCOMPARE(t->targetState(), s2); + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::setTargetStates: target state(s) cannot be null"); + t->setTargetStates(QList<QAbstractState*>() << 0); + QCOMPARE(t->targetState(), s2); + t->setTargetStates(QList<QAbstractState*>() << s2); + QCOMPARE(t->targetState(), s2); + QCOMPARE(t->targetStates().size(), 1); + QCOMPARE(t->targetStates().at(0), s2); + QCOMPARE(s1->addTransition(t), (QAbstractTransition*)t); + QCOMPARE(t->sourceState(), s1); + QCOMPARE(t->machine(), &machine); + + { + QAbstractTransition *trans = s2->addTransition(s3); + QVERIFY(trans != 0); + QCOMPARE(trans->sourceState(), (QAbstractState*)s2); + QCOMPARE(trans->targetState(), (QAbstractState*)s3); + { + char warning[256]; + sprintf(warning, "QState::removeTransition: transition %p's source state (%p) is different from this state (%p)", trans, s2, s1); + QTest::ignoreMessage(QtWarningMsg, warning); + s1->removeTransition(trans); + } + s2->removeTransition(trans); + QCOMPARE(trans->sourceState(), (QAbstractState*)0); + QCOMPARE(trans->targetState(), (QAbstractState*)s3); + QCOMPARE(s2->addTransition(trans), trans); + QCOMPARE(trans->sourceState(), (QAbstractState*)s2); + } + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s1); + QCOMPARE(machine.initialState(), (QAbstractState*)s1); + { + char warning[256]; + sprintf(warning, "QState::setInitialState: state %p is not a child of this state (%p)", machine.rootState(), machine.rootState()); + QTest::ignoreMessage(QtWarningMsg, warning); + machine.setInitialState(machine.rootState()); + QCOMPARE(machine.initialState(), (QAbstractState*)s1); + } + QVERIFY(machine.configuration().isEmpty()); + globalTick = 0; + QVERIFY(!machine.isRunning()); + machine.start(); + + QTRY_COMPARE(startedSpy.count(), 1); + QTRY_COMPARE(finishedSpy.count(), 1); + QTRY_COMPARE(stoppedSpy.count(), 0); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s3)); + + // s1 is entered + QCOMPARE(s1->events.count(), 2); + QCOMPARE(s1->events.at(0).first, 0); + QCOMPARE(s1->events.at(0).second, TestState::Entry); + // s1 is exited + QCOMPARE(s1->events.at(1).first, 1); + QCOMPARE(s1->events.at(1).second, TestState::Exit); + // t is triggered + QCOMPARE(t->triggers.count(), 1); + QCOMPARE(t->triggers.at(0), 2); + // s2 is entered + QCOMPARE(s2->events.count(), 2); + QCOMPARE(s2->events.at(0).first, 3); + QCOMPARE(s2->events.at(0).second, TestState::Entry); + // s2 is exited + QCOMPARE(s2->events.at(1).first, 4); + QCOMPARE(s2->events.at(1).second, TestState::Exit); + } + // Two top-level states, one has two child states + { + QStateMachine machine; + + TestState *s1 = new TestState(machine.rootState()); + TestState *s11 = new TestState(s1); + TestState *s12 = new TestState(s1); + TestState *s2 = new TestState(machine.rootState()); + QFinalState *s3 = new QFinalState(machine.rootState()); + s1->setInitialState(s11); + TestTransition *t1 = new TestTransition(s12); + s11->addTransition(t1); + TestTransition *t2 = new TestTransition(s2); + s12->addTransition(t2); + s2->addTransition(s3); + + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s1); + globalTick = 0; + machine.start(); + + QTRY_COMPARE(startedSpy.count(), 1); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s3)); + + // s1 is entered + QCOMPARE(s1->events.count(), 2); + QCOMPARE(s1->events.at(0).first, 0); + QCOMPARE(s1->events.at(0).second, TestState::Entry); + // s11 is entered + QCOMPARE(s11->events.count(), 2); + QCOMPARE(s11->events.at(0).first, 1); + QCOMPARE(s11->events.at(0).second, TestState::Entry); + // s11 is exited + QCOMPARE(s11->events.at(1).first, 2); + QCOMPARE(s11->events.at(1).second, TestState::Exit); + // t1 is triggered + QCOMPARE(t1->triggers.count(), 1); + QCOMPARE(t1->triggers.at(0), 3); + // s12 is entered + QCOMPARE(s12->events.count(), 2); + QCOMPARE(s12->events.at(0).first, 4); + QCOMPARE(s12->events.at(0).second, TestState::Entry); + // s12 is exited + QCOMPARE(s12->events.at(1).first, 5); + QCOMPARE(s12->events.at(1).second, TestState::Exit); + // s1 is exited + QCOMPARE(s1->events.at(1).first, 6); + QCOMPARE(s1->events.at(1).second, TestState::Exit); + // t2 is triggered + QCOMPARE(t2->triggers.count(), 1); + QCOMPARE(t2->triggers.at(0), 7); + // s2 is entered + QCOMPARE(s2->events.count(), 2); + QCOMPARE(s2->events.at(0).first, 8); + QCOMPARE(s2->events.at(0).second, TestState::Entry); + // s2 is exited + QCOMPARE(s2->events.at(1).first, 9); + QCOMPARE(s2->events.at(1).second, TestState::Exit); + } +} + +void tst_QStateMachine::assignProperty() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + + QTest::ignoreMessage(QtWarningMsg, "QState::assignProperty: cannot assign property 'foo' of null object"); + s1->assignProperty(0, "foo", QVariant()); + + s1->assignProperty(s1, "objectName", "s1"); + QFinalState *s2 = new QFinalState(machine.rootState()); + s1->addTransition(s2); + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(s1->objectName(), QString::fromLatin1("s1")); + + s1->assignProperty(s1, "objectName", "foo"); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(s1->objectName(), QString::fromLatin1("foo")); + + s1->assignProperty(s1, "noSuchProperty", 123); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(s1->objectName(), QString::fromLatin1("foo")); + QCOMPARE(s1->dynamicPropertyNames().size(), 1); + QCOMPARE(s1->dynamicPropertyNames().at(0), QByteArray("noSuchProperty")); + + QSignalSpy polishedSpy(s1, SIGNAL(polished())); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(polishedSpy.count(), 1); +} + +void tst_QStateMachine::assignPropertyWithAnimation() +{ + // Single animation + { + QStateMachine machine; + QVERIFY(machine.animationsEnabled()); + machine.setAnimationsEnabled(false); + QVERIFY(!machine.animationsEnabled()); + machine.setAnimationsEnabled(true); + QVERIFY(machine.animationsEnabled()); + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QVERIFY(trans->animations().isEmpty()); + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::addAnimation: cannot add null animation"); + trans->addAnimation(0); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(250); + trans->addAnimation(&anim); + QCOMPARE(trans->animations().size(), 1); + QCOMPARE(trans->animations().at(0), (QAbstractAnimation*)&anim); + QCOMPARE(anim.parent(), (QObject*)0); + QTest::ignoreMessage(QtWarningMsg, "QAbstractTransition::removeAnimation: cannot remove null animation"); + trans->removeAnimation(0); + trans->removeAnimation(&anim); + QVERIFY(trans->animations().isEmpty()); + trans->addAnimation(&anim); + QCOMPARE(trans->animations().size(), 1); + QCOMPARE(trans->animations().at(0), (QAbstractAnimation*)&anim); + QFinalState *s3 = new QFinalState(machine.rootState()); + s2->addTransition(s2, SIGNAL(polished()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 456); + QCOMPARE(obj.property("bar").toInt(), 789); + } + // Two animations + { + QStateMachine machine; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(&obj, "foo", 123); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(150); + trans->addAnimation(&anim); + QPropertyAnimation anim2(&obj, "bar"); + anim2.setDuration(150); + trans->addAnimation(&anim2); + QFinalState *s3 = new QFinalState(machine.rootState()); + s2->addTransition(s2, SIGNAL(polished()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 456); + QCOMPARE(obj.property("bar").toInt(), 789); + } + // Animation group + { + QStateMachine machine; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(&obj, "foo", 123); + s1->assignProperty(&obj, "bar", 321); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(&obj, "foo", 456); + s2->assignProperty(&obj, "bar", 654); + s2->assignProperty(&obj, "baz", 789); + QAbstractTransition *trans = s1->addTransition(s2); + QSequentialAnimationGroup group; + group.addAnimation(new QPropertyAnimation(&obj, "foo")); + group.addAnimation(new QPropertyAnimation(&obj, "bar")); + trans->addAnimation(&group); + QFinalState *s3 = new QFinalState(machine.rootState()); + s2->addTransition(s2, SIGNAL(polished()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 456); + QCOMPARE(obj.property("bar").toInt(), 654); + QCOMPARE(obj.property("baz").toInt(), 789); + } + // Nested states + { + QStateMachine machine; + QObject obj; + obj.setProperty("foo", 321); + obj.setProperty("bar", 654); + QState *s1 = new QState(machine.rootState()); + QCOMPARE(s1->childMode(), QState::ExclusiveStates); + s1->setChildMode(QState::ParallelStates); + QCOMPARE(s1->childMode(), QState::ParallelStates); + s1->setChildMode(QState::ExclusiveStates); + QCOMPARE(s1->childMode(), QState::ExclusiveStates); + QCOMPARE(s1->initialState(), (QAbstractState*)0); + s1->setObjectName("s1"); + s1->assignProperty(&obj, "foo", 123); + s1->assignProperty(&obj, "bar", 456); + QState *s2 = new QState(machine.rootState()); + s2->setObjectName("s2"); + s2->assignProperty(&obj, "foo", 321); + QState *s21 = new QState(s2); + s21->setObjectName("s21"); + s21->assignProperty(&obj, "bar", 654); + QState *s22 = new QState(s2); + s22->setObjectName("s22"); + s22->assignProperty(&obj, "bar", 789); + s2->setInitialState(s21); + QCOMPARE(s2->initialState(), (QAbstractState*)s21); + + QAbstractTransition *trans = s1->addTransition(s2); + QPropertyAnimation anim(&obj, "foo"); + anim.setDuration(500); + trans->addAnimation(&anim); + QPropertyAnimation anim2(&obj, "bar"); + anim2.setDuration(250); + trans->addAnimation(&anim2); + + s21->addTransition(s21, SIGNAL(polished()), s22); + + QFinalState *s3 = new QFinalState(machine.rootState()); + s22->addTransition(s2, SIGNAL(polished()), s3); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(obj.property("foo").toInt(), 321); + QCOMPARE(obj.property("bar").toInt(), 789); + } +} + +struct StringEvent : public QEvent +{ +public: + StringEvent(const QString &val) + : QEvent(QEvent::Type(QEvent::User+2)), + value(val) {} + + QString value; +}; + +class StringTransition : public QAbstractTransition +{ +public: + StringTransition(const QString &value, QAbstractState *target) + : QAbstractTransition(QList<QAbstractState*>() << target), m_value(value) {} + +protected: + virtual bool eventTest(QEvent *e) + { + if (e->type() != QEvent::Type(QEvent::User+2)) + return false; + StringEvent *se = static_cast<StringEvent*>(e); + return (m_value == se->value) && (!m_cond.isValid() || (m_cond.indexIn(m_value) != -1)); + } + virtual void onTransition(QEvent *) {} + +private: + QString m_value; + QRegExp m_cond; +}; + +class StringEventPoster : public QState +{ +public: + StringEventPoster(QStateMachine *machine, const QString &value, QState *parent = 0) + : QState(parent), m_machine(machine), m_value(value), m_delay(0) {} + + void setString(const QString &value) + { m_value = value; } + void setDelay(int delay) + { m_delay = delay; } + +protected: + virtual void onEntry(QEvent *) + { + m_machine->postEvent(new StringEvent(m_value), m_delay); + } + virtual void onExit(QEvent *) {} + +private: + QStateMachine *m_machine; + QString m_value; + int m_delay; +}; + +void tst_QStateMachine::postEvent() +{ + for (int x = 0; x < 2; ++x) { + QStateMachine machine; + { + QEvent e(QEvent::None); + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::postEvent: cannot post event when the state machine is not running"); + machine.postEvent(&e); + } + StringEventPoster *s1 = new StringEventPoster(&machine, "a"); + if (x == 1) + s1->setDelay(100); + QFinalState *s2 = new QFinalState; + s1->addTransition(new StringTransition("a", s2)); + machine.addState(s1); + machine.addState(s2); + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + s1->setString("b"); + QFinalState *s3 = new QFinalState(); + machine.addState(s3); + s1->addTransition(new StringTransition("b", s3)); + finishedSpy.clear(); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s3)); + } +} + +void tst_QStateMachine::stateFinished() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + QState *s1_1 = new QState(s1); + QFinalState *s1_2 = new QFinalState(s1); + s1_1->addTransition(s1_2); + s1->setInitialState(s1_1); + QFinalState *s2 = new QFinalState(machine.rootState()); + s1->addTransition(s1, SIGNAL(finished()), s2); + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +void tst_QStateMachine::parallelStates() +{ + QStateMachine machine; + + QState *s1 = new QState(QState::ParallelStates); + QCOMPARE(s1->childMode(), QState::ParallelStates); + QState *s1_1 = new QState(s1); + QState *s1_1_1 = new QState(s1_1); + QFinalState *s1_1_f = new QFinalState(s1_1); + s1_1_1->addTransition(s1_1_f); + s1_1->setInitialState(s1_1_1); + QState *s1_2 = new QState(s1); + QState *s1_2_1 = new QState(s1_2); + QFinalState *s1_2_f = new QFinalState(s1_2); + s1_2_1->addTransition(s1_2_f); + s1_2->setInitialState(s1_2_1); + { + char warning[256]; + sprintf(warning, "QState::setInitialState: ignoring attempt to set initial state of parallel state group %p", s1); + QTest::ignoreMessage(QtWarningMsg, warning); + s1->setInitialState(0); + } + machine.addState(s1); + + QFinalState *s2 = new QFinalState(); + machine.addState(s2); + + s1->addTransition(s1, SIGNAL(finished()), s2); + + machine.setInitialState(s1); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); +} + +void tst_QStateMachine::allSourceToTargetConfigurations() +{ + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + s0->setObjectName("s0"); + QState *s1 = new QState(s0); + s1->setObjectName("s1"); + QState *s11 = new QState(s1); + s11->setObjectName("s11"); + QState *s2 = new QState(s0); + s2->setObjectName("s2"); + QState *s21 = new QState(s2); + s21->setObjectName("s21"); + QState *s211 = new QState(s21); + s211->setObjectName("s211"); + QFinalState *f = new QFinalState(machine.rootState()); + f->setObjectName("f"); + + s0->setInitialState(s1); + s1->setInitialState(s11); + s2->setInitialState(s21); + s21->setInitialState(s211); + + s11->addTransition(new StringTransition("g", s211)); + s1->addTransition(new StringTransition("a", s1)); + s1->addTransition(new StringTransition("b", s11)); + s1->addTransition(new StringTransition("c", s2)); + s1->addTransition(new StringTransition("d", s0)); + s1->addTransition(new StringTransition("f", s211)); + s211->addTransition(new StringTransition("d", s21)); + s211->addTransition(new StringTransition("g", s0)); + s211->addTransition(new StringTransition("h", f)); + s21->addTransition(new StringTransition("b", s211)); + s2->addTransition(new StringTransition("c", s1)); + s2->addTransition(new StringTransition("f", s11)); + s0->addTransition(new StringTransition("e", s211)); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new StringEvent("a")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("b")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("c")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("d")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("e")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("f")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("g")); + QCoreApplication::processEvents(); + machine.postEvent(new StringEvent("h")); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); +} + +class SignalEmitter : public QObject +{ +Q_OBJECT + public: + SignalEmitter(QObject *parent = 0) + : QObject(parent) {} + void emitSignalWithNoArg() + { emit signalWithNoArg(); } + void emitSignalWithIntArg(int arg) + { emit signalWithIntArg(arg); } + void emitSignalWithStringArg(const QString &arg) + { emit signalWithStringArg(arg); } +Q_SIGNALS: + void signalWithNoArg(); + void signalWithIntArg(int); + void signalWithStringArg(const QString &); +}; + +class TestSignalTransition : public QSignalTransition +{ +public: + TestSignalTransition(QState *sourceState = 0) + : QSignalTransition(sourceState) {} + TestSignalTransition(QObject *sender, const char *signal, + QAbstractState *target) + : QSignalTransition(sender, signal, QList<QAbstractState*>() << target) {} + QVariantList argumentsReceived() const { + return m_args; + } +protected: + bool eventTest(QEvent *e) { + if (!QSignalTransition::eventTest(e)) + return false; + QSignalEvent *se = static_cast<QSignalEvent*>(e); + const_cast<TestSignalTransition*>(this)->m_args = se->arguments(); + return true; + } +private: + QVariantList m_args; +}; + +void tst_QStateMachine::signalTransitions() +{ + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: sender cannot be null"); + QCOMPARE(s0->addTransition(0, SIGNAL(noSuchSignal()), 0), (QObject*)0); + + SignalEmitter emitter; + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: signal cannot be null"); + QCOMPARE(s0->addTransition(&emitter, 0, 0), (QObject*)0); + + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: cannot add transition to null state"); + QCOMPARE(s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), 0), (QObject*)0); + + QFinalState *s1 = new QFinalState(machine.rootState()); + QTest::ignoreMessage(QtWarningMsg, "QState::addTransition: no such signal SignalEmitter::noSuchSignal()"); + QCOMPARE(s0->addTransition(&emitter, SIGNAL(noSuchSignal()), s1), (QObject*)0); + + { + QSignalTransition *trans = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); + QVERIFY(trans != 0); + QCOMPARE(trans->sourceState(), s0); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + QCOMPARE(trans->senderObject(), (QObject*)&emitter); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); + } + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithNoArg(); + + QTRY_COMPARE(finishedSpy.count(), 1); + + emitter.emitSignalWithNoArg(); + } + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + SignalEmitter emitter; + TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithIntArg(int)), s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithIntArg(123); + + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(trans->argumentsReceived().size(), 1); + QCOMPARE(trans->argumentsReceived().at(0).toInt(), 123); + } + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + SignalEmitter emitter; + TestSignalTransition *trans = new TestSignalTransition(&emitter, SIGNAL(signalWithStringArg(QString)), s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QString testString = QString::fromLatin1("hello"); + emitter.emitSignalWithStringArg(testString); + + QTRY_COMPARE(finishedSpy.count(), 1); + QCOMPARE(trans->argumentsReceived().size(), 1); + QCOMPARE(trans->argumentsReceived().at(0).toString(), testString); + } + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + + TestSignalTransition *trans = new TestSignalTransition(); + QCOMPARE(trans->senderObject(), (QObject*)0); + QCOMPARE(trans->signal(), QByteArray()); + + SignalEmitter emitter; + trans->setSenderObject(&emitter); + QCOMPARE(trans->senderObject(), (QObject*)&emitter); + trans->setSignal(SIGNAL(signalWithNoArg())); + QCOMPARE(trans->signal(), QByteArray(SIGNAL(signalWithNoArg()))); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + emitter.emitSignalWithNoArg(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + // Multiple transitions for same (object,signal) + { + QStateMachine machine; + SignalEmitter emitter; + QState *s0 = new QState(machine.rootState()); + QState *s1 = new QState(machine.rootState()); + QSignalTransition *t0 = s0->addTransition(&emitter, SIGNAL(signalWithNoArg()), s1); + QSignalTransition *t1 = s1->addTransition(&emitter, SIGNAL(signalWithNoArg()), s0); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + s0->removeTransition(t0); + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s1->removeTransition(t1); + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s0->addTransition(t0); + s1->addTransition(t1); + emitter.emitSignalWithNoArg(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + } +} + +void tst_QStateMachine::eventTransitions() +{ + QPushButton button; + for (int x = 0; x < 2; ++x) { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + + QMouseEventTransition *trans; + if (x == 0) { + trans = new QMouseEventTransition(&button, QEvent::MouseButtonPress, Qt::LeftButton); + QCOMPARE(trans->targetState(), (QAbstractState*)0); + trans->setTargetState(s1); + } else { + trans = new QMouseEventTransition(&button, QEvent::MouseButtonPress, + Qt::LeftButton, QList<QAbstractState*>() << s1); + } + QCOMPARE(trans->eventType(), QEvent::MouseButtonPress); + QCOMPARE(trans->button(), Qt::LeftButton); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 1); + + QTest::mousePress(&button, Qt::LeftButton); + + trans->setEventType(QEvent::MouseButtonRelease); + QCOMPARE(trans->eventType(), QEvent::MouseButtonRelease); + machine.start(); + QCoreApplication::processEvents(); + QTest::mouseRelease(&button, Qt::LeftButton); + QTRY_COMPARE(finishedSpy.count(), 2); + } + for (int x = 0; x < 3; ++x) { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + + QEventTransition *trans; + if (x == 0) { + trans = new QEventTransition(); + QCOMPARE(trans->eventObject(), (QObject*)0); + QCOMPARE(trans->eventType(), QEvent::None); + trans->setEventObject(&button); + trans->setEventType(QEvent::MouseButtonPress); + trans->setTargetState(s1); + } else if (x == 1) { + trans = new QEventTransition(&button, QEvent::MouseButtonPress); + trans->setTargetState(s1); + } else { + trans = new QEventTransition(&button, QEvent::MouseButtonPress, + QList<QAbstractState*>() << s1); + } + QCOMPARE(trans->eventObject(), (QObject*)&button); + QCOMPARE(trans->eventType(), QEvent::MouseButtonPress); + QCOMPARE(trans->targetState(), (QAbstractState*)s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + + QMouseEventTransition *trans = new QMouseEventTransition(); + QCOMPARE(trans->eventObject(), (QObject*)0); + QCOMPARE(trans->eventType(), QEvent::None); + QCOMPARE(trans->button(), Qt::NoButton); + trans->setEventObject(&button); + trans->setEventType(QEvent::MouseButtonPress); + trans->setButton(Qt::LeftButton); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + + QKeyEventTransition *trans = new QKeyEventTransition(&button, QEvent::KeyPress, Qt::Key_A); + QCOMPARE(trans->eventType(), QEvent::KeyPress); + QCOMPARE(trans->key(), (int)Qt::Key_A); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::keyPress(&button, Qt::Key_A); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QFinalState *s1 = new QFinalState(machine.rootState()); + + QKeyEventTransition *trans = new QKeyEventTransition(); + QCOMPARE(trans->eventObject(), (QObject*)0); + QCOMPARE(trans->eventType(), QEvent::None); + QCOMPARE(trans->key(), 0); + trans->setEventObject(&button); + trans->setEventType(QEvent::KeyPress); + trans->setKey(Qt::Key_A); + trans->setTargetState(s1); + s0->addTransition(trans); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + + QTest::keyPress(&button, Qt::Key_A); + QCoreApplication::processEvents(); + + QTRY_COMPARE(finishedSpy.count(), 1); + } + // Multiple transitions for same (object,event) + { + QStateMachine machine; + QState *s0 = new QState(machine.rootState()); + QState *s1 = new QState(machine.rootState()); + QEventTransition *t0 = new QEventTransition(&button, QEvent::MouseButtonPress); + t0->setTargetState(s1); + s0->addTransition(t0); + QEventTransition *t1 = new QEventTransition(&button, QEvent::MouseButtonPress); + t1->setTargetState(s0); + s1->addTransition(t1); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.setInitialState(s0); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + s0->removeTransition(t0); + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s1->removeTransition(t1); + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s0)); + + s0->addTransition(t0); + s1->addTransition(t1); + QTest::mousePress(&button, Qt::LeftButton); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + } +} + +void tst_QStateMachine::historyStates() +{ + for (int x = 0; x < 2; ++x) { + QStateMachine machine; + QState *root = machine.rootState(); + QState *s0 = new QState(root); + QState *s00 = new QState(s0); + QState *s01 = new QState(s0); + QHistoryState *s0h; + if (x == 0) { + s0h = new QHistoryState(s0); + QCOMPARE(s0h->historyType(), QHistoryState::ShallowHistory); + s0h->setHistoryType(QHistoryState::DeepHistory); + } else { + s0h = new QHistoryState(QHistoryState::DeepHistory, s0); + } + QCOMPARE(s0h->historyType(), QHistoryState::DeepHistory); + s0h->setHistoryType(QHistoryState::ShallowHistory); + QCOMPARE(s0h->historyType(), QHistoryState::ShallowHistory); + QCOMPARE(s0h->defaultState(), (QAbstractState*)0); + s0h->setDefaultState(s00); + QCOMPARE(s0h->defaultState(), (QAbstractState*)s00); + char warning[256]; + sprintf(warning, "QHistoryState::setDefaultState: state %p does not belong to this history state's group (%p)", s0, s0); + QTest::ignoreMessage(QtWarningMsg, warning); + s0h->setDefaultState(s0); + QState *s1 = new QState(root); + QFinalState *s2 = new QFinalState(root); + + s00->addTransition(new StringTransition("a", s01)); + s0->addTransition(new StringTransition("b", s1)); + s1->addTransition(new StringTransition("c", s0h)); + s0->addTransition(new StringTransition("d", s2)); + + root->setInitialState(s0); + s0->setInitialState(s00); + + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s0)); + QVERIFY(machine.configuration().contains(s00)); + + machine.postEvent(new StringEvent("a")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s0)); + QVERIFY(machine.configuration().contains(s01)); + + machine.postEvent(new StringEvent("b")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.postEvent(new StringEvent("c")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s0)); + QVERIFY(machine.configuration().contains(s01)); + + machine.postEvent(new StringEvent("d")); + QCoreApplication::processEvents(); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s2)); + + QTRY_COMPARE(finishedSpy.count(), 1); + } +} + +void tst_QStateMachine::startAndStop() +{ + QStateMachine machine; + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + QVERIFY(!machine.isRunning()); + QTest::ignoreMessage(QtWarningMsg, "QStateMachine::start: No initial state set for machine. Refusing to start."); + machine.start(); + QCOMPARE(startedSpy.count(), 0); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + QVERIFY(!machine.isRunning()); + machine.stop(); + QCOMPARE(startedSpy.count(), 0); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + machine.start(); + QTRY_COMPARE(machine.isRunning(), true); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s1)); + + machine.stop(); + QTRY_COMPARE(machine.isRunning(), false); + QTRY_COMPARE(stoppedSpy.count(), 1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(finishedSpy.count(), 0); + + QCOMPARE(machine.configuration().count(), 1); + QVERIFY(machine.configuration().contains(s1)); +} + +void tst_QStateMachine::targetStateWithNoParent() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + s1->setObjectName("s1"); + QState s2; + s1->addTransition(&s2); + machine.setInitialState(s1); + QSignalSpy startedSpy(&machine, SIGNAL(started())); + QSignalSpy stoppedSpy(&machine, SIGNAL(stopped())); + QSignalSpy finishedSpy(&machine, SIGNAL(finished())); + machine.start(); + QTest::ignoreMessage(QtWarningMsg, "Unrecoverable error detected in running state machine: No common ancestor for targets and source of transition from state 's1'"); + QTRY_COMPARE(machine.isRunning(), true); + QTRY_COMPARE(startedSpy.count(), 1); + QCOMPARE(stoppedSpy.count(), 0); + QCOMPARE(finishedSpy.count(), 0); + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(machine.errorState())); + QCOMPARE(machine.error(), QStateMachine::NoCommonAncestorForTransitionError); +} + +void tst_QStateMachine::targetStateDeleted() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + s1->setObjectName("s1"); + QState *s2 = new QState(machine.rootState()); + QAbstractTransition *trans = s1->addTransition(s2); + delete s2; + QCOMPARE(trans->targetState(), (QAbstractState*)0); + QVERIFY(trans->targetStates().isEmpty()); +} + +void tst_QStateMachine::defaultGlobalRestorePolicy() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(machine.rootState()); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); +} + +/* +void tst_QStateMachine::restorePolicyNotInherited() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *parentState = new QState(machine.rootState()); + parentState->setObjectName("parentState"); + parentState->setRestorePolicy(QState::RestoreProperties); + + QState *s1 = new QState(parentState); + s1->setObjectName("s1"); + s1->assignProperty(propertyHolder, "a", 3); + parentState->setInitialState(s1); + + QState *s2 = new QState(parentState); + s2->setObjectName("s2"); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(parentState); + s3->setObjectName("s3"); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(parentState); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + +}*/ + +void tst_QStateMachine::globalRestorePolicySetToDoNotRestore() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::DoNotRestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(machine.rootState()); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 4); +} + +/* +void tst_QStateMachine::setRestorePolicyToDoNotRestore() +{ + QObject *object = new QObject(); + object->setProperty("a", 1); + object->setProperty("b", 2); + + QStateMachine machine; + + QState *S1 = new QState(); + S1->setObjectName("S1"); + S1->assignProperty(object, "a", 3); + S1->setRestorePolicy(QState::DoNotRestoreProperties); + machine.addState(S1); + + QState *S2 = new QState(); + S2->setObjectName("S2"); + S2->assignProperty(object, "b", 5); + S2->setRestorePolicy(QState::DoNotRestoreProperties); + machine.addState(S2); + + QState *S3 = new QState(); + S3->setObjectName("S3"); + S3->setRestorePolicy(QState::DoNotRestoreProperties); + machine.addState(S3); + + QFinalState *S4 = new QFinalState(); + machine.addState(S4); + + S1->addTransition(new EventTransition(QEvent::User, S2)); + S2->addTransition(new EventTransition(QEvent::User, S3)); + S3->addTransition(S4); + + machine.setInitialState(S1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(object->property("a").toInt(), 3); + QCOMPARE(object->property("b").toInt(), 5); +} + +void tst_QStateMachine::setGlobalRestorePolicyToGlobalRestore() +{ + s_countWarnings = false; + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::GlobalRestorePolicy); + + QCOMPARE(machine.globalRestorePolicy(), QStateMachine::DoNotRestoreProperties); + QCOMPARE(s_msgType, QtWarningMsg); + + s_msgType = QtDebugMsg; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + machine.setGlobalRestorePolicy(QStateMachine::GlobalRestorePolicy); + + QCOMPARE(machine.globalRestorePolicy(), QStateMachine::RestoreProperties); + QCOMPARE(s_msgType, QtWarningMsg); +} + + +void tst_QStateMachine::restorePolicyOnChildState() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *parentState = new QState(machine.rootState()); + parentState->setObjectName("parentState"); + + QState *s1 = new QState(parentState); + s1->setRestorePolicy(QState::RestoreProperties); + s1->setObjectName("s1"); + s1->assignProperty(propertyHolder, "a", 3); + parentState->setInitialState(s1); + + QState *s2 = new QState(parentState); + s2->setRestorePolicy(QState::RestoreProperties); + s2->setObjectName("s2"); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(parentState); + s3->setRestorePolicy(QState::RestoreProperties); + s3->setObjectName("s3"); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(parentState); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 2); +} +*/ + +void tst_QStateMachine::globalRestorePolicySetToRestore() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("a", 1); + propertyHolder->setProperty("b", 2); + + QState *s1 = new QState(machine.rootState()); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(propertyHolder, "b", 4); + + QState *s3 = new QState(machine.rootState()); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 3); + QCOMPARE(propertyHolder->property("b").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("a").toInt(), 1); + QCOMPARE(propertyHolder->property("b").toInt(), 2); +} + +/* +void tst_QStateMachine::mixedRestoreProperties() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(); + propertyHolder->setProperty("a", 1); + + QState *s1 = new QState(machine.rootState()); + s1->setRestorePolicy(QState::RestoreProperties); + s1->assignProperty(propertyHolder, "a", 3); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(propertyHolder, "a", 4); + + QState *s3 = new QState(machine.rootState()); + + QState *s4 = new QState(machine.rootState()); + s4->assignProperty(propertyHolder, "a", 5); + + QState *s5 = new QState(machine.rootState()); + s5->setRestorePolicy(QState::RestoreProperties); + s5->assignProperty(propertyHolder, "a", 6); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s2->addTransition(new EventTransition(QEvent::User, s3)); + s3->addTransition(new EventTransition(QEvent::User, s4)); + s4->addTransition(new EventTransition(QEvent::User, s5)); + s5->addTransition(new EventTransition(QEvent::User, s3)); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + // Enter s1, save current + QCOMPARE(propertyHolder->property("a").toInt(), 3); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s2, restorePolicy == DoNotRestore, so restore all properties + QCOMPARE(propertyHolder->property("a").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s3 + QCOMPARE(propertyHolder->property("a").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s4 + QCOMPARE(propertyHolder->property("a").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s5, save current + QCOMPARE(propertyHolder->property("a").toInt(), 6); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + // Enter s3, restore + QCOMPARE(propertyHolder->property("a").toInt(), 5); +} +*/ + +void tst_QStateMachine::transitionWithParent() +{ + QStateMachine machine; + QState *s1 = new QState(machine.rootState()); + QState *s2 = new QState(machine.rootState()); + EventTransition *trans = new EventTransition(QEvent::User, s2, s1); + QCOMPARE(trans->sourceState(), s1); + QCOMPARE(trans->targetState(), (QAbstractState*)s2); + QCOMPARE(trans->targetStates().size(), 1); + QCOMPARE(trans->targetStates().at(0), (QAbstractState*)s2); +} + +void tst_QStateMachine::simpleAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("fooBar", 1.0); + + QState *s1 = new QState(machine.rootState()); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "fooBar", 2.0); + + EventTransition *et = new EventTransition(QEvent::User, s2); + QPropertyAnimation *animation = new QPropertyAnimation(object, "fooBar", s2); + et->addAnimation(animation); + s1->addTransition(et); + + QState *s3 = new QState(machine.rootState()); + s2->addTransition(animation, SIGNAL(finished()), s3); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("fooBar").toDouble(), 2.0); +} + +class SlotCalledCounter: public QObject +{ + Q_OBJECT +public: + SlotCalledCounter() : counter(0) {} + + int counter; + +public slots: + void slot() { counter++; } +}; + +void tst_QStateMachine::twoAnimations() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + object->setProperty("bar", 3.0); + + QState *s1 = new QState(machine.rootState()); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + s2->assignProperty(object, "bar", 10.0); + + QPropertyAnimation *animationFoo = new QPropertyAnimation(object, "foo", s2); + QPropertyAnimation *animationBar = new QPropertyAnimation(object, "bar", s2); + animationBar->setDuration(900); + + SlotCalledCounter counter; + connect(animationFoo, SIGNAL(finished()), &counter, SLOT(slot())); + connect(animationBar, SIGNAL(finished()), &counter, SLOT(slot())); + + EventTransition *et = new EventTransition(QEvent::User, s2); + et->addAnimation(animationFoo); + et->addAnimation(animationBar); + s1->addTransition(et); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(s2, SIGNAL(polished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(object->property("bar").toDouble(), 10.0); + + QCOMPARE(counter.counter, 2); +} + +void tst_QStateMachine::twoAnimatedTransitions() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 5.0); + QPropertyAnimation *fooAnimation = new QPropertyAnimation(object, "foo", s2); + s1->addTransition(new EventTransition(QEvent::User, s2))->addAnimation(fooAnimation); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(fooAnimation, SIGNAL(finished()), s3); + + QState *s4 = new QState(machine.rootState()); + s4->assignProperty(object, "foo", 2.0); + QPropertyAnimation *fooAnimation2 = new QPropertyAnimation(object, "foo", s4); + s3->addTransition(new EventTransition(QEvent::User, s4))->addAnimation(fooAnimation2); + + QState *s5 = new QState(machine.rootState()); + QObject::connect(s5, SIGNAL(entered()), QApplication::instance(), SLOT(quit())); + s4->addTransition(fooAnimation2, SIGNAL(finished()), s5); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 5.0); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s5)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::playAnimationTwice() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 5.0); + QPropertyAnimation *fooAnimation = new QPropertyAnimation(object, "foo", s2); + s1->addTransition(new EventTransition(QEvent::User, s2))->addAnimation(fooAnimation); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(fooAnimation, SIGNAL(finished()), s3); + + QState *s4 = new QState(machine.rootState()); + s4->assignProperty(object, "foo", 2.0); + s3->addTransition(new EventTransition(QEvent::User, s4))->addAnimation(fooAnimation); + + QState *s5 = new QState(machine.rootState()); + QObject::connect(s5, SIGNAL(entered()), QApplication::instance(), SLOT(quit())); + s4->addTransition(fooAnimation, SIGNAL(finished()), s5); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 5.0); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s5)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::nestedTargetStateForAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + object->setProperty("bar", 3.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + QState *s2 = new QState(machine.rootState()); + + s2->assignProperty(object, "foo", 2.0); + + QState *s2Child = new QState(s2); + s2Child->assignProperty(object, "bar", 10.0); + s2->setInitialState(s2Child); + + QState *s2Child2 = new QState(s2); + s2Child2->assignProperty(object, "bar", 11.0); + QAbstractTransition *at = s2Child->addTransition(new EventTransition(QEvent::User, s2Child2)); + + QPropertyAnimation *animation = new QPropertyAnimation(object, "bar", s2); + animation->setDuration(2000); + connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(animation); + + at = s1->addTransition(new EventTransition(QEvent::User, s2)); + + animation = new QPropertyAnimation(object, "foo", s2); + connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(animation); + + animation = new QPropertyAnimation(object, "bar", s2); + connect(animation, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(animation); + + QState *s3 = new QState(machine.rootState()); + s2->addTransition(s2Child, SIGNAL(polished()), s3); + + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + machine.postEvent(new QEvent(QEvent::User)); + + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(object->property("bar").toDouble(), 10.0); + QCOMPARE(counter.counter, 2); +} + +void tst_QStateMachine::animatedGlobalRestoreProperty() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + + QState *s4 = new QState(machine.rootState()); + QObject::connect(s4, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = s1->addTransition(new EventTransition(QEvent::User, s2)); + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", s2); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(pa); + + at = s2->addTransition(pa, SIGNAL(finished()), s3); + pa = new QPropertyAnimation(object, "foo", s3); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(pa); + + at = s3->addTransition(pa, SIGNAL(finished()), s4); + pa = new QPropertyAnimation(object, "foo", s4); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + at->addAnimation(pa); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s4)); + QCOMPARE(object->property("foo").toDouble(), 1.0); + QCOMPARE(counter.counter, 2); +} + +void tst_QStateMachine::specificTargetValueOfAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QPropertyAnimation *anim = new QPropertyAnimation(object, "foo"); + anim->setEndValue(10.0); + s1->addTransition(new EventTransition(QEvent::User, s2))->addAnimation(anim); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + s2->addTransition(anim, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(anim->endValue().toDouble(), 10.0); +} + +void tst_QStateMachine::addDefaultAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + machine.addDefaultAnimation(pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::addDefaultAnimationWithUnusedAnimation() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + object->setProperty("bar", 2.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + machine.addDefaultAnimation(pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + pa = new QPropertyAnimation(object, "bar", &machine); + connect(pa, SIGNAL(finished()), &counter, SLOT(slot())); + machine.addDefaultAnimation(pa); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); + QCOMPARE(counter.counter, 1); +} + +void tst_QStateMachine::removeDefaultAnimation() +{ + QStateMachine machine; + + QObject propertyHolder; + propertyHolder.setProperty("foo", 0); + + QCOMPARE(machine.defaultAnimations().size(), 0); + + QPropertyAnimation *anim = new QPropertyAnimation(&propertyHolder, "foo"); + + machine.addDefaultAnimation(anim); + + QCOMPARE(machine.defaultAnimations().size(), 1); + QVERIFY(machine.defaultAnimations().contains(anim)); + + machine.removeDefaultAnimation(anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + + machine.addDefaultAnimation(anim); + + QPropertyAnimation *anim2 = new QPropertyAnimation(&propertyHolder, "foo"); + machine.addDefaultAnimation(anim2); + + QCOMPARE(machine.defaultAnimations().size(), 2); + QVERIFY(machine.defaultAnimations().contains(anim)); + QVERIFY(machine.defaultAnimations().contains(anim2)); + + machine.removeDefaultAnimation(anim); + + QCOMPARE(machine.defaultAnimations().size(), 1); + QVERIFY(machine.defaultAnimations().contains(anim2)); + + machine.removeDefaultAnimation(anim2); + QCOMPARE(machine.defaultAnimations().size(), 0); +} + +void tst_QStateMachine::overrideDefaultAnimationWithSpecific() +{ + QStateMachine machine; + + QObject *object = new QObject(&machine); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimation(defaultAnimation); + at->addAnimation(moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +/* +void tst_QStateMachine::addDefaultAnimationForSource() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + machine.addDefaultAnimationForSourceState(s1, pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::addDefaultAnimationForTarget() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + QState *s1 = new QState(machine.rootState()); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *pa = new QPropertyAnimation(object, "foo", &machine); + machine.addDefaultAnimationForTargetState(s2, pa); + s2->addTransition(pa, SIGNAL(finished()), s3); + + machine.setInitialState(s1); + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(object->property("foo").toDouble(), 2.0); +} + +void tst_QStateMachine::removeDefaultAnimationForSource() +{ + QStateMachine machine; + + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 0); + + QPropertyAnimation *anim = new QPropertyAnimation(this, "foo"); + + machine.addDefaultAnimationForSourceState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 1); + QVERIFY(machine.defaultAnimationsForSourceState(machine.rootState()).contains(anim)); + + machine.removeDefaultAnimationForTargetState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 1); + QVERIFY(machine.defaultAnimationsForSourceState(machine.rootState()).contains(anim)); + + machine.removeDefaultAnimationForSourceState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 0); + + machine.addDefaultAnimationForSourceState(machine.rootState(), anim); + + QPropertyAnimation *anim2 = new QPropertyAnimation(this, "foo"); + machine.addDefaultAnimationForSourceState(machine.rootState(), anim2); + + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 2); + QVERIFY(machine.defaultAnimationsForSourceState(machine.rootState()).contains(anim)); + QVERIFY(machine.defaultAnimationsForSourceState(machine.rootState()).contains(anim2)); + + machine.removeDefaultAnimationForSourceState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 1); + QVERIFY(machine.defaultAnimationsForSourceState(machine.rootState()).contains(anim2)); + + machine.removeDefaultAnimationForSourceState(machine.rootState(), anim2); + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 0); +} + +void tst_QStateMachine::removeDefaultAnimationForTarget() +{ + QStateMachine machine; + + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 0); + + QPropertyAnimation *anim = new QPropertyAnimation(this, "foo"); + + machine.addDefaultAnimationForTargetState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 1); + QVERIFY(machine.defaultAnimationsForTargetState(machine.rootState()).contains(anim)); + + machine.removeDefaultAnimationForSourceState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimations().size(), 0); + QCOMPARE(machine.defaultAnimationsForSourceState(machine.rootState()).size(), 0); + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 1); + QVERIFY(machine.defaultAnimationsForTargetState(machine.rootState()).contains(anim)); + + machine.removeDefaultAnimationForTargetState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 0); + + machine.addDefaultAnimationForTargetState(machine.rootState(), anim); + + QPropertyAnimation *anim2 = new QPropertyAnimation(this, "foo"); + machine.addDefaultAnimationForTargetState(machine.rootState(), anim2); + + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 2); + QVERIFY(machine.defaultAnimationsForTargetState(machine.rootState()).contains(anim)); + QVERIFY(machine.defaultAnimationsForTargetState(machine.rootState()).contains(anim2)); + + machine.removeDefaultAnimationForTargetState(machine.rootState(), anim); + + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 1); + QVERIFY(machine.defaultAnimationsForTargetState(machine.rootState()).contains(anim2)); + + machine.removeDefaultAnimationForTargetState(machine.rootState(), anim2); + QCOMPARE(machine.defaultAnimationsForTargetState(machine.rootState()).size(), 0); +} + +void tst_QStateMachine::overrideDefaultAnimationWithSource() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimation(defaultAnimation); + machine.addDefaultAnimationForSourceState(s1, moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +void tst_QStateMachine::overrideDefaultAnimationWithTarget() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimation(defaultAnimation); + machine.addDefaultAnimationForTargetState(s2, moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped + +} + +void tst_QStateMachine::overrideDefaultSourceAnimationWithSpecific() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimationForSourceState(s1, defaultAnimation); + at->addAnimation(moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +void tst_QStateMachine::overrideDefaultTargetAnimationWithSpecific() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + QAbstractTransition *at = s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimationForTargetState(s2, defaultAnimation); + at->addAnimation(moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +void tst_QStateMachine::overrideDefaultTargetAnimationWithSource() +{ + QStateMachine machine; + + QObject *object = new QObject(); + object->setProperty("foo", 1.0); + + SlotCalledCounter counter; + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(object, "foo", 2.0); + + QState *s3 = new QState(machine.rootState()); + QObject::connect(s3, SIGNAL(entered()), QCoreApplication::instance(), SLOT(quit())); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + + QPropertyAnimation *defaultAnimation = new QPropertyAnimation(object, "foo"); + connect(defaultAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + QPropertyAnimation *moreSpecificAnimation = new QPropertyAnimation(object, "foo"); + s2->addTransition(moreSpecificAnimation, SIGNAL(finished()), s3); + connect(moreSpecificAnimation, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), &counter, SLOT(slot())); + + machine.addDefaultAnimationForTargetState(s2, defaultAnimation); + machine.addDefaultAnimationForSourceState(s1, moreSpecificAnimation); + + machine.start(); + QCoreApplication::processEvents(); + + machine.postEvent(new QEvent(QEvent::User)); + QCOREAPPLICATION_EXEC(5000); + + QVERIFY(machine.configuration().contains(s3)); + QCOMPARE(counter.counter, 2); // specific animation started and stopped +} + +*/ + +void tst_QStateMachine::parallelStateAssignmentsDone() +{ + QStateMachine machine; + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("foo", 123); + propertyHolder->setProperty("bar", 456); + propertyHolder->setProperty("zoot", 789); + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *parallelState = new QState(QState::ParallelStates, machine.rootState()); + parallelState->assignProperty(propertyHolder, "foo", 321); + + QState *s2 = new QState(parallelState); + s2->assignProperty(propertyHolder, "bar", 654); + + QState *s3 = new QState(parallelState); + s3->assignProperty(propertyHolder, "zoot", 987); + + s1->addTransition(new EventTransition(QEvent::User, parallelState)); + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("foo").toInt(), 123); + QCOMPARE(propertyHolder->property("bar").toInt(), 456); + QCOMPARE(propertyHolder->property("zoot").toInt(), 789); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(propertyHolder->property("foo").toInt(), 321); + QCOMPARE(propertyHolder->property("bar").toInt(), 654); + QCOMPARE(propertyHolder->property("zoot").toInt(), 987); +} + +void tst_QStateMachine::transitionsFromParallelStateWithNoChildren() +{ + QStateMachine machine; + + QState *parallelState = new QState(QState::ParallelStates, machine.rootState()); + machine.setInitialState(parallelState); + + QState *s1 = new QState(machine.rootState()); + parallelState->addTransition(new EventTransition(QEvent::User, s1)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(1, machine.configuration().size()); + QVERIFY(machine.configuration().contains(parallelState)); + + machine.postEvent(new QEvent(QEvent::User)); + + QCoreApplication::processEvents(); + + QCOMPARE(1, machine.configuration().size()); + QVERIFY(machine.configuration().contains(s1)); +} + +void tst_QStateMachine::parallelStateTransition() +{ + QStateMachine machine; + + QState *parallelState = new QState(QState::ParallelStates, machine.rootState()); + machine.setInitialState(parallelState); + + QState *s1 = new QState(parallelState); + QState *s2 = new QState(parallelState); + + QState *s1InitialChild = new QState(s1); + s1->setInitialState(s1InitialChild); + + QState *s2InitialChild = new QState(s2); + s2->setInitialState(s2InitialChild); + + QState *s1OtherChild = new QState(s1); + + s1->addTransition(new EventTransition(QEvent::User, s1OtherChild)); + + machine.start(); + QCoreApplication::processEvents(); + + QVERIFY(machine.configuration().contains(parallelState)); + QVERIFY(machine.configuration().contains(s1)); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s1InitialChild)); + QVERIFY(machine.configuration().contains(s2InitialChild)); + QCOMPARE(machine.configuration().size(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QVERIFY(machine.configuration().contains(parallelState)); + + QVERIFY(machine.configuration().contains(s1)); + + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s1OtherChild)); + QVERIFY(machine.configuration().contains(s2InitialChild)); + QCOMPARE(machine.configuration().size(), 5); + +} + +void tst_QStateMachine::nestedRestoreProperties() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("foo", 1); + propertyHolder->setProperty("bar", 2); + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(propertyHolder, "foo", 3); + + QState *s21 = new QState(s2); + s21->assignProperty(propertyHolder, "bar", 4); + s2->setInitialState(s21); + + QState *s22 = new QState(s2); + s22->assignProperty(propertyHolder, "bar", 5); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s21->addTransition(new EventTransition(QEvent::User, s22)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + QCOMPARE(propertyHolder->property("foo").toInt(), 1); + QCOMPARE(propertyHolder->property("bar").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s22)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 5); +} + +void tst_QStateMachine::nestedRestoreProperties2() +{ + QStateMachine machine; + machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties); + + QObject *propertyHolder = new QObject(&machine); + propertyHolder->setProperty("foo", 1); + propertyHolder->setProperty("bar", 2); + + QState *s1 = new QState(machine.rootState()); + machine.setInitialState(s1); + + QState *s2 = new QState(machine.rootState()); + s2->assignProperty(propertyHolder, "foo", 3); + + QState *s21 = new QState(s2); + s21->assignProperty(propertyHolder, "bar", 4); + s2->setInitialState(s21); + + QState *s22 = new QState(s2); + s22->assignProperty(propertyHolder, "foo", 6); + s22->assignProperty(propertyHolder, "bar", 5); + + s1->addTransition(new EventTransition(QEvent::User, s2)); + s21->addTransition(new EventTransition(QEvent::User, s22)); + s22->addTransition(new EventTransition(QEvent::User, s21)); + + machine.start(); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 1); + QVERIFY(machine.configuration().contains(s1)); + QCOMPARE(propertyHolder->property("foo").toInt(), 1); + QCOMPARE(propertyHolder->property("bar").toInt(), 2); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 4); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s22)); + QCOMPARE(propertyHolder->property("foo").toInt(), 6); + QCOMPARE(propertyHolder->property("bar").toInt(), 5); + + machine.postEvent(new QEvent(QEvent::User)); + QCoreApplication::processEvents(); + + QCOMPARE(machine.configuration().size(), 2); + QVERIFY(machine.configuration().contains(s2)); + QVERIFY(machine.configuration().contains(s21)); + QCOMPARE(propertyHolder->property("foo").toInt(), 3); + QCOMPARE(propertyHolder->property("bar").toInt(), 4); + +} + + +QTEST_MAIN(tst_QStateMachine) +#include "tst_qstatemachine.moc" diff --git a/tests/benchmarks/benchmarks.pro b/tests/benchmarks/benchmarks.pro index 8e2c243..4c39373 100644 --- a/tests/benchmarks/benchmarks.pro +++ b/tests/benchmarks/benchmarks.pro @@ -1,6 +1,7 @@ TEMPLATE = subdirs SUBDIRS = containers-associative \ containers-sequential \ + qanimation \ qbytearray \ qpainter \ qtestlib-simple events \ diff --git a/tests/benchmarks/qanimation/dummyanimation.cpp b/tests/benchmarks/qanimation/dummyanimation.cpp new file mode 100644 index 0000000..6fb1d0a --- /dev/null +++ b/tests/benchmarks/qanimation/dummyanimation.cpp @@ -0,0 +1,20 @@ +#include "dummyanimation.h" +#include "dummyobject.h" + + +DummyAnimation::DummyAnimation(DummyObject *d) : m_dummy(d) +{ +} + +void DummyAnimation::updateCurrentValue(const QVariant &value) +{ + if (state() == Stopped) + return; + if (m_dummy) + m_dummy->setRect(value.toRect()); +} + +void DummyAnimation::updateState(State state) +{ + Q_UNUSED(state); +} diff --git a/tests/benchmarks/qanimation/dummyanimation.h b/tests/benchmarks/qanimation/dummyanimation.h new file mode 100644 index 0000000..fe6592b --- /dev/null +++ b/tests/benchmarks/qanimation/dummyanimation.h @@ -0,0 +1,19 @@ +#include <QtGui> + +#ifndef _DUMMYANIMATION_H__ + +class DummyObject; + +class DummyAnimation : public QVariantAnimation +{ +public: + DummyAnimation(DummyObject *d); + + void updateCurrentValue(const QVariant &value); + void updateState(State state); + +private: + DummyObject *m_dummy; +}; + +#endif
\ No newline at end of file diff --git a/tests/benchmarks/qanimation/dummyobject.cpp b/tests/benchmarks/qanimation/dummyobject.cpp new file mode 100644 index 0000000..bd76388 --- /dev/null +++ b/tests/benchmarks/qanimation/dummyobject.cpp @@ -0,0 +1,25 @@ +#include "dummyobject.h" + +DummyObject::DummyObject() +{ +} + +QRect DummyObject::rect() const +{ + return m_rect; +} + +void DummyObject::setRect(const QRect &r) +{ + m_rect = r; +} + +float DummyObject::opacity() const +{ + return m_opacity; +} + +void DummyObject::setOpacity(float o) +{ + m_opacity = o; +} diff --git a/tests/benchmarks/qanimation/dummyobject.h b/tests/benchmarks/qanimation/dummyobject.h new file mode 100644 index 0000000..c989662 --- /dev/null +++ b/tests/benchmarks/qanimation/dummyobject.h @@ -0,0 +1,23 @@ +#include <QtGui> + +#ifndef _DUMMYOBJECT_H__ + +class DummyObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QRect rect READ rect WRITE setRect) + Q_PROPERTY(float opacity READ opacity WRITE setOpacity) +public: + DummyObject(); + QRect rect() const; + void setRect(const QRect &r); + float opacity() const; + void setOpacity(float); + +private: + QRect m_rect; + float m_opacity; +}; + + +#endif
\ No newline at end of file diff --git a/tests/benchmarks/qanimation/main.cpp b/tests/benchmarks/qanimation/main.cpp new file mode 100644 index 0000000..7bb770f --- /dev/null +++ b/tests/benchmarks/qanimation/main.cpp @@ -0,0 +1,150 @@ +#include <QtGui> +#include <qtest.h> + +#include "dummyobject.h" +#include "dummyanimation.h" +#include "rectanimation.h" + +#define ITERATION_COUNT 10e3 + +class tst_qanimation : public QObject +{ + Q_OBJECT +private slots: + void itemPropertyAnimation(); + void itemPropertyAnimation_data() { data();} + void dummyAnimation(); + void dummyAnimation_data() { data();} + void dummyPropertyAnimation(); + void dummyPropertyAnimation_data() { data();} + void rectAnimation(); + void rectAnimation_data() { data();} + + void floatAnimation_data() { data(); } + void floatAnimation(); + +private: + void data(); +}; + + +void tst_qanimation::data() +{ + QTest::addColumn<bool>("started"); + QTest::newRow("NotRunning") << false; + QTest::newRow("Running") << true; +} + +void tst_qanimation::itemPropertyAnimation() +{ + QFETCH(bool, started); + QGraphicsWidget item; + + //then the property animation + { + QPropertyAnimation anim(&item, "pos"); + anim.setDuration(ITERATION_COUNT); + anim.setStartValue(QPointF(0,0)); + anim.setEndValue(QPointF(ITERATION_COUNT,ITERATION_COUNT)); + if (started) + anim.start(); + QBENCHMARK { + for(int i = 0; i < ITERATION_COUNT; ++i) { + anim.setCurrentTime(i); + } + } + } + +} + +void tst_qanimation::dummyAnimation() +{ + QFETCH(bool, started); + DummyObject dummy; + + //first the dummy animation + { + DummyAnimation anim(&dummy); + anim.setDuration(ITERATION_COUNT); + anim.setStartValue(QRect(0, 0, 0, 0)); + anim.setEndValue(QRect(0, 0, ITERATION_COUNT,ITERATION_COUNT)); + if (started) + anim.start(); + QBENCHMARK { + for(int i = 0; i < anim.duration(); ++i) { + anim.setCurrentTime(i); + } + } + } +} + +void tst_qanimation::dummyPropertyAnimation() +{ + QFETCH(bool, started); + DummyObject dummy; + + //then the property animation + { + QPropertyAnimation anim(&dummy, "rect"); + anim.setDuration(ITERATION_COUNT); + anim.setStartValue(QRect(0, 0, 0, 0)); + anim.setEndValue(QRect(0, 0, ITERATION_COUNT,ITERATION_COUNT)); + if (started) + anim.start(); + QBENCHMARK { + for(int i = 0; i < ITERATION_COUNT; ++i) { + anim.setCurrentTime(i); + } + } + } +} + +void tst_qanimation::rectAnimation() +{ + //this is the simplest animation you can do + QFETCH(bool, started); + DummyObject dummy; + + //then the property animation + { + RectAnimation anim(&dummy); + anim.setDuration(ITERATION_COUNT); + anim.setStartValue(QRect(0, 0, 0, 0)); + anim.setEndValue(QRect(0, 0, ITERATION_COUNT,ITERATION_COUNT)); + if (started) + anim.start(); + QBENCHMARK { + for(int i = 0; i < ITERATION_COUNT; ++i) { + anim.setCurrentTime(i); + } + } + } +} + +void tst_qanimation::floatAnimation() +{ + //this is the simplest animation you can do + QFETCH(bool, started); + DummyObject dummy; + + //then the property animation + { + QPropertyAnimation anim(&dummy, "opacity"); + anim.setDuration(ITERATION_COUNT); + anim.setStartValue(0.f); + anim.setEndValue(1.f); + if (started) + anim.start(); + QBENCHMARK { + for(int i = 0; i < ITERATION_COUNT; ++i) { + anim.setCurrentTime(i); + } + } + } +} + + + +QTEST_MAIN(tst_qanimation) + +#include "main.moc" diff --git a/tests/benchmarks/qanimation/qanimation.pro b/tests/benchmarks/qanimation/qanimation.pro new file mode 100644 index 0000000..55cd75e --- /dev/null +++ b/tests/benchmarks/qanimation/qanimation.pro @@ -0,0 +1,18 @@ +load(qttest_p4) +TEMPLATE = app +TARGET = tst_qanimation +DEPENDPATH += . +INCLUDEPATH += . + +CONFIG += release +#CONFIG += debug + + +SOURCES += main.cpp \ + dummyobject.cpp \ + dummyanimation.cpp \ + rectanimation.cpp + +HEADERS += dummyobject.h \ + dummyanimation.h \ + rectanimation.h diff --git a/tests/benchmarks/qanimation/rectanimation.cpp b/tests/benchmarks/qanimation/rectanimation.cpp new file mode 100644 index 0000000..d60a943 --- /dev/null +++ b/tests/benchmarks/qanimation/rectanimation.cpp @@ -0,0 +1,58 @@ +#include "rectanimation.h" +#include "dummyobject.h" + +static inline int interpolateInteger(int from, int to, qreal progress) +{ + return from + (to - from) * progress; +} + + +RectAnimation::RectAnimation(DummyObject *obj) : m_object(obj), m_dura(250) +{ +} + +void RectAnimation::setEndValue(const QRect &rect) +{ + m_end = rect; +} + +void RectAnimation::setStartValue(const QRect &rect) +{ + m_start = rect; +} + +void RectAnimation::setDuration(int d) +{ + m_dura = d; +} + +int RectAnimation::duration() const +{ + return m_dura; +} + + +void RectAnimation::updateCurrentTime(int msecs) +{ + qreal progress = m_easing.valueForProgress( qreal(msecs) / qreal(m_dura) ); + QRect now; + now.setCoords(interpolateInteger(m_start.left(), m_end.left(), progress), + interpolateInteger(m_start.top(), m_end.top(), progress), + interpolateInteger(m_start.right(), m_end.right(), progress), + interpolateInteger(m_start.bottom(), m_end.bottom(), progress)); + + bool changed = (now != m_current); + if (changed) + m_current = now; + + if (state() == Stopped) + return; + + if (m_object) + m_object->setRect(m_current); +} + +void RectAnimation::updateState(QAbstractAnimation::State state) +{ + Q_UNUSED(state); +} diff --git a/tests/benchmarks/qanimation/rectanimation.h b/tests/benchmarks/qanimation/rectanimation.h new file mode 100644 index 0000000..99b82b4 --- /dev/null +++ b/tests/benchmarks/qanimation/rectanimation.h @@ -0,0 +1,30 @@ +#include <QtGui> + +#ifndef _RECTANIMATION_H__ + +class DummyObject; + +//this class is even simpler than the dummy +//and uses no QVariant at all +class RectAnimation : public QAbstractAnimation +{ +public: + RectAnimation(DummyObject *obj); + + void setEndValue(const QRect &rect); + void setStartValue(const QRect &rect); + + void setDuration(int d); + int duration() const; + + virtual void updateCurrentTime(int msecs); + virtual void updateState(QAbstractAnimation::State state); + +private: + DummyObject *m_object; + QEasingCurve m_easing; + QRect m_start, m_end, m_current; + int m_dura; +}; + +#endif diff --git a/tests/benchmarks/qvariant/qvariant.pro b/tests/benchmarks/qvariant/qvariant.pro index 68b4a97..63b5442 100644 --- a/tests/benchmarks/qvariant/qvariant.pro +++ b/tests/benchmarks/qvariant/qvariant.pro @@ -3,7 +3,6 @@ TEMPLATE = app TARGET = tst_qvariant DEPENDPATH += . INCLUDEPATH += . -QT -= gui CONFIG += release #CONFIG += debug diff --git a/tests/benchmarks/qvariant/tst_qvariant.cpp b/tests/benchmarks/qvariant/tst_qvariant.cpp index 4a7ad02..0eb1ae2 100644 --- a/tests/benchmarks/qvariant/tst_qvariant.cpp +++ b/tests/benchmarks/qvariant/tst_qvariant.cpp @@ -38,7 +38,9 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ + #include <QtCore> +#include <QtGui/QPixmap> #include <qtest.h> #define ITERATION_COUNT 1e5 @@ -47,64 +49,73 @@ class tst_qvariant : public QObject { Q_OBJECT private slots: + void testBound(); + void doubleVariantCreation(); void floatVariantCreation(); void rectVariantCreation(); void stringVariantCreation(); + void pixmapVariantCreation(); + void doubleVariantSetValue(); void floatVariantSetValue(); void rectVariantSetValue(); void stringVariantSetValue(); + void doubleVariantAssignment(); void floatVariantAssignment(); void rectVariantAssignment(); void stringVariantAssignment(); }; - -void tst_qvariant::doubleVariantCreation() +void tst_qvariant::testBound() { - double d = 0; + qreal d = qreal(.5); QBENCHMARK { for(int i = 0; i < ITERATION_COUNT; ++i) { - QVariant v(d); + d = qBound<qreal>(0, d, 1); } } } -void tst_qvariant::floatVariantCreation() +template <typename T> +static void variantCreation(T val) { - float f = 0; QBENCHMARK { for(int i = 0; i < ITERATION_COUNT; ++i) { - QVariant v(f); + QVariant v(val); } } } +void tst_qvariant::doubleVariantCreation() +{ + variantCreation<double>(0.0); +} + +void tst_qvariant::floatVariantCreation() +{ + variantCreation<float>(0.0f); +} + void tst_qvariant::rectVariantCreation() { - QRect r(1,2,3,4); - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - QVariant v(r); - } - } + variantCreation<QRect>(QRect(1, 2, 3, 4)); } void tst_qvariant::stringVariantCreation() { - QString s; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - QVariant v(s); - } - } + variantCreation<QString>(QString()); } -void tst_qvariant::doubleVariantSetValue() +void tst_qvariant::pixmapVariantCreation() +{ + variantCreation<QPixmap>(QPixmap()); +} + +template <typename T> +static void variantSetValue(T d) { - double d = 0; QVariant v; QBENCHMARK { for(int i = 0; i < ITERATION_COUNT; ++i) { @@ -113,42 +124,29 @@ void tst_qvariant::doubleVariantSetValue() } } +void tst_qvariant::doubleVariantSetValue() +{ + variantSetValue<double>(0.0); +} + void tst_qvariant::floatVariantSetValue() { - float f = 0; - QVariant v; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - qVariantSetValue(v, f); - } - } + variantSetValue<float>(0.0f); } void tst_qvariant::rectVariantSetValue() { - QRect r; - QVariant v; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - qVariantSetValue(v, r); - } - } + variantSetValue<QRect>(QRect()); } void tst_qvariant::stringVariantSetValue() { - QString s; - QVariant v; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - qVariantSetValue(v, s); - } - } + variantSetValue<QString>(QString()); } -void tst_qvariant::doubleVariantAssignment() +template <typename T> +static void variantAssignment(T d) { - double d = 0; QVariant v; QBENCHMARK { for(int i = 0; i < ITERATION_COUNT; ++i) { @@ -157,37 +155,24 @@ void tst_qvariant::doubleVariantAssignment() } } +void tst_qvariant::doubleVariantAssignment() +{ + variantAssignment<double>(0.0); +} + void tst_qvariant::floatVariantAssignment() { - float f = 0; - QVariant v; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - v = f; - } - } + variantAssignment<float>(0.0f); } void tst_qvariant::rectVariantAssignment() { - QRect r; - QVariant v; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - v = r; - } - } + variantAssignment<QRect>(QRect()); } void tst_qvariant::stringVariantAssignment() { - QString s; - QVariant v; - QBENCHMARK { - for(int i = 0; i < ITERATION_COUNT; ++i) { - v = s; - } - } + variantAssignment<QString>(QString()); } QTEST_MAIN(tst_qvariant) |