Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QTest>
#include <QtQml/private/qsequentialanimationgroupjob_p.h>
#include <QtQml/private/qparallelanimationgroupjob_p.h>
#include <QtQml/private/qpauseanimationjob_p.h>
#include <limits>
#include <memory>
Q_DECLARE_METATYPE(QAbstractAnimationJob::State)
Q_DECLARE_METATYPE(QAbstractAnimationJob*)
class tst_QSequentialAnimationGroupJob : public QObject
{
Q_OBJECT
public Q_SLOTS:
void initTestCase();
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 uncontrolledWithLoops();
void finishWithUncontrolledAnimation();
void addRemoveAnimation();
void currentAnimation();
void currentAnimationWithZeroDuration();
void insertAnimation();
void clear();
void pauseResume();
void deleteFromListener();
void noIntegerOverflow();
};
void tst_QSequentialAnimationGroupJob::initTestCase()
{
qRegisterMetaType<QAbstractAnimationJob::State>("QAbstractAnimationJob::State");
qRegisterMetaType<QAbstractAnimationJob*>("QAbstractAnimationJob*");
}
void tst_QSequentialAnimationGroupJob::construction()
{
QSequentialAnimationGroupJob animationgroup;
}
class TestAnimation : public QAbstractAnimationJob
{
public:
TestAnimation(int duration = 250) : m_duration(duration) {}
int duration() const override { return m_duration; }
void setDuration(int duration) { m_duration = duration; }
private:
int m_duration;
};
class TestValueAnimation : public TestAnimation
{
public:
TestValueAnimation(int duration = 250)
: TestAnimation(duration) {}
void updateCurrentTime(int msecs) override
{
if (msecs >= duration())
value = end;
else
value = start + (end - start) * (qreal(msecs) / duration());
}
qreal start = 0, end = 0;
qreal value = 0;
};
class UncontrolledAnimation : public QObject, public QAbstractAnimationJob
{
Q_OBJECT
public:
int duration() const override { return -1; /* not time driven */ }
protected:
void updateCurrentTime(int currentTime) override
{
if (currentTime >= 250)
stop();
}
};
class StateChangeListener: public QAnimationJobChangeListener
{
public:
void animationStateChanged(
QAbstractAnimationJob *job, QAbstractAnimationJob::State newState,
QAbstractAnimationJob::State) override
{
states << newState;
if (beEvil) {
delete job->group(); //manual delete intentional
groupDeleted = true;
}
}
void clear() { states.clear(); }
int count() const { return states.size(); }
QList<QAbstractAnimationJob::State> states;
bool beEvil = false;
bool groupDeleted = false;
};
class FinishedListener: public QAnimationJobChangeListener
{
public:
FinishedListener() {}
void animationFinished(QAbstractAnimationJob *) override { ++m_count; }
void clear() { m_count = 0; }
int count() { return m_count; }
private:
int m_count = 0;
};
void tst_QSequentialAnimationGroupJob::setCurrentTime()
{
// sequence operating on same object/property
QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
TestAnimation *a1_s_o1 = new TestAnimation;
TestAnimation *a2_s_o1 = new TestAnimation;
TestAnimation *a3_s_o1 = new TestAnimation;
a2_s_o1->setLoopCount(3);
sequence->appendAnimation(a1_s_o1);
sequence->appendAnimation(a2_s_o1);
sequence->appendAnimation(a3_s_o1);
// sequence operating on different object/properties
QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob();
TestAnimation *a1_s_o2 = new TestAnimation;
TestAnimation *a1_s_o3 = new TestAnimation;
sequence2->appendAnimation(a1_s_o2);
sequence2->appendAnimation(a1_s_o3);
QSequentialAnimationGroupJob group;
group.appendAnimation(sequence);
group.appendAnimation(sequence2);
// Current time = 1
group.setCurrentTime(1);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.currentLoopTime(), 1);
QCOMPARE(sequence->currentLoopTime(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 1);
QCOMPARE(a2_s_o1->currentLoopTime(), 0);
QCOMPARE(a3_s_o1->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 250
group.setCurrentTime(250);
QCOMPARE(group.currentLoopTime(), 250);
QCOMPARE(sequence->currentLoopTime(), 250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 0);
QCOMPARE(a3_s_o1->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 251
group.setCurrentTime(251);
QCOMPARE(group.currentLoopTime(), 251);
QCOMPARE(sequence->currentLoopTime(), 251);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 1);
QCOMPARE(a2_s_o1->currentLoop(), 0);
QCOMPARE(a3_s_o1->currentLoopTime(), 0);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 750
group.setCurrentTime(750);
QCOMPARE(group.currentLoopTime(), 750);
QCOMPARE(sequence->currentLoopTime(), 750);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 0);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 0);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 1000
group.setCurrentTime(1000);
QCOMPARE(group.currentLoopTime(), 1000);
QCOMPARE(sequence->currentLoopTime(), 1000);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 0);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 1010
group.setCurrentTime(1010);
QCOMPARE(group.currentLoopTime(), 1010);
QCOMPARE(sequence->currentLoopTime(), 1010);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 10);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 1250
group.setCurrentTime(1250);
QCOMPARE(group.currentLoopTime(), 1250);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 1500
group.setCurrentTime(1500);
QCOMPARE(group.currentLoopTime(), 1500);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 1750
group.setCurrentTime(1750);
QCOMPARE(group.currentLoopTime(), 1750);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 500);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 250);
// Current time = 2000
group.setCurrentTime(2000);
QCOMPARE(group.currentLoopTime(), 1750);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 500);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 250);
}
void tst_QSequentialAnimationGroupJob::setCurrentTimeWithUncontrolledAnimation()
{
// sequence operating on different object/properties
QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
TestAnimation *a1_s_o1 = new TestAnimation;
TestAnimation *a1_s_o2 = new TestAnimation;
sequence->appendAnimation(a1_s_o1);
sequence->appendAnimation(a1_s_o2);
UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation;
QCOMPARE(notTimeDriven->totalDuration(), -1);
TestAnimation *loopsForever = new TestAnimation;
loopsForever->setLoopCount(-1);
QCOMPARE(loopsForever->totalDuration(), -1);
QSequentialAnimationGroupJob group;
group.appendAnimation(sequence);
group.appendAnimation(notTimeDriven);
group.appendAnimation(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(), QAnimationGroupJob::Paused);
QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.currentLoopTime(), 1);
QCOMPARE(sequence->currentLoopTime(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 1);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(notTimeDriven->currentLoopTime(), 0);
QCOMPARE(loopsForever->currentLoopTime(), 0);
// Current time = 250
group.setCurrentTime(250);
QCOMPARE(group.currentLoopTime(), 250);
QCOMPARE(sequence->currentLoopTime(), 250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(notTimeDriven->currentLoopTime(), 0);
QCOMPARE(loopsForever->currentLoopTime(), 0);
// Current time = 500
group.setCurrentTime(500);
QCOMPARE(group.currentLoopTime(), 500);
QCOMPARE(sequence->currentLoopTime(), 500);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(notTimeDriven->currentLoopTime(), 0);
QCOMPARE(loopsForever->currentLoopTime(), 0);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob *>(notTimeDriven));
// Current time = 505
group.setCurrentTime(505);
QCOMPARE(group.currentLoopTime(), 505);
QCOMPARE(sequence->currentLoopTime(), 500);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(notTimeDriven->currentLoopTime(), 5);
QCOMPARE(loopsForever->currentLoopTime(), 0);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob *>(notTimeDriven));
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Paused);
QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
// Current time = 750 (end of notTimeDriven animation)
group.setCurrentTime(750);
QCOMPARE(group.currentLoopTime(), 750);
QCOMPARE(sequence->currentLoopTime(), 500);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(notTimeDriven->currentLoopTime(), 250);
QCOMPARE(loopsForever->currentLoopTime(), 0);
QCOMPARE(group.currentAnimation(), loopsForever);
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
QCOMPARE(loopsForever->state(), QAnimationGroupJob::Paused);
// Current time = 800 (as notTimeDriven was finished at 750, loopsforever should still run)
group.setCurrentTime(800);
QCOMPARE(group.currentLoopTime(), 800);
QCOMPARE(group.currentAnimation(), loopsForever);
QCOMPARE(sequence->currentLoopTime(), 500);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(notTimeDriven->currentLoopTime(), 250);
QCOMPARE(loopsForever->currentLoopTime(), 50);
loopsForever->stop(); // this should stop the group
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::seekingForwards()
{
// sequence operating on same object/property
QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob;
TestAnimation *a1_s_o1 = new TestAnimation;
TestAnimation *a2_s_o1 = new TestAnimation;
TestAnimation *a3_s_o1 = new TestAnimation;
a2_s_o1->setLoopCount(3);
sequence->appendAnimation(a1_s_o1);
sequence->appendAnimation(a2_s_o1);
sequence->appendAnimation(a3_s_o1);
// sequence operating on different object/properties
QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob;
TestAnimation *a1_s_o2 = new TestAnimation;
TestAnimation *a1_s_o3 = new TestAnimation;
sequence2->appendAnimation(a1_s_o2);
sequence2->appendAnimation(a1_s_o3);
QSequentialAnimationGroupJob group;
group.appendAnimation(sequence);
group.appendAnimation(sequence2);
// Current time = 1
group.setCurrentTime(1);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.currentLoopTime(), 1);
QCOMPARE(sequence->currentLoopTime(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 1);
QCOMPARE(a2_s_o1->currentLoopTime(), 0);
QCOMPARE(a3_s_o1->currentLoopTime(), 0);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// Current time = 1500
group.setCurrentTime(1500);
QCOMPARE(group.currentLoopTime(), 1500);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 250);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
// this will restart the group
group.start();
group.pause();
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Paused);
QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
// Current time = 1750
group.setCurrentTime(1750);
QCOMPARE(group.currentLoopTime(), 1750);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 500);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 250);
}
void tst_QSequentialAnimationGroupJob::seekingBackwards()
{
// sequence operating on same object/property
QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
TestAnimation *a1_s_o1 = new TestAnimation;
TestAnimation *a2_s_o1 = new TestAnimation;
TestAnimation *a3_s_o1 = new TestAnimation;
a2_s_o1->setLoopCount(3);
sequence->appendAnimation(a1_s_o1);
sequence->appendAnimation(a2_s_o1);
sequence->appendAnimation(a3_s_o1);
// sequence operating on different object/properties
QAnimationGroupJob *sequence2 = new QSequentialAnimationGroupJob();
TestAnimation *a1_s_o2 = new TestAnimation;
TestAnimation *a1_s_o3 = new TestAnimation;
sequence2->appendAnimation(a1_s_o2);
sequence2->appendAnimation(a1_s_o3);
QSequentialAnimationGroupJob group;
group.appendAnimation(sequence);
group.appendAnimation(sequence2);
group.start();
// Current time = 1600
group.setCurrentTime(1600);
QCOMPARE(group.currentLoopTime(), 1600);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 350);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 100);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence2->state(), QAnimationGroupJob::Running);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Running);
// Seeking backwards, current time = 1
group.setCurrentTime(1);
QCOMPARE(group.currentLoopTime(), 1);
QCOMPARE(sequence->currentLoopTime(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 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->currentLoopTime(), 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->currentLoopTime(), 0);
QCOMPARE(sequence2->currentLoopTime(), 0);
QCOMPARE(a1_s_o2->currentLoopTime(), 0);
QCOMPARE(a1_s_o3->currentLoopTime(), 0);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(sequence->state(), QAnimationGroupJob::Running);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Running);
QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
// Current time = 2000
group.setCurrentTime(2000);
QCOMPARE(group.currentLoopTime(), 1750);
QCOMPARE(sequence->currentLoopTime(), 1250);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 2);
QCOMPARE(a3_s_o1->currentLoopTime(), 250);
QCOMPARE(sequence2->currentLoopTime(), 500);
QCOMPARE(a1_s_o2->currentLoopTime(), 250);
QCOMPARE(a1_s_o3->currentLoopTime(), 250);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(sequence2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1_s_o3->state(), QAnimationGroupJob::Stopped);
}
typedef QList<QAbstractAnimationJob::State> StateList;
static bool compareStates(const StateChangeListener& spy, const StateList &expectedStates)
{
bool equals = true;
for (int i = 0; i < qMax(expectedStates.size(), spy.count()); ++i) {
if (i >= spy.count() || i >= expectedStates.size()) {
equals = false;
break;
}
QAbstractAnimationJob::State st = expectedStates.at(i);
QAbstractAnimationJob::State actual = spy.states.at(i);
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.size(), spy.count()); ++i) {
if (i < expectedStates.size()) {
int exp = int(expectedStates.at(i));
if (!e.isEmpty())
e += QLatin1String(", ");
e += QLatin1String(stateStrings[exp]);
}
if (i < spy.count()) {
QAbstractAnimationJob::State actual = spy.states.at(i);
if (!a.isEmpty())
a += QLatin1String(", ");
if (int(actual) >= 0 && int(actual) <= 2) {
a += QLatin1String(stateStrings[int(actual)]);
} else {
a += QLatin1String("NaN");
}
}
}
qDebug().noquote() << "\nexpected (count == " << expectedStates.size() << "): " << e
<< "\nactual (count == " << spy.count() << "): " << a << "\n";
}
return equals;
}
void tst_QSequentialAnimationGroupJob::pauseAndResume()
{
// sequence operating on same object/property
QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
TestAnimation *a1_s_o1 = new TestAnimation;
TestAnimation *a2_s_o1 = new TestAnimation;
TestAnimation *a3_s_o1 = new TestAnimation;
a2_s_o1->setLoopCount(2);
sequence->appendAnimation(a1_s_o1);
sequence->appendAnimation(a2_s_o1);
sequence->appendAnimation(a3_s_o1);
sequence->setLoopCount(2);
StateChangeListener a1StateChangedSpy;
a1_s_o1->addAnimationChangeListener(&a1StateChangedSpy, QAbstractAnimationJob::StateChange);
StateChangeListener seqStateChangedSpy;
sequence->addAnimationChangeListener(&seqStateChangedSpy, QAbstractAnimationJob::StateChange);
QSequentialAnimationGroupJob group;
group.appendAnimation(sequence);
group.start();
group.pause();
// Current time = 1751
group.setCurrentTime(1751);
QCOMPARE(group.currentLoopTime(), 1751);
QCOMPARE(sequence->currentLoopTime(), 751);
QCOMPARE(sequence->currentLoop(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 1);
QCOMPARE(a3_s_o1->currentLoop(), 0);
QCOMPARE(a3_s_o1->currentLoopTime(), 1);
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1StateChangedSpy.count(), 5); // Running,Paused,Stopped,Running,Stopped
QCOMPARE(seqStateChangedSpy.count(), 2); // Running,Paused
QVERIFY(compareStates(a1StateChangedSpy, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused
<< QAbstractAnimationJob::Stopped
<< QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Stopped)));
//### is this the same test as compareStates test above?
QCOMPARE(a1StateChangedSpy.states.at(0), QAnimationGroupJob::Running);
QCOMPARE(a1StateChangedSpy.states.at(1), QAnimationGroupJob::Paused);
QCOMPARE(a1StateChangedSpy.states.at(2), QAnimationGroupJob::Stopped);
QCOMPARE(a1StateChangedSpy.states.at(3), QAnimationGroupJob::Running);
QCOMPARE(a1StateChangedSpy.states.at(4), QAnimationGroupJob::Stopped);
QCOMPARE(seqStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
QCOMPARE(seqStateChangedSpy.states.at(1), QAnimationGroupJob::Paused);
group.resume();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(sequence->state(), QAnimationGroupJob::Running);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Running);
QVERIFY(group.currentLoopTime() >= 1751);
QVERIFY(sequence->currentLoopTime() >= 751);
QCOMPARE(sequence->currentLoop(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 1);
QCOMPARE(a3_s_o1->currentLoop(), 0);
QVERIFY(a3_s_o1->currentLoopTime() >= 1);
QCOMPARE(seqStateChangedSpy.count(), 3); // Running,Paused,Running
QCOMPARE(seqStateChangedSpy.states.at(2), QAnimationGroupJob::Running);
group.pause();
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Paused);
QVERIFY(group.currentLoopTime() >= 1751);
QVERIFY(sequence->currentLoopTime() >= 751);
QCOMPARE(sequence->currentLoop(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoop(), 1);
QCOMPARE(a3_s_o1->currentLoop(), 0);
QVERIFY(a3_s_o1->currentLoopTime() >= 1);
QCOMPARE(seqStateChangedSpy.count(), 4); // Running,Paused,Running,Paused
QCOMPARE(seqStateChangedSpy.states.at(3), QAnimationGroupJob::Paused);
group.stop();
QCOMPARE(seqStateChangedSpy.count(), 5); // Running,Paused,Running,Paused,Stopped
QCOMPARE(seqStateChangedSpy.states.at(4), QAnimationGroupJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::restart()
{
// originally was sequence operating on same object/property
QAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
//### no equivilant signal
//QSignalSpy seqCurrentAnimChangedSpy(sequence, SIGNAL(currentAnimationChanged(QAbstractAnimationJob*)));
StateChangeListener seqStateChangedSpy;
sequence->addAnimationChangeListener(&seqStateChangedSpy, QAbstractAnimationJob::StateChange);
TestAnimation *anims[3];
QScopedPointer<StateChangeListener> animsStateChanged[3];
for (int i = 0; i < 3; i++) {
anims[i] = new TestAnimation(100);
animsStateChanged[i].reset(new StateChangeListener);
anims[i]->addAnimationChangeListener(animsStateChanged[i].data(),
QAbstractAnimationJob::StateChange);
}
anims[1]->setLoopCount(2);
sequence->appendAnimation(anims[0]);
sequence->appendAnimation(anims[1]);
sequence->appendAnimation(anims[2]);
sequence->setLoopCount(2);
QSequentialAnimationGroupJob group;
group.appendAnimation(sequence);
group.start();
QTest::qWait(500);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QTest::qWait(300);
QTRY_COMPARE(group.state(), QAnimationGroupJob::Stopped);
for (int i = 0; i < 3; i++) {
QCOMPARE(animsStateChanged[i]->count(), 4);
QCOMPARE(animsStateChanged[i]->states.at(0), QAnimationGroupJob::Running);
QCOMPARE(animsStateChanged[i]->states.at(1), QAnimationGroupJob::Stopped);
QCOMPARE(animsStateChanged[i]->states.at(2), QAnimationGroupJob::Running);
QCOMPARE(animsStateChanged[i]->states.at(3), QAnimationGroupJob::Stopped);
}
QCOMPARE(seqStateChangedSpy.count(), 2);
QCOMPARE(seqStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
QCOMPARE(seqStateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
//QCOMPARE(seqCurrentAnimChangedSpy.count(), 6);
//for(int i=0; i<seqCurrentAnimChangedSpy.count(); i++)
// QCOMPARE(static_cast<QAbstractAnimationJob*>(anims[i%3]), qVariantValue<QAbstractAnimationJob*>(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_QSequentialAnimationGroupJob::looping()
{
// originally was sequence operating on same object/property
QSequentialAnimationGroupJob *sequence = new QSequentialAnimationGroupJob();
QAbstractAnimationJob *a1_s_o1 = new TestAnimation;
QAbstractAnimationJob *a2_s_o1 = new TestAnimation;
QAbstractAnimationJob *a3_s_o1 = new TestAnimation;
StateChangeListener a1Spy;
a1_s_o1->addAnimationChangeListener(&a1Spy, QAbstractAnimationJob::StateChange);
StateChangeListener a2Spy;
a2_s_o1->addAnimationChangeListener(&a2Spy, QAbstractAnimationJob::StateChange);
StateChangeListener a3Spy;
a3_s_o1->addAnimationChangeListener(&a3Spy, QAbstractAnimationJob::StateChange);
StateChangeListener seqSpy;
sequence->addAnimationChangeListener(&seqSpy, QAbstractAnimationJob::StateChange);
a2_s_o1->setLoopCount(2);
sequence->appendAnimation(a1_s_o1);
sequence->appendAnimation(a2_s_o1);
sequence->appendAnimation(a3_s_o1);
sequence->setLoopCount(2);
QSequentialAnimationGroupJob group;
StateChangeListener groupSpy;
group.addAnimationChangeListener(&groupSpy, QAbstractAnimationJob::StateChange);
group.appendAnimation(sequence);
group.setLoopCount(2);
group.start();
group.pause();
// Current time = 1750
group.setCurrentTime(1750);
QCOMPARE(group.currentLoopTime(), 1750);
QCOMPARE(sequence->currentLoopTime(), 750);
QCOMPARE(sequence->currentLoop(), 1);
QCOMPARE(a1_s_o1->currentLoopTime(), 250);
QCOMPARE(a2_s_o1->currentLoopTime(), 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->currentLoopTime(), 0);
QCOMPARE(sequence->currentAnimation(), a3_s_o1);
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1Spy.count(), 5); // Running,Paused,Stopped,Running,Stopped
QVERIFY(compareStates(a1Spy, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused
<< QAbstractAnimationJob::Stopped
<< QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Stopped)));
QCOMPARE(a2Spy.count(), 4); // Running,Stopped,Running,Stopped
QVERIFY(compareStates(a3Spy, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Stopped
<< QAbstractAnimationJob::Running
<< QAbstractAnimationJob::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.currentLoopTime(), 1);
QCOMPARE(group.currentLoop(), 1);
QCOMPARE(sequence->currentLoopTime(), 1);
QCOMPARE(sequence->currentLoop(), 0);
QCOMPARE(a1_s_o1->currentLoopTime(), 1);
QCOMPARE(a2_s_o1->currentLoopTime(), 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->currentLoopTime(), 250);
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(sequence->state(), QAnimationGroupJob::Paused);
QCOMPARE(a1_s_o1->state(), QAnimationGroupJob::Paused);
QCOMPARE(a2_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3_s_o1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a1Spy.count(), 7); // Running,Paused,Stopped,Running,Stopped,Running,Stopped
QCOMPARE(a2Spy.count(), 4); // Running, Stopped, Running, Stopped
QVERIFY(compareStates(a3Spy, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Stopped
<< QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused
<< QAbstractAnimationJob::Stopped)));
QVERIFY(compareStates(seqSpy, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused
<< QAbstractAnimationJob::Stopped
<< QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused)));
QCOMPARE(groupSpy.count(), 2);
//cleanup
a1_s_o1->removeAnimationChangeListener(&a1Spy, QAbstractAnimationJob::StateChange);
a2_s_o1->removeAnimationChangeListener(&a2Spy, QAbstractAnimationJob::StateChange);
a3_s_o1->removeAnimationChangeListener(&a3Spy, QAbstractAnimationJob::StateChange);
sequence->removeAnimationChangeListener(&seqSpy, QAbstractAnimationJob::StateChange);
group.removeAnimationChangeListener(&groupSpy, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::startDelay()
{
QSequentialAnimationGroupJob group;
group.appendAnimation(new QPauseAnimationJob(250));
group.appendAnimation(new QPauseAnimationJob(125));
QCOMPARE(group.totalDuration(), 375);
group.start();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QTest::qWait(500);
QTRY_COMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.currentLoopTime(), 375);
}
void tst_QSequentialAnimationGroupJob::clearGroup()
{
QSequentialAnimationGroupJob group;
static const int animationCount = 20;
for (int i = 0; i < animationCount/2; ++i) {
QSequentialAnimationGroupJob *subGroup = new QSequentialAnimationGroupJob;
group.appendAnimation(subGroup);
group.appendAnimation(new QPauseAnimationJob(100));
subGroup->appendAnimation(new QPauseAnimationJob(10));
}
QCOMPARE(group.children()->count(), animationCount);
group.clear();
QVERIFY(group.children()->isEmpty());
QCOMPARE(group.currentLoopTime(), 0);
}
void tst_QSequentialAnimationGroupJob::groupWithZeroDurationAnimations()
{
QSequentialAnimationGroupJob group;
TestValueAnimation *a1 = new TestValueAnimation(0);
a1->start = 42;
a1->end = 43;
group.appendAnimation(a1);
//this should just run fine and change nothing
group.setCurrentTime(0);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(a1));
TestValueAnimation *a2 = new TestValueAnimation(500);
a2->start = 13;
a2->end = 31;
group.appendAnimation(a2);
TestValueAnimation *a3 = new TestValueAnimation(0);
a3->start = 43;
a3->end = 44;
group.appendAnimation(a3);
TestValueAnimation *a4 = new TestValueAnimation(250);
a4->start = 13;
a4->end = 75;
group.appendAnimation(a4);
TestValueAnimation *a5 = new TestValueAnimation(0);
a5->start = 42;
a5->end = 12;
group.appendAnimation(a5);
QCOMPARE((int)a1->value, 43); //### is this actually the behavior we want?
QCOMPARE((int)a2->value, 0);
QCOMPARE((int)a3->value, 0);
QCOMPARE((int)a4->value, 0);
QCOMPARE((int)a5->value, 0);
group.start();
QCOMPARE((int)a1->value, 43); //### is this actually the behavior we want?
QCOMPARE((int)a2->value, 13);
QCOMPARE((int)a3->value, 0);
QCOMPARE((int)a4->value, 0);
QCOMPARE((int)a5->value, 0);
QTest::qWait(100);
QCOMPARE((int)a1->value, 43);
QVERIFY(a2->value > 13 && a2->value < 31);
QCOMPARE((int)a3->value, 0);
QCOMPARE((int)a4->value, 0);
QCOMPARE((int)a5->value, 0);
QTest::qWait(500);
QTRY_COMPARE((int)a3->value, 44);
QCOMPARE((int)a1->value, 43);
QCOMPARE((int)a2->value, 31);
//QCOMPARE((int)a4->value, 36);
QCOMPARE((int)a5->value, 0);
QCOMPARE(a1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a4->state(), QAnimationGroupJob::Running);
QCOMPARE(a5->state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QTest::qWait(500);
QTRY_COMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE((int)a1->value, 43);
QCOMPARE((int)a2->value, 31);
QCOMPARE((int)a3->value, 44);
QCOMPARE((int)a4->value, 75);
QCOMPARE((int)a5->value, 12);
QCOMPARE(a1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a2->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a3->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a4->state(), QAnimationGroupJob::Stopped);
QCOMPARE(a5->state(), QAnimationGroupJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::propagateGroupUpdateToChildren()
{
// this test verifies if group state changes are updating its children correctly
QSequentialAnimationGroupJob group;
TestAnimation anim1(100);
TestAnimation anim2(200);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
group.appendAnimation(&anim1);
group.appendAnimation(&anim2);
group.start();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
group.pause();
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(anim1.state(), QAnimationGroupJob::Paused);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
group.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::updateChildrenWithRunningGroup()
{
// assert that its possible to modify a child's state directly while their group is running
QSequentialAnimationGroupJob group;
TestAnimation anim(200);
StateChangeListener groupStateChangedSpy;
group.addAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
StateChangeListener childStateChangedSpy;
anim.addAnimationChangeListener(&childStateChangedSpy, QAbstractAnimationJob::StateChange);
QCOMPARE(groupStateChangedSpy.count(), 0);
QCOMPARE(childStateChangedSpy.count(), 0);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
group.appendAnimation(&anim);
group.start();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim.state(), QAnimationGroupJob::Running);
QCOMPARE(groupStateChangedSpy.count(), 1);
QCOMPARE(childStateChangedSpy.count(), 1);
QCOMPARE(groupStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
QCOMPARE(childStateChangedSpy.states.at(0), QAnimationGroupJob::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(), QAnimationGroupJob::Running);
QCOMPARE(anim.state(), QAnimationGroupJob::Paused);
// in the animation stops directly, the group will still be running
anim.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
//cleanup
group.removeAnimationChangeListener(&groupStateChangedSpy, QAbstractAnimationJob::StateChange);
anim.removeAnimationChangeListener(&childStateChangedSpy, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::deleteChildrenWithRunningGroup()
{
// test if children can be activated when their group is stopped
QSequentialAnimationGroupJob group;
std::unique_ptr<TestAnimation> anim1 = std::make_unique<TestAnimation>(200);
group.appendAnimation(anim1.get());
QCOMPARE(group.duration(), anim1->duration());
group.start();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
QTest::qWait(100);
QTRY_VERIFY(group.currentLoopTime() > 0);
anim1.reset();
QVERIFY(group.children()->isEmpty());
QCOMPARE(group.duration(), 0);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.currentLoopTime(), 0); //that's the invariant
}
void tst_QSequentialAnimationGroupJob::startChildrenWithStoppedGroup()
{
// test if children can be activated when their group is stopped
QSequentialAnimationGroupJob group;
TestAnimation anim1(200);
TestAnimation anim2(200);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
group.appendAnimation(&anim1);
group.appendAnimation(&anim2);
group.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
anim1.start();
anim2.start();
anim2.pause();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
}
void tst_QSequentialAnimationGroupJob::stopGroupWithRunningChild()
{
// children that started independently will not be affected by a group stop
QSequentialAnimationGroupJob group;
TestAnimation anim1(200);
TestAnimation anim2(200);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
group.appendAnimation(&anim1);
group.appendAnimation(&anim2);
anim1.start();
anim2.start();
anim2.pause();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
group.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
anim1.stop();
anim2.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::startGroupWithRunningChild()
{
// as the group has precedence over its children, starting a group will restart all the children
QSequentialAnimationGroupJob group;
TestAnimation *anim1 = new TestAnimation(200);
TestAnimation *anim2 = new TestAnimation(200);
StateChangeListener stateChangedSpy1;
anim1->addAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
StateChangeListener stateChangedSpy2;
anim2->addAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
QCOMPARE(stateChangedSpy1.count(), 0);
QCOMPARE(stateChangedSpy2.count(), 0);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2->state(), QAnimationGroupJob::Stopped);
group.appendAnimation(anim1);
group.appendAnimation(anim2);
anim1->start();
anim2->start();
anim2->pause();
QVERIFY(compareStates(stateChangedSpy1, (StateList() << QAbstractAnimationJob::Running)));
QVERIFY(compareStates(stateChangedSpy2, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused)));
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
QCOMPARE(anim2->state(), QAnimationGroupJob::Paused);
group.start();
QVERIFY(compareStates(stateChangedSpy1, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Stopped
<< QAbstractAnimationJob::Running)));
QVERIFY(compareStates(stateChangedSpy2, (StateList() << QAbstractAnimationJob::Running
<< QAbstractAnimationJob::Paused)));
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
QCOMPARE(anim2->state(), QAnimationGroupJob::Paused);
QTest::qWait(300);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2->state(), QAnimationGroupJob::Running);
QCOMPARE(stateChangedSpy2.count(), 4);
QCOMPARE(stateChangedSpy2.states.at(2), QAnimationGroupJob::Stopped);
QCOMPARE(stateChangedSpy2.states.at(3), QAnimationGroupJob::Running);
group.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2->state(), QAnimationGroupJob::Stopped);
anim1->removeAnimationChangeListener(&stateChangedSpy1, QAbstractAnimationJob::StateChange);
anim2->removeAnimationChangeListener(&stateChangedSpy2, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::zeroDurationAnimation()
{
QSequentialAnimationGroupJob group;
TestAnimation *anim1 = new TestAnimation(0);
TestAnimation *anim2 = new TestAnimation(100);
TestValueAnimation *anim3 = new TestValueAnimation(0);
anim3->end = 100;
StateChangeListener stateChangedSpy;
anim1->addAnimationChangeListener(&stateChangedSpy, QAbstractAnimationJob::StateChange);
group.appendAnimation(anim1);
group.appendAnimation(anim2);
group.appendAnimation(anim3);
group.setLoopCount(2);
group.start();
QCOMPARE(stateChangedSpy.count(), 2);
QCOMPARE(stateChangedSpy.states.at(0), QAnimationGroupJob::Running);
QCOMPARE(stateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2->state(), QAnimationGroupJob::Running);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
//now let's try to seek to the next loop
group.setCurrentTime(group.duration() + 1);
QCOMPARE(anim1->state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim2->state(), QAnimationGroupJob::Running);
QCOMPARE(anim3->state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
//TODO: test that anim3 was run
QCOMPARE(anim3->value, qreal(100)); //anim3 should have been run
anim1->removeAnimationChangeListener(&stateChangedSpy, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::stopUncontrolledAnimations()
{
QSequentialAnimationGroupJob group;
UncontrolledAnimation notTimeDriven;
QCOMPARE(notTimeDriven.totalDuration(), -1);
TestAnimation loopsForever(100);
loopsForever.setLoopCount(-1);
group.appendAnimation(¬TimeDriven);
group.appendAnimation(&loopsForever);
group.start();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
QCOMPARE(loopsForever.state(), QAnimationGroupJob::Stopped);
notTimeDriven.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
QCOMPARE(loopsForever.state(), QAnimationGroupJob::Running);
loopsForever.stop();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
QCOMPARE(loopsForever.state(), QAnimationGroupJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::finishWithUncontrolledAnimation()
{
const int targetDuration = 300;
//1st case:
//first we test a group with one uncontrolled animation
QSequentialAnimationGroupJob group;
UncontrolledAnimation notTimeDriven;
group.appendAnimation(¬TimeDriven);
FinishedListener spy;
group.addAnimationChangeListener(&spy, QAbstractAnimationJob::Completion);
group.start();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
QCOMPARE(group.currentLoopTime(), 0);
QCOMPARE(notTimeDriven.currentLoopTime(), 0);
QTest::qWait(targetDuration); //wait for the end of notTimeDriven
QTRY_COMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
const int actualDuration = notTimeDriven.currentLoopTime();
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.currentLoopTime(), actualDuration);
QCOMPARE(spy.count(), 1);
//2nd case:
// lets make sure the seeking will work again
spy.clear();
TestAnimation anim;
group.appendAnimation(&anim);
StateChangeListener animStateChangedSpy;
anim.addAnimationChangeListener(&animStateChangedSpy, QAbstractAnimationJob::StateChange);
group.setCurrentTime(targetDuration);
QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
if (actualDuration > targetDuration) {
QCOMPARE(notTimeDriven.currentLoopTime(), targetDuration);
QCOMPARE(group.currentAnimation(), ¬TimeDriven);
} else {
QCOMPARE(notTimeDriven.currentLoopTime(), 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(), QAnimationGroupJob::Running);
QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
QCOMPARE(group.currentLoopTime(), 0);
QCOMPARE(notTimeDriven.currentLoopTime(), 0);
QCOMPARE(animStateChangedSpy.count(), 0);
QTest::qWait(targetDuration); //wait for the end of notTimeDriven
QTRY_COMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim.state(), QAnimationGroupJob::Running);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
QCOMPARE(animStateChangedSpy.count(), 1);
QTest::qWait(targetDuration); //wait for the end of anim
QTRY_COMPARE(anim.state(), QAnimationGroupJob::Stopped);
QCOMPARE(anim.currentLoopTime(), anim.duration());
//we should simply be at the end
QCOMPARE(spy.count(), 1);
QCOMPARE(animStateChangedSpy.count(), 2);
QCOMPARE(group.currentLoopTime(), notTimeDriven.currentLoopTime() + anim.currentLoopTime());
//cleanup
group.removeAnimationChangeListener(&spy, QAbstractAnimationJob::Completion);
anim.removeAnimationChangeListener(&animStateChangedSpy, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::addRemoveAnimation()
{
//this test is specific to the sequential animation group
QSequentialAnimationGroupJob group;
QCOMPARE(group.duration(), 0);
QCOMPARE(group.currentLoopTime(), 0);
QAbstractAnimationJob *anim1 = new TestAnimation;
group.appendAnimation(anim1);
QCOMPARE(group.duration(), 250);
QCOMPARE(group.currentLoopTime(), 0);
QCOMPARE(group.currentAnimation(), anim1);
//let's append an animation
QAbstractAnimationJob *anim2 = new TestAnimation;
group.appendAnimation(anim2);
QCOMPARE(group.duration(), 500);
QCOMPARE(group.currentLoopTime(), 0);
QCOMPARE(group.currentAnimation(), anim1);
//let's prepend an animation
QAbstractAnimationJob *anim0 = new TestAnimation;
group.prependAnimation(anim0);
QCOMPARE(group.duration(), 750);
QCOMPARE(group.currentLoopTime(), 0);
QCOMPARE(group.currentAnimation(), anim0); //anim0 has become the new currentAnimation
group.setCurrentTime(300); //anim0 | anim1 | anim2
QCOMPARE(group.currentLoopTime(), 300);
QCOMPARE(group.currentAnimation(), anim1);
QCOMPARE(anim1->currentLoopTime(), 50);
std::unique_ptr<QAbstractAnimationJob> anim0Guard(anim0);
group.removeAnimation(anim0); //anim1 | anim2
QCOMPARE(group.currentLoopTime(), 50);
QCOMPARE(group.currentAnimation(), anim1);
QCOMPARE(anim1->currentLoopTime(), 50);
group.setCurrentTime(0);
group.prependAnimation(anim0Guard.release()); //anim0 | anim1 | anim2
group.setCurrentTime(300);
QCOMPARE(group.currentLoopTime(), 300);
QCOMPARE(group.currentAnimation(), anim1);
QCOMPARE(anim1->currentLoopTime(), 50);
std::unique_ptr<QAbstractAnimationJob> anim1Guard(anim1);
group.removeAnimation(anim1); //anim0 | anim2
QCOMPARE(group.currentLoopTime(), 250);
QCOMPARE(group.currentAnimation(), anim2);
QCOMPARE(anim0->currentLoopTime(), 250);
}
void tst_QSequentialAnimationGroupJob::currentAnimation()
{
QSequentialAnimationGroupJob group;
QVERIFY(!group.currentAnimation());
TestAnimation anim(0);
group.appendAnimation(&anim);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
}
void tst_QSequentialAnimationGroupJob::currentAnimationWithZeroDuration()
{
QSequentialAnimationGroupJob group;
QVERIFY(!group.currentAnimation());
TestAnimation zero1(0);
TestAnimation zero2(0);
TestAnimation anim;
TestAnimation zero3(0);
TestAnimation zero4(0);
group.appendAnimation(&zero1);
group.appendAnimation(&zero2);
group.appendAnimation(&anim);
group.appendAnimation(&zero3);
group.appendAnimation(&zero4);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&zero1));
group.setCurrentTime(0);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
group.setCurrentTime(group.duration());
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&zero4));
group.setDirection(QAbstractAnimationJob::Backward);
group.setCurrentTime(0);
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&zero1));
group.setCurrentTime(group.duration());
QCOMPARE(group.currentAnimation(), static_cast<QAbstractAnimationJob*>(&anim));
}
void tst_QSequentialAnimationGroupJob::insertAnimation()
{
QSequentialAnimationGroupJob group;
group.setLoopCount(2);
TestAnimation *anim = new TestAnimation;
group.appendAnimation(anim);
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
group.appendAnimation(new TestAnimation);
}
class ClearFinishedListener: public QAnimationJobChangeListener
{
public:
ClearFinishedListener(QSequentialAnimationGroupJob *g) : group(g) {}
void animationFinished(QAbstractAnimationJob *) override
{
group->clear();
}
QSequentialAnimationGroupJob *group;
};
class RefillFinishedListener: public QAnimationJobChangeListener
{
public:
RefillFinishedListener(QSequentialAnimationGroupJob *g) : group(g) {}
void animationFinished(QAbstractAnimationJob *) override
{
group->stop();
group->clear();
group->appendAnimation(new TestAnimation);
group->start();
}
QSequentialAnimationGroupJob *group;
};
void tst_QSequentialAnimationGroupJob::clear()
{
QSKIP("deleting an animation when finished is not currently supported");
QSequentialAnimationGroupJob group;
TestAnimation *anim1 = new TestAnimation;
group.appendAnimation(anim1);
ClearFinishedListener clearListener(&group);
anim1->addAnimationChangeListener(&clearListener, QAbstractAnimationJob::Completion);
TestAnimation *anim2 = new TestAnimation;
group.appendAnimation(anim2);
QCOMPARE(group.children()->first(), anim1);
QCOMPARE(group.children()->last(), anim2);
group.start();
QTest::qWait(anim1->duration() + 100);
QTRY_VERIFY(group.children()->isEmpty());
QCOMPARE(group.state(), QAbstractAnimationJob::Stopped);
QCOMPARE(group.currentLoopTime(), 0);
anim1 = new TestAnimation;
group.appendAnimation(anim1);
RefillFinishedListener refillListener(&group);
anim1->addAnimationChangeListener(&refillListener, QAbstractAnimationJob::Completion);
group.start();
QTest::qWait(anim1->duration() + 100);
QTRY_COMPARE(group.state(), QAbstractAnimationJob::Running);
}
void tst_QSequentialAnimationGroupJob::pauseResume()
{
QParallelAnimationGroupJob group;
TestAnimation *anim = new TestAnimation;
group.appendAnimation(anim);
StateChangeListener spy;
anim->addAnimationChangeListener(&spy, QAbstractAnimationJob::StateChange);
QCOMPARE(group.duration(), 250);
group.start();
QTest::qWait(100);
QTRY_COMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(anim->state(), QAnimationGroupJob::Running);
QCOMPARE(spy.count(), 1);
spy.clear();
const int currentTime = group.currentLoopTime();
QCOMPARE(anim->currentLoopTime(), currentTime);
group.pause();
QCOMPARE(group.state(), QAnimationGroupJob::Paused);
QCOMPARE(group.currentLoopTime(), currentTime);
QCOMPARE(anim->state(), QAnimationGroupJob::Paused);
QCOMPARE(anim->currentLoopTime(), currentTime);
QCOMPARE(spy.count(), 1);
spy.clear();
group.resume();
QCOMPARE(group.state(), QAnimationGroupJob::Running);
QCOMPARE(group.currentLoopTime(), currentTime);
QCOMPARE(anim->state(), QAnimationGroupJob::Running);
QCOMPARE(anim->currentLoopTime(), currentTime);
QCOMPARE(spy.count(), 1);
anim->removeAnimationChangeListener(&spy, QAbstractAnimationJob::StateChange);
}
void tst_QSequentialAnimationGroupJob::uncontrolledWithLoops()
{
QSequentialAnimationGroupJob group;
TestAnimation *plain = new TestAnimation(100);
TestAnimation *loopsForever = new TestAnimation();
UncontrolledAnimation *notTimeBased = new UncontrolledAnimation();
loopsForever->setLoopCount(-1);
group.appendAnimation(plain);
group.appendAnimation(loopsForever);
group.appendAnimation(notTimeBased);
StateChangeListener listener;
group.addAnimationChangeListener(&listener, QAbstractAnimationJob::CurrentLoop);
group.setLoopCount(2);
group.start();
QCOMPARE(group.currentLoop(), 0);
QCOMPARE(group.state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(plain->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
loopsForever->stop();
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Stopped); // Stops on its own after 250ms
QTRY_COMPARE(group.currentLoop(), 1);
QCOMPARE(group.state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(plain->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(plain->state(), QAbstractAnimationJob::Stopped);
QTRY_COMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
loopsForever->stop();
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(notTimeBased->state(), QAbstractAnimationJob::Stopped);
QTRY_COMPARE(group.state(), QAbstractAnimationJob::Stopped);
}
void tst_QSequentialAnimationGroupJob::deleteFromListener()
{
QSequentialAnimationGroupJob *group = new QSequentialAnimationGroupJob;
UncontrolledAnimation *uncontrolled = new UncontrolledAnimation();
TestAnimation *shortLoop = new TestAnimation(100);
UncontrolledAnimation *more = new UncontrolledAnimation();
shortLoop->setLoopCount(-1);
group->appendAnimation(uncontrolled);
group->appendAnimation(shortLoop);
group->appendAnimation(more);
StateChangeListener listener;
listener.beEvil = true;
shortLoop->addAnimationChangeListener(&listener, QAbstractAnimationJob::StateChange);
group->setLoopCount(2);
group->start();
QCOMPARE(group->currentLoop(), 0);
QCOMPARE(group->state(), QAbstractAnimationJob::Running);
QTRY_COMPARE(uncontrolled->state(), QAbstractAnimationJob::Running);
QVERIFY(!listener.groupDeleted);
uncontrolled->stop();
QTRY_VERIFY(listener.groupDeleted);
// It's dead, Jim.
}
void tst_QSequentialAnimationGroupJob::noIntegerOverflow()
{
const int Max = std::numeric_limits<int>::max();
// summed child durations saturate to INT_MAX instead of wrapping
{
QSequentialAnimationGroupJob group;
group.appendAnimation(new TestAnimation(Max / 2 + 1));
group.appendAnimation(new TestAnimation(Max / 2 + 1));
QCOMPARE(group.duration(), Max);
}
// the right child and its local time are still found past INT_MAX
{
QSequentialAnimationGroupJob group;
auto *a1 = new TestAnimation(Max - 100); // [0, Max - 100)
auto *a2 = new TestAnimation(200); // timeOffset + 200 should not wrap
group.appendAnimation(a1);
group.appendAnimation(a2);
QCOMPARE(group.duration(), Max);
group.setCurrentTime(Max - 50); // 50 ms into a2
QCOMPARE(group.currentAnimation(), a2);
QCOMPARE(a2->currentLoopTime(), 50);
a1->setDuration(Max);
group.setCurrentTime(Max - 25);
QCOMPARE(group.currentAnimation(), a1);
QCOMPARE(a1->currentLoopTime(), Max - 25);
}
// removeAnimation() recomputes the times without overflow
{
QSequentialAnimationGroupJob group;
group.setLoopCount(2);
auto *a1 = new TestAnimation(Max / 2 + 1);
auto *a2 = new TestAnimation(Max / 2 + 1);
group.appendAnimation(a1);
group.appendAnimation(a2);
QCOMPARE(group.totalDuration(), Max);
group.setCurrentTime(50); // 50 ms into a1
QCOMPARE(group.currentAnimation(), a1);
std::unique_ptr<QAbstractAnimationJob> a2Guard(a2);
group.removeAnimation(a2);
QCOMPARE(group.currentTime(), Max);
}
}
QTEST_MAIN(tst_QSequentialAnimationGroupJob)
#include "tst_qsequentialanimationgroupjob.moc"