1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30
31#include <QtQml/private/qparallelanimationgroupjob_p.h>
32
33Q_DECLARE_METATYPE(QAbstractAnimationJob::State)
34
35class tst_QParallelAnimationGroupJob : public QObject
36{
37 Q_OBJECT
38public Q_SLOTS:
39 void initTestCase();
40
41private slots:
42 void construction();
43 void setCurrentTime();
44 void stateChanged();
45 void clearGroup();
46 void propagateGroupUpdateToChildren();
47 void updateChildrenWithRunningGroup();
48 void deleteChildrenWithRunningGroup();
49 void startChildrenWithStoppedGroup();
50 void stopGroupWithRunningChild();
51 void startGroupWithRunningChild();
52 void zeroDurationAnimation();
53 void stopUncontrolledAnimations();
54 void uncontrolledWithLoops();
55 void loopCount_data();
56 void loopCount();
57 void addAndRemoveDuration();
58 void pauseResume();
59
60 void crashWhenRemovingUncontrolledAnimation();
61};
62
63void tst_QParallelAnimationGroupJob::initTestCase()
64{
65 qRegisterMetaType<QAbstractAnimationJob::State>(typeName: "QAbstractAnimationJob::State");
66#if defined(Q_OS_DARWIN)
67 // give the Apple application's start event queue time to clear
68 QTest::qWait(1000);
69#endif
70}
71
72void tst_QParallelAnimationGroupJob::construction()
73{
74 QParallelAnimationGroupJob animationgroup;
75}
76
77class TestAnimation : public QAbstractAnimationJob
78{
79public:
80 TestAnimation(int duration = 250) : m_duration(duration) {}
81 int duration() const { return m_duration; }
82
83private:
84 int m_duration;
85};
86
87class UncontrolledAnimation : public QObject, public QAbstractAnimationJob
88{
89 Q_OBJECT
90public:
91 UncontrolledAnimation()
92 {
93 }
94
95 int duration() const { return -1; /* not time driven */ }
96
97protected:
98 void timerEvent(QTimerEvent *event)
99 {
100 if (event->timerId() == id)
101 stop();
102 }
103
104 void updateRunning(bool running)
105 {
106 if (running) {
107 id = startTimer(interval: 500);
108 } else {
109 killTimer(id);
110 id = 0;
111 }
112 }
113
114private:
115 int id = 0;
116};
117
118class StateChangeListener: public QAnimationJobChangeListener
119{
120public:
121 virtual void animationStateChanged(QAbstractAnimationJob *, QAbstractAnimationJob::State newState, QAbstractAnimationJob::State)
122 {
123 states << newState;
124 }
125
126 void clear() { states.clear(); }
127 int count() { return states.count(); }
128
129 QList<QAbstractAnimationJob::State> states;
130};
131
132class FinishedListener: public QAnimationJobChangeListener
133{
134public:
135 FinishedListener() {}
136
137 virtual void animationFinished(QAbstractAnimationJob *) { ++m_count; }
138 void clear() { m_count = 0; }
139 int count() { return m_count; }
140
141private:
142 int m_count = 0;
143};
144
145void tst_QParallelAnimationGroupJob::setCurrentTime()
146{
147 // originally was parallel operating on different object/properties
148 QAnimationGroupJob *parallel = new QParallelAnimationGroupJob();
149 TestAnimation *a1_p_o1 = new TestAnimation;
150 TestAnimation *a1_p_o2 = new TestAnimation;
151 TestAnimation *a1_p_o3 = new TestAnimation;
152 a1_p_o2->setLoopCount(3);
153 parallel->appendAnimation(animation: a1_p_o1);
154 parallel->appendAnimation(animation: a1_p_o2);
155 parallel->appendAnimation(animation: a1_p_o3);
156
157 UncontrolledAnimation *notTimeDriven = new UncontrolledAnimation;
158 QCOMPARE(notTimeDriven->totalDuration(), -1);
159
160 TestAnimation *loopsForever = new TestAnimation;
161 loopsForever->setLoopCount(-1);
162 QCOMPARE(loopsForever->totalDuration(), -1);
163
164 QParallelAnimationGroupJob group;
165 group.appendAnimation(animation: parallel);
166 group.appendAnimation(animation: notTimeDriven);
167 group.appendAnimation(animation: loopsForever);
168
169 // Current time = 1
170 group.setCurrentTime(1);
171 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
172 QCOMPARE(parallel->state(), QAnimationGroupJob::Stopped);
173 QCOMPARE(a1_p_o1->state(), QAnimationGroupJob::Stopped);
174 QCOMPARE(a1_p_o2->state(), QAnimationGroupJob::Stopped);
175 QCOMPARE(a1_p_o3->state(), QAnimationGroupJob::Stopped);
176 QCOMPARE(notTimeDriven->state(), QAnimationGroupJob::Stopped);
177 QCOMPARE(loopsForever->state(), QAnimationGroupJob::Stopped);
178
179 QCOMPARE(group.currentLoopTime(), 1);
180 QCOMPARE(a1_p_o1->currentLoopTime(), 1);
181 QCOMPARE(a1_p_o2->currentLoopTime(), 1);
182 QCOMPARE(a1_p_o3->currentLoopTime(), 1);
183 QCOMPARE(notTimeDriven->currentLoopTime(), 1);
184 QCOMPARE(loopsForever->currentLoopTime(), 1);
185
186 // Current time = 250
187 group.setCurrentTime(250);
188 QCOMPARE(group.currentLoopTime(), 250);
189 QCOMPARE(a1_p_o1->currentLoopTime(), 250);
190 QCOMPARE(a1_p_o2->currentLoopTime(), 0);
191 QCOMPARE(a1_p_o2->currentLoop(), 1);
192 QCOMPARE(a1_p_o3->currentLoopTime(), 250);
193 QCOMPARE(notTimeDriven->currentLoopTime(), 250);
194 QCOMPARE(loopsForever->currentLoopTime(), 0);
195 QCOMPARE(loopsForever->currentLoop(), 1);
196
197 // Current time = 251
198 group.setCurrentTime(251);
199 QCOMPARE(group.currentLoopTime(), 251);
200 QCOMPARE(a1_p_o1->currentLoopTime(), 250);
201 QCOMPARE(a1_p_o2->currentLoopTime(), 1);
202 QCOMPARE(a1_p_o2->currentLoop(), 1);
203 QCOMPARE(a1_p_o3->currentLoopTime(), 250);
204 QCOMPARE(notTimeDriven->currentLoopTime(), 251);
205 QCOMPARE(loopsForever->currentLoopTime(), 1);
206}
207
208void tst_QParallelAnimationGroupJob::stateChanged()
209{
210 //this ensures that the correct animations are started when starting the group
211 TestAnimation *anim1 = new TestAnimation(1000);
212 TestAnimation *anim2 = new TestAnimation(2000);
213 TestAnimation *anim3 = new TestAnimation(3000);
214 TestAnimation *anim4 = new TestAnimation(3000);
215
216 QParallelAnimationGroupJob group;
217 group.appendAnimation(animation: anim1);
218 group.appendAnimation(animation: anim2);
219 group.appendAnimation(animation: anim3);
220 group.appendAnimation(animation: anim4);
221
222 StateChangeListener spy1;
223 anim1->addAnimationChangeListener(listener: &spy1, QAbstractAnimationJob::StateChange);
224 StateChangeListener spy2;
225 anim2->addAnimationChangeListener(listener: &spy2, QAbstractAnimationJob::StateChange);
226 StateChangeListener spy3;
227 anim3->addAnimationChangeListener(listener: &spy3, QAbstractAnimationJob::StateChange);
228 StateChangeListener spy4;
229 anim4->addAnimationChangeListener(listener: &spy4, QAbstractAnimationJob::StateChange);
230
231 //first; let's start forward
232 group.start();
233 //all the animations should be started
234 QCOMPARE(spy1.count(), 1);
235 QCOMPARE(spy1.states.last(), TestAnimation::Running);
236 QCOMPARE(spy2.count(), 1);
237 QCOMPARE(spy2.states.last(), TestAnimation::Running);
238 QCOMPARE(spy3.count(), 1);
239 QCOMPARE(spy3.states.last(), TestAnimation::Running);
240 QCOMPARE(spy4.count(), 1);
241 QCOMPARE(spy4.states.last(), TestAnimation::Running);
242
243 group.setCurrentTime(1500); //anim1 should be finished
244 QCOMPARE(group.state(), QAnimationGroupJob::Running);
245 QCOMPARE(spy1.count(), 2);
246 QCOMPARE(spy1.states.last(), TestAnimation::Stopped);
247 QCOMPARE(spy2.count(), 1); //no change
248 QCOMPARE(spy3.count(), 1); //no change
249 QCOMPARE(spy4.count(), 1); //no change
250
251 group.setCurrentTime(2500); //anim2 should be finished
252 QCOMPARE(group.state(), QAnimationGroupJob::Running);
253 QCOMPARE(spy1.count(), 2); //no change
254 QCOMPARE(spy2.count(), 2);
255 QCOMPARE(spy2.states.last(), TestAnimation::Stopped);
256 QCOMPARE(spy3.count(), 1); //no change
257 QCOMPARE(spy4.count(), 1); //no change
258
259 group.setCurrentTime(3500); //everything should be finished
260 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
261 QCOMPARE(spy1.count(), 2); //no change
262 QCOMPARE(spy2.count(), 2); //no change
263 QCOMPARE(spy3.count(), 2);
264 QCOMPARE(spy3.states.last(), TestAnimation::Stopped);
265 QCOMPARE(spy4.count(), 2);
266 QCOMPARE(spy4.states.last(), TestAnimation::Stopped);
267
268 //cleanup
269 spy1.clear();
270 spy2.clear();
271 spy3.clear();
272 spy4.clear();
273
274 //now let's try to reverse that
275 group.setDirection(QAbstractAnimationJob::Backward);
276 group.start();
277
278 //only anim3 and anim4 should be started
279 QCOMPARE(group.state(), QAnimationGroupJob::Running);
280 QCOMPARE(spy1.count(), 0);
281 QCOMPARE(spy2.count(), 0);
282 QCOMPARE(spy3.count(), 1);
283 QCOMPARE(spy3.states.last(), TestAnimation::Running);
284 QCOMPARE(spy4.count(), 1);
285 QCOMPARE(spy4.states.last(), TestAnimation::Running);
286
287 group.setCurrentTime(1500); //anim2 should be started
288 QCOMPARE(group.state(), QAnimationGroupJob::Running);
289 QCOMPARE(spy1.count(), 0); //no change
290 QCOMPARE(spy2.count(), 1);
291 QCOMPARE(spy2.states.last(), TestAnimation::Running);
292 QCOMPARE(spy3.count(), 1); //no change
293 QCOMPARE(spy4.count(), 1); //no change
294
295 group.setCurrentTime(500); //anim1 is finally also started
296 QCOMPARE(group.state(), QAnimationGroupJob::Running);
297 QCOMPARE(spy1.count(), 1);
298 QCOMPARE(spy1.states.last(), TestAnimation::Running);
299 QCOMPARE(spy2.count(), 1); //no change
300 QCOMPARE(spy3.count(), 1); //no change
301 QCOMPARE(spy4.count(), 1); //no change
302
303 group.setCurrentTime(0); //everything should be stopped
304 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
305 QCOMPARE(spy1.count(), 2);
306 QCOMPARE(spy1.states.last(), TestAnimation::Stopped);
307 QCOMPARE(spy2.count(), 2);
308 QCOMPARE(spy2.states.last(), TestAnimation::Stopped);
309 QCOMPARE(spy3.count(), 2);
310 QCOMPARE(spy3.states.last(), TestAnimation::Stopped);
311 QCOMPARE(spy4.count(), 2);
312 QCOMPARE(spy4.states.last(), TestAnimation::Stopped);
313}
314
315void tst_QParallelAnimationGroupJob::clearGroup()
316{
317 QParallelAnimationGroupJob group;
318 static const int animationCount = 10;
319
320 for (int i = 0; i < animationCount; ++i) {
321 group.appendAnimation(animation: new QParallelAnimationGroupJob);
322 }
323
324 int count = 0;
325 for (QAbstractAnimationJob *anim = group.firstChild(); anim; anim = anim->nextSibling())
326 ++count;
327 QCOMPARE(count, animationCount);
328
329 group.clear();
330
331 QVERIFY(!group.firstChild() && !group.lastChild());
332 QCOMPARE(group.currentLoopTime(), 0);
333}
334
335void tst_QParallelAnimationGroupJob::propagateGroupUpdateToChildren()
336{
337 // this test verifies if group state changes are updating its children correctly
338 QParallelAnimationGroupJob group;
339
340 TestAnimation anim1(100);
341 TestAnimation anim2(200);
342
343 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
344 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
345 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
346
347 group.appendAnimation(animation: &anim1);
348 group.appendAnimation(animation: &anim2);
349
350 group.start();
351
352 QCOMPARE(group.state(), QAnimationGroupJob::Running);
353 QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
354 QCOMPARE(anim2.state(), QAnimationGroupJob::Running);
355
356 group.pause();
357
358 QCOMPARE(group.state(), QAnimationGroupJob::Paused);
359 QCOMPARE(anim1.state(), QAnimationGroupJob::Paused);
360 QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
361
362 group.stop();
363
364 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
365 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
366 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
367}
368
369void tst_QParallelAnimationGroupJob::updateChildrenWithRunningGroup()
370{
371 // assert that its possible to modify a child's state directly while their group is running
372 QParallelAnimationGroupJob group;
373
374 TestAnimation anim(200);
375
376 StateChangeListener groupStateChangedSpy;
377 group.addAnimationChangeListener(listener: &groupStateChangedSpy, QAbstractAnimationJob::StateChange);
378 StateChangeListener childStateChangedSpy;
379 anim.addAnimationChangeListener(listener: &childStateChangedSpy, QAbstractAnimationJob::StateChange);
380
381 QCOMPARE(groupStateChangedSpy.count(), 0);
382 QCOMPARE(childStateChangedSpy.count(), 0);
383 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
384 QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
385
386 group.appendAnimation(animation: &anim);
387
388 group.start();
389
390 QCOMPARE(group.state(), QAnimationGroupJob::Running);
391 QCOMPARE(anim.state(), QAnimationGroupJob::Running);
392
393 QCOMPARE(groupStateChangedSpy.count(), 1);
394 QCOMPARE(childStateChangedSpy.count(), 1);
395
396 QCOMPARE(groupStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
397 QCOMPARE(childStateChangedSpy.states.at(0), QAnimationGroupJob::Running);
398
399 // starting directly a running child will not have any effect
400 anim.start();
401
402 QCOMPARE(groupStateChangedSpy.count(), 1);
403 QCOMPARE(childStateChangedSpy.count(), 1);
404
405 anim.pause();
406
407 QCOMPARE(group.state(), QAnimationGroupJob::Running);
408 QCOMPARE(anim.state(), QAnimationGroupJob::Paused);
409
410 // in the animation stops directly, the group will still be running
411 anim.stop();
412
413 QCOMPARE(group.state(), QAnimationGroupJob::Running);
414 QCOMPARE(anim.state(), QAnimationGroupJob::Stopped);
415
416 //cleanup
417 group.removeAnimationChangeListener(listener: &groupStateChangedSpy, QAbstractAnimationJob::StateChange);
418 anim.removeAnimationChangeListener(listener: &childStateChangedSpy, QAbstractAnimationJob::StateChange);
419}
420
421void tst_QParallelAnimationGroupJob::deleteChildrenWithRunningGroup()
422{
423 // test if children can be activated when their group is stopped
424 QParallelAnimationGroupJob group;
425
426 TestAnimation *anim1 = new TestAnimation(200);
427 group.appendAnimation(animation: anim1);
428
429 QCOMPARE(group.duration(), anim1->duration());
430
431 group.start();
432 QCOMPARE(group.state(), QAnimationGroupJob::Running);
433 QCOMPARE(anim1->state(), QAnimationGroupJob::Running);
434
435 QTest::qWait(ms: 80);
436 QVERIFY(group.currentLoopTime() > 0);
437
438 delete anim1;
439 QVERIFY(!group.firstChild());
440 QCOMPARE(group.duration(), 0);
441 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
442 QCOMPARE(group.currentLoopTime(), 0); //that's the invariant
443}
444
445void tst_QParallelAnimationGroupJob::startChildrenWithStoppedGroup()
446{
447 // test if children can be activated when their group is stopped
448 QParallelAnimationGroupJob group;
449
450 TestAnimation anim1(200);
451 TestAnimation anim2(200);
452
453 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
454 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
455 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
456
457 group.appendAnimation(animation: &anim1);
458 group.appendAnimation(animation: &anim2);
459
460 group.stop();
461
462 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
463 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
464 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
465
466 anim1.start();
467 anim2.start();
468 anim2.pause();
469
470 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
471 QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
472 QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
473}
474
475void tst_QParallelAnimationGroupJob::stopGroupWithRunningChild()
476{
477 // children that started independently will not be affected by a group stop
478 QParallelAnimationGroupJob group;
479
480 TestAnimation anim1(200);
481 TestAnimation anim2(200);
482
483 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
484 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
485 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
486
487 group.appendAnimation(animation: &anim1);
488 group.appendAnimation(animation: &anim2);
489
490 anim1.start();
491 anim2.start();
492 anim2.pause();
493
494 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
495 QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
496 QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
497
498 group.stop();
499
500 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
501 QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
502 QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
503
504 anim1.stop();
505 anim2.stop();
506
507 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
508 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
509 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
510}
511
512void tst_QParallelAnimationGroupJob::startGroupWithRunningChild()
513{
514 // as the group has precedence over its children, starting a group will restart all the children
515 QParallelAnimationGroupJob group;
516
517 TestAnimation anim1(200);
518 TestAnimation anim2(200);
519
520 StateChangeListener stateChangedSpy1;
521 anim1.addAnimationChangeListener(listener: &stateChangedSpy1, QAbstractAnimationJob::StateChange);
522 StateChangeListener stateChangedSpy2;
523 anim2.addAnimationChangeListener(listener: &stateChangedSpy2, QAbstractAnimationJob::StateChange);
524
525 QCOMPARE(stateChangedSpy1.count(), 0);
526 QCOMPARE(stateChangedSpy2.count(), 0);
527 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
528 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
529 QCOMPARE(anim2.state(), QAnimationGroupJob::Stopped);
530
531 group.appendAnimation(animation: &anim1);
532 group.appendAnimation(animation: &anim2);
533
534 anim1.start();
535 anim2.start();
536 anim2.pause();
537
538 QCOMPARE(stateChangedSpy1.states.at(0), QAnimationGroupJob::Running);
539 QCOMPARE(stateChangedSpy2.states.at(0), QAnimationGroupJob::Running);
540 QCOMPARE(stateChangedSpy2.states.at(1), QAnimationGroupJob::Paused);
541
542 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
543 QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
544 QCOMPARE(anim2.state(), QAnimationGroupJob::Paused);
545
546 group.start();
547
548 QCOMPARE(stateChangedSpy1.count(), 3);
549 QCOMPARE(stateChangedSpy1.states.at(1), QAnimationGroupJob::Stopped);
550 QCOMPARE(stateChangedSpy1.states.at(2), QAnimationGroupJob::Running);
551
552 QCOMPARE(stateChangedSpy2.count(), 4);
553 QCOMPARE(stateChangedSpy2.states.at(2), QAnimationGroupJob::Stopped);
554 QCOMPARE(stateChangedSpy2.states.at(3), QAnimationGroupJob::Running);
555
556 QCOMPARE(group.state(), QAnimationGroupJob::Running);
557 QCOMPARE(anim1.state(), QAnimationGroupJob::Running);
558 QCOMPARE(anim2.state(), QAnimationGroupJob::Running);
559
560 //cleanup
561 anim1.removeAnimationChangeListener(listener: &stateChangedSpy1, QAbstractAnimationJob::StateChange);
562 anim2.removeAnimationChangeListener(listener: &stateChangedSpy2, QAbstractAnimationJob::StateChange);
563}
564
565void tst_QParallelAnimationGroupJob::zeroDurationAnimation()
566{
567 QParallelAnimationGroupJob group;
568
569 TestAnimation anim1(0);
570 TestAnimation anim2(100);
571 TestAnimation anim3(10);
572
573 StateChangeListener stateChangedSpy1;
574 anim1.addAnimationChangeListener(listener: &stateChangedSpy1, QAbstractAnimationJob::StateChange);
575 FinishedListener finishedSpy1;
576 anim1.addAnimationChangeListener(listener: &finishedSpy1, QAbstractAnimationJob::Completion);
577
578 StateChangeListener stateChangedSpy2;
579 anim2.addAnimationChangeListener(listener: &stateChangedSpy2, QAbstractAnimationJob::StateChange);
580 FinishedListener finishedSpy2;
581 anim2.addAnimationChangeListener(listener: &finishedSpy2, QAbstractAnimationJob::Completion);
582
583 StateChangeListener stateChangedSpy3;
584 anim3.addAnimationChangeListener(listener: &stateChangedSpy3, QAbstractAnimationJob::StateChange);
585 FinishedListener finishedSpy3;
586 anim3.addAnimationChangeListener(listener: &finishedSpy3, QAbstractAnimationJob::Completion);
587
588 group.appendAnimation(animation: &anim1);
589 group.appendAnimation(animation: &anim2);
590 group.appendAnimation(animation: &anim3);
591 QCOMPARE(stateChangedSpy1.count(), 0);
592 group.start();
593 QCOMPARE(stateChangedSpy1.count(), 2);
594 QCOMPARE(finishedSpy1.count(), 1);
595 QCOMPARE(stateChangedSpy1.states.at(0), QAnimationGroupJob::Running);
596 QCOMPARE(stateChangedSpy1.states.at(1), QAnimationGroupJob::Stopped);
597
598 QCOMPARE(stateChangedSpy2.count(), 1);
599 QCOMPARE(finishedSpy2.count(), 0);
600 QCOMPARE(stateChangedSpy1.states.at(0), QAnimationGroupJob::Running);
601
602 QCOMPARE(stateChangedSpy3.count(), 1);
603 QCOMPARE(finishedSpy3.count(), 0);
604 QCOMPARE(stateChangedSpy3.states.at(0), QAnimationGroupJob::Running);
605
606 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
607 QCOMPARE(anim2.state(), QAnimationGroupJob::Running);
608 QCOMPARE(anim3.state(), QAnimationGroupJob::Running);
609 QCOMPARE(group.state(), QAnimationGroupJob::Running);
610
611 group.stop();
612 group.setLoopCount(4);
613 stateChangedSpy1.clear();
614 stateChangedSpy2.clear();
615 stateChangedSpy3.clear();
616
617 group.start();
618 QCOMPARE(stateChangedSpy1.count(), 2);
619 QCOMPARE(stateChangedSpy2.count(), 1);
620 QCOMPARE(stateChangedSpy3.count(), 1);
621 group.setCurrentTime(50);
622 QCOMPARE(stateChangedSpy1.count(), 2);
623 QCOMPARE(stateChangedSpy2.count(), 1);
624 QCOMPARE(stateChangedSpy3.count(), 2);
625 group.setCurrentTime(150);
626 QCOMPARE(stateChangedSpy1.count(), 4);
627 QCOMPARE(stateChangedSpy2.count(), 3);
628 QCOMPARE(stateChangedSpy3.count(), 4);
629 group.setCurrentTime(50);
630 QCOMPARE(stateChangedSpy1.count(), 6);
631 QCOMPARE(stateChangedSpy2.count(), 5);
632 QCOMPARE(stateChangedSpy3.count(), 6);
633
634 //cleanup
635 anim1.removeAnimationChangeListener(listener: &stateChangedSpy1, QAbstractAnimationJob::StateChange);
636 anim1.removeAnimationChangeListener(listener: &finishedSpy1, QAbstractAnimationJob::Completion);
637 anim2.removeAnimationChangeListener(listener: &stateChangedSpy2, QAbstractAnimationJob::StateChange);
638 anim2.removeAnimationChangeListener(listener: &finishedSpy2, QAbstractAnimationJob::Completion);
639 anim3.removeAnimationChangeListener(listener: &stateChangedSpy3, QAbstractAnimationJob::StateChange);
640 anim3.removeAnimationChangeListener(listener: &finishedSpy3, QAbstractAnimationJob::Completion);
641}
642
643void tst_QParallelAnimationGroupJob::stopUncontrolledAnimations()
644{
645 QParallelAnimationGroupJob group;
646
647 TestAnimation anim1(0);
648
649 UncontrolledAnimation notTimeDriven;
650 QCOMPARE(notTimeDriven.totalDuration(), -1);
651
652 TestAnimation loopsForever(100);
653 loopsForever.setLoopCount(-1);
654
655 StateChangeListener stateChangedSpy;
656 anim1.addAnimationChangeListener(listener: &stateChangedSpy, QAbstractAnimationJob::StateChange);
657
658 group.appendAnimation(animation: &anim1);
659 group.appendAnimation(animation: &notTimeDriven);
660 group.appendAnimation(animation: &loopsForever);
661
662 group.start();
663
664 QCOMPARE(stateChangedSpy.count(), 2);
665 QCOMPARE(stateChangedSpy.states.at(0), QAnimationGroupJob::Running);
666 QCOMPARE(stateChangedSpy.states.at(1), QAnimationGroupJob::Stopped);
667
668 QCOMPARE(group.state(), QAnimationGroupJob::Running);
669 QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Running);
670 QCOMPARE(loopsForever.state(), QAnimationGroupJob::Running);
671 QCOMPARE(anim1.state(), QAnimationGroupJob::Stopped);
672
673 notTimeDriven.stop();
674
675 QCOMPARE(group.state(), QAnimationGroupJob::Running);
676 QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
677 QCOMPARE(loopsForever.state(), QAnimationGroupJob::Running);
678
679 loopsForever.stop();
680
681 QCOMPARE(group.state(), QAnimationGroupJob::Stopped);
682 QCOMPARE(notTimeDriven.state(), QAnimationGroupJob::Stopped);
683 QCOMPARE(loopsForever.state(), QAnimationGroupJob::Stopped);
684}
685
686struct AnimState {
687 AnimState(int time = -1) : time(time) {}
688 AnimState(int time, int state) : time(time), state(state) {}
689 int time;
690 int state = -1;
691};
692
693#define Running QAbstractAnimationJob::Running
694#define Stopped QAbstractAnimationJob::Stopped
695
696Q_DECLARE_METATYPE(AnimState)
697void tst_QParallelAnimationGroupJob::loopCount_data()
698{
699 QTest::addColumn<bool>(name: "directionBackward");
700 QTest::addColumn<int>(name: "setLoopCount");
701 QTest::addColumn<int>(name: "initialGroupTime");
702 QTest::addColumn<int>(name: "currentGroupTime");
703 QTest::addColumn<AnimState>(name: "expected1");
704 QTest::addColumn<AnimState>(name: "expected2");
705 QTest::addColumn<AnimState>(name: "expected3");
706
707 // D U R A T I O N
708 // 100 60*2 0
709 // direction = Forward
710 QTest::newRow(dataTag: "50") << false << 3 << 0 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
711 QTest::newRow(dataTag: "100") << false << 3 << 0 << 100 << AnimState(100 ) << AnimState( 40, Running) << AnimState( 0, Stopped);
712 QTest::newRow(dataTag: "110") << false << 3 << 0 << 110 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
713 QTest::newRow(dataTag: "120") << false << 3 << 0 << 120 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped);
714
715 QTest::newRow(dataTag: "170") << false << 3 << 0 << 170 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
716 QTest::newRow(dataTag: "220") << false << 3 << 0 << 220 << AnimState(100 ) << AnimState( 40, Running) << AnimState( 0, Stopped);
717 QTest::newRow(dataTag: "230") << false << 3 << 0 << 230 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
718 QTest::newRow(dataTag: "240") << false << 3 << 0 << 240 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped);
719
720 QTest::newRow(dataTag: "290") << false << 3 << 0 << 290 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
721 QTest::newRow(dataTag: "340") << false << 3 << 0 << 340 << AnimState(100 ) << AnimState( 40, Running) << AnimState( 0, Stopped);
722 QTest::newRow(dataTag: "350") << false << 3 << 0 << 350 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
723 QTest::newRow(dataTag: "360") << false << 3 << 0 << 360 << AnimState(100, Stopped) << AnimState( 60 ) << AnimState( 0, Stopped);
724
725 QTest::newRow(dataTag: "410") << false << 3 << 0 << 410 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped);
726 QTest::newRow(dataTag: "460") << false << 3 << 0 << 460 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped);
727 QTest::newRow(dataTag: "470") << false << 3 << 0 << 470 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped);
728 QTest::newRow(dataTag: "480") << false << 3 << 0 << 480 << AnimState(100, Stopped) << AnimState( 60, Stopped) << AnimState( 0, Stopped);
729
730 // direction = Forward, rewind
731 QTest::newRow(dataTag: "120-110") << false << 3 << 120 << 110 << AnimState( 0, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
732 QTest::newRow(dataTag: "120-50") << false << 3 << 120 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
733 QTest::newRow(dataTag: "120-0") << false << 3 << 120 << 0 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped);
734 QTest::newRow(dataTag: "300-110") << false << 3 << 300 << 110 << AnimState( 0, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
735 QTest::newRow(dataTag: "300-50") << false << 3 << 300 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
736 QTest::newRow(dataTag: "300-0") << false << 3 << 300 << 0 << AnimState( 0, Running) << AnimState( 0, Running) << AnimState( 0, Stopped);
737 QTest::newRow(dataTag: "115-105") << false << 3 << 115 << 105 << AnimState( 42, Stopped) << AnimState( 45, Running) << AnimState( 0, Stopped);
738
739 // direction = Backward
740 QTest::newRow(dataTag: "b120-120") << true << 3 << 120 << 120 << AnimState( 42, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped);
741 QTest::newRow(dataTag: "b120-110") << true << 3 << 120 << 110 << AnimState( 42, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
742 QTest::newRow(dataTag: "b120-100") << true << 3 << 120 << 100 << AnimState(100, Running) << AnimState( 40, Running) << AnimState( 0, Stopped);
743 QTest::newRow(dataTag: "b120-50") << true << 3 << 120 << 50 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
744 QTest::newRow(dataTag: "b120-0") << true << 3 << 120 << 0 << AnimState( 0, Stopped) << AnimState( 0, Stopped) << AnimState( 0, Stopped);
745 QTest::newRow(dataTag: "b360-170") << true << 3 << 360 << 170 << AnimState( 50, Running) << AnimState( 50, Running) << AnimState( 0, Stopped);
746 QTest::newRow(dataTag: "b360-220") << true << 3 << 360 << 220 << AnimState(100, Running) << AnimState( 40, Running) << AnimState( 0, Stopped);
747 QTest::newRow(dataTag: "b360-210") << true << 3 << 360 << 210 << AnimState( 90, Running) << AnimState( 30, Running) << AnimState( 0, Stopped);
748 QTest::newRow(dataTag: "b360-120") << true << 3 << 360 << 120 << AnimState( 0, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped);
749
750 // rewind, direction = Backward
751 QTest::newRow(dataTag: "b50-110") << true << 3 << 50 << 110 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
752 QTest::newRow(dataTag: "b50-120") << true << 3 << 50 << 120 << AnimState(100, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped);
753 QTest::newRow(dataTag: "b50-140") << true << 3 << 50 << 140 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped);
754 QTest::newRow(dataTag: "b50-240") << true << 3 << 50 << 240 << AnimState(100, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped);
755 QTest::newRow(dataTag: "b50-260") << true << 3 << 50 << 260 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped);
756 QTest::newRow(dataTag: "b50-350") << true << 3 << 50 << 350 << AnimState(100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
757
758 // infinite looping
759 QTest::newRow(dataTag: "inf1220") << false << -1 << 0 << 1220 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped);
760 QTest::newRow(dataTag: "inf1310") << false << -1 << 0 << 1310 << AnimState( 100, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
761 // infinite looping, direction = Backward (will only loop once)
762 QTest::newRow(dataTag: "b.inf120-120") << true << -1 << 120 << 120 << AnimState( 42, Stopped) << AnimState( 60, Running) << AnimState( 0, Stopped);
763 QTest::newRow(dataTag: "b.inf120-20") << true << -1 << 120 << 20 << AnimState( 20, Running) << AnimState( 20, Running) << AnimState( 0, Stopped);
764 QTest::newRow(dataTag: "b.inf120-110") << true << -1 << 120 << 110 << AnimState( 42, Stopped) << AnimState( 50, Running) << AnimState( 0, Stopped);
765
766
767}
768
769#undef Stopped
770#undef Running
771
772void tst_QParallelAnimationGroupJob::loopCount()
773{
774 QFETCH(bool, directionBackward);
775 QFETCH(int, setLoopCount);
776 QFETCH(int, initialGroupTime);
777 QFETCH(int, currentGroupTime);
778 QFETCH(AnimState, expected1);
779 QFETCH(AnimState, expected2);
780 QFETCH(AnimState, expected3);
781
782 QParallelAnimationGroupJob group;
783
784 TestAnimation anim1(100);
785 TestAnimation anim2(60); //total 120
786 anim2.setLoopCount(2);
787 TestAnimation anim3(0);
788
789 group.appendAnimation(animation: &anim1);
790 group.appendAnimation(animation: &anim2);
791 group.appendAnimation(animation: &anim3);
792
793 group.setLoopCount(setLoopCount);
794 if (initialGroupTime >= 0)
795 group.setCurrentTime(initialGroupTime);
796 if (directionBackward)
797 group.setDirection(QAbstractAnimationJob::Backward);
798
799 group.start();
800 if (initialGroupTime >= 0)
801 group.setCurrentTime(initialGroupTime);
802
803 anim1.setCurrentTime(42); // 42 is "untouched"
804 anim2.setCurrentTime(42);
805
806 group.setCurrentTime(currentGroupTime);
807
808 QCOMPARE(anim1.currentLoopTime(), expected1.time);
809 QCOMPARE(anim2.currentLoopTime(), expected2.time);
810 QCOMPARE(anim3.currentLoopTime(), expected3.time);
811
812 if (expected1.state >=0)
813 QCOMPARE(int(anim1.state()), expected1.state);
814 if (expected2.state >=0)
815 QCOMPARE(int(anim2.state()), expected2.state);
816 if (expected3.state >=0)
817 QCOMPARE(int(anim3.state()), expected3.state);
818
819}
820
821void tst_QParallelAnimationGroupJob::addAndRemoveDuration()
822{
823 QParallelAnimationGroupJob group;
824 QCOMPARE(group.duration(), 0);
825 TestAnimation *test = new TestAnimation(250); // 0, duration = 250;
826 group.appendAnimation(animation: test);
827 QCOMPARE(test->group(), static_cast<QAnimationGroupJob*>(&group));
828 QCOMPARE(test->duration(), 250);
829 QCOMPARE(group.duration(), 250);
830
831 TestAnimation *test2 = new TestAnimation(750); // 1
832 group.appendAnimation(animation: test2);
833 QCOMPARE(test2->group(), static_cast<QAnimationGroupJob*>(&group));
834 QCOMPARE(group.duration(), 750);
835
836 TestAnimation *test3 = new TestAnimation(500); // 2
837 group.appendAnimation(animation: test3);
838 QCOMPARE(test3->group(), static_cast<QAnimationGroupJob*>(&group));
839 QCOMPARE(group.duration(), 750);
840
841 group.removeAnimation(animation: test2); // remove the one with duration = 750
842 delete test2;
843 QCOMPARE(group.duration(), 500);
844
845 group.removeAnimation(animation: test3); // remove the one with duration = 500
846 delete test3;
847 QCOMPARE(group.duration(), 250);
848
849 group.removeAnimation(animation: test); // remove the last one (with duration = 250)
850 QCOMPARE(test->group(), static_cast<QAnimationGroupJob*>(nullptr));
851 QCOMPARE(group.duration(), 0);
852 delete test;
853}
854
855void tst_QParallelAnimationGroupJob::pauseResume()
856{
857#ifdef Q_CC_MINGW
858 QSKIP("QTBUG-36290 - MinGW Animation tests are flaky.");
859#endif
860 QParallelAnimationGroupJob group;
861 TestAnimation *anim = new TestAnimation(250); // 0, duration = 250;
862 group.appendAnimation(animation: anim);
863 StateChangeListener spy;
864 anim->addAnimationChangeListener(listener: &spy, QAbstractAnimationJob::StateChange);
865 QCOMPARE(group.duration(), 250);
866 group.start();
867 QTest::qWait(ms: 100);
868 QCOMPARE(group.state(), QAnimationGroupJob::Running);
869 QCOMPARE(anim->state(), QAnimationGroupJob::Running);
870 QCOMPARE(spy.count(), 1);
871 spy.clear();
872 const int currentTime = group.currentLoopTime();
873 QCOMPARE(anim->currentLoopTime(), currentTime);
874
875 group.pause();
876 QCOMPARE(group.state(), QAnimationGroupJob::Paused);
877 QCOMPARE(group.currentLoopTime(), currentTime);
878 QCOMPARE(anim->state(), QAnimationGroupJob::Paused);
879 QCOMPARE(anim->currentLoopTime(), currentTime);
880 QCOMPARE(spy.count(), 1);
881 spy.clear();
882
883 group.resume();
884 QCOMPARE(group.state(), QAnimationGroupJob::Running);
885 QCOMPARE(group.currentLoopTime(), currentTime);
886 QCOMPARE(anim->state(), QAnimationGroupJob::Running);
887 QCOMPARE(anim->currentLoopTime(), currentTime);
888 QCOMPARE(spy.count(), 1);
889
890 group.stop();
891 spy.clear();
892 group.appendAnimation(animation: new TestAnimation(500));
893 group.start();
894 QCOMPARE(spy.count(), 1); //the animation should have been started
895 QCOMPARE(spy.states.at(0), TestAnimation::Running);
896 group.setCurrentTime(250); //end of first animation
897 QCOMPARE(spy.count(), 2); //the animation should have been stopped
898 QCOMPARE(spy.states.at(1), TestAnimation::Stopped);
899 group.pause();
900 QCOMPARE(spy.count(), 2); //this shouldn't have changed
901 group.resume();
902 QCOMPARE(spy.count(), 2); //this shouldn't have changed
903}
904
905// This is a regression test for QTBUG-8910, where a crash occurred when the
906// last animation was removed from a group.
907void tst_QParallelAnimationGroupJob::crashWhenRemovingUncontrolledAnimation()
908{
909 QParallelAnimationGroupJob group;
910 TestAnimation *anim = new TestAnimation;
911 anim->setLoopCount(-1);
912 TestAnimation *anim2 = new TestAnimation;
913 anim2->setLoopCount(-1);
914 group.appendAnimation(animation: anim);
915 group.appendAnimation(animation: anim2);
916 group.start();
917 delete anim;
918 // it would crash here because the internals of the group would still have a reference to anim
919 delete anim2;
920}
921
922void tst_QParallelAnimationGroupJob::uncontrolledWithLoops()
923{
924 QParallelAnimationGroupJob group;
925
926 TestAnimation *plain = new TestAnimation(100);
927 TestAnimation *loopsForever = new TestAnimation();
928 UncontrolledAnimation *notTimeBased = new UncontrolledAnimation();
929
930 loopsForever->setLoopCount(-1);
931
932 group.appendAnimation(animation: plain);
933 group.appendAnimation(animation: loopsForever);
934 group.appendAnimation(animation: notTimeBased);
935
936 StateChangeListener listener;
937 group.addAnimationChangeListener(listener: &listener, QAbstractAnimationJob::CurrentLoop);
938 group.setLoopCount(2);
939
940 group.start();
941
942 QCOMPARE(group.currentLoop(), 0);
943 QCOMPARE(group.state(), QAbstractAnimationJob::Running);
944 QCOMPARE(plain->state(), QAbstractAnimationJob::Running);
945 QCOMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
946 QCOMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
947
948 loopsForever->stop();
949 notTimeBased->stop();
950
951 QTRY_COMPARE(group.currentLoop(), 1);
952 QCOMPARE(group.state(), QAbstractAnimationJob::Running);
953 QCOMPARE(loopsForever->state(), QAbstractAnimationJob::Running);
954 QCOMPARE(notTimeBased->state(), QAbstractAnimationJob::Running);
955
956 loopsForever->stop();
957 notTimeBased->stop();
958
959 QTRY_COMPARE(group.state(), QAbstractAnimationJob::Stopped);
960}
961
962
963QTEST_MAIN(tst_QParallelAnimationGroupJob)
964#include "tst_qparallelanimationgroupjob.moc"
965

source code of qtdeclarative/tests/auto/qml/animation/qparallelanimationgroupjob/tst_qparallelanimationgroupjob.cpp