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#include <qtest.h>
29#include <QtQml/qqmlengine.h>
30#include <QtQml/qqmlexpression.h>
31#include <QtQml/qqmlcomponent.h>
32#include <QtQuick/qquickview.h>
33#include <QtQuick/private/qquickrectangle_p.h>
34#include <private/qquickimage_p.h>
35#include <private/qquickanimatedimage_p.h>
36#include <QSignalSpy>
37#include <QtQml/qqmlcontext.h>
38
39#include "../../shared/testhttpserver.h"
40#include "../../shared/util.h"
41
42Q_DECLARE_METATYPE(QQuickImageBase::Status)
43
44template <typename T> static T evaluate(QObject *scope, const QString &expression)
45{
46 QQmlExpression expr(qmlContext(scope), scope, expression);
47 QVariant result = expr.evaluate();
48 if (expr.hasError())
49 qWarning() << expr.error().toString();
50 return result.value<T>();
51}
52
53template <> void evaluate<void>(QObject *scope, const QString &expression)
54{
55 QQmlExpression expr(qmlContext(scope), scope, expression);
56 expr.evaluate();
57 if (expr.hasError())
58 qWarning() << expr.error().toString();
59}
60
61class tst_qquickanimatedimage : public QQmlDataTest
62{
63 Q_OBJECT
64public:
65 tst_qquickanimatedimage() {}
66
67private slots:
68 void cleanup();
69 void play();
70 void pause();
71 void stopped();
72 void setFrame();
73 void frameCount();
74 void mirror_running();
75 void mirror_notRunning();
76 void mirror_notRunning_data();
77 void remote();
78 void remote_data();
79 void sourceSize();
80 void sourceSizeChanges();
81 void sourceSizeChanges_intermediate();
82 void sourceSizeReadOnly();
83 void invalidSource();
84 void qtbug_16520();
85 void progressAndStatusChanges();
86 void playingAndPausedChanges();
87 void noCaching();
88 void sourceChangesOnFrameChanged();
89 void currentFrame();
90};
91
92void tst_qquickanimatedimage::cleanup()
93{
94 QQuickWindow window;
95 window.releaseResources();
96}
97
98void tst_qquickanimatedimage::play()
99{
100 QQmlEngine engine;
101 QQmlComponent component(&engine, testFileUrl(fileName: "stickman.qml"));
102 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
103 QVERIFY(anim);
104 QVERIFY(anim->isPlaying());
105
106 delete anim;
107}
108
109void tst_qquickanimatedimage::pause()
110{
111 QQmlEngine engine;
112 QQmlComponent component(&engine, testFileUrl(fileName: "stickmanpause.qml"));
113 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
114 QVERIFY(anim);
115
116 QTRY_VERIFY(anim->isPaused());
117 QTRY_VERIFY(anim->isPlaying());
118
119 delete anim;
120}
121
122void tst_qquickanimatedimage::stopped()
123{
124 QQmlEngine engine;
125 QQmlComponent component(&engine, testFileUrl(fileName: "stickmanstopped.qml"));
126 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
127 QVERIFY(anim);
128 QTRY_VERIFY(!anim->isPlaying());
129 QCOMPARE(anim->currentFrame(), 0);
130
131 delete anim;
132}
133
134void tst_qquickanimatedimage::setFrame()
135{
136 QQmlEngine engine;
137 QQmlComponent component(&engine, testFileUrl(fileName: "stickmanpause.qml"));
138 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
139 QVERIFY(anim);
140 QVERIFY(anim->isPlaying());
141 QCOMPARE(anim->currentFrame(), 2);
142
143 delete anim;
144}
145
146void tst_qquickanimatedimage::frameCount()
147{
148 QQmlEngine engine;
149 QQmlComponent component(&engine, testFileUrl(fileName: "colors.qml"));
150 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
151 QVERIFY(anim);
152 QVERIFY(anim->isPlaying());
153 QCOMPARE(anim->frameCount(), 3);
154
155 QSignalSpy frameCountChangedSpy(anim, &QQuickAnimatedImage::frameCountChanged);
156
157 const QUrl origSource = anim->source();
158 anim->setSource(QUrl());
159 QCOMPARE(anim->frameCount(), 0);
160 QCOMPARE(frameCountChangedSpy.count(), 1);
161 anim->setSource(origSource);
162 QCOMPARE(anim->frameCount(), 3);
163 QCOMPARE(frameCountChangedSpy.count(), 2);
164
165 delete anim;
166}
167
168void tst_qquickanimatedimage::mirror_running()
169{
170 // test where mirror is set to true after animation has started
171
172 QQuickView window;
173 window.setSource(testFileUrl(fileName: "hearts.qml"));
174 window.show();
175 QVERIFY(QTest::qWaitForWindowExposed(&window));
176
177 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: window.rootObject());
178 QVERIFY(anim);
179
180 int width = anim->property(name: "width").toInt();
181
182 QCOMPARE(anim->frameCount(), 2);
183
184 QCOMPARE(anim->currentFrame(), 0);
185 QImage frame0 = window.grabWindow();
186
187 anim->setCurrentFrame(1);
188 QCOMPARE(anim->currentFrame(), 1);
189 QImage frame1 = window.grabWindow();
190
191 anim->setCurrentFrame(0);
192
193 QSignalSpy spy(anim, SIGNAL(frameChanged()));
194 QVERIFY(spy.isValid());
195 anim->setPlaying(true);
196
197 QTRY_VERIFY(spy.count() == 1); spy.clear();
198 anim->setMirror(true);
199
200 QCOMPARE(anim->currentFrame(), 1);
201 QImage frame1_flipped = window.grabWindow();
202
203 QTRY_VERIFY(spy.count() == 1); spy.clear();
204 QCOMPARE(anim->currentFrame(), 0); // animation only has 2 frames, should cycle back to first
205 QImage frame0_flipped = window.grabWindow();
206
207 QTransform transform;
208 transform.translate(dx: width, dy: 0).scale(sx: -1, sy: 1.0);
209 QImage frame0_expected = frame0.transformed(matrix: transform);
210 QImage frame1_expected = frame1.transformed(matrix: transform);
211
212 if (window.devicePixelRatio() != 1.0 && window.rendererInterface()->graphicsApi() == QSGRendererInterface::Software)
213 QSKIP("QTBUG-53823");
214 QCOMPARE(frame0_flipped, frame0_expected);
215 if (window.devicePixelRatio() != 1.0 && window.rendererInterface()->graphicsApi() == QSGRendererInterface::Software)
216 QSKIP("QTBUG-53823");
217 QCOMPARE(frame1_flipped, frame1_expected);
218
219 delete anim;
220}
221
222void tst_qquickanimatedimage::mirror_notRunning()
223{
224 QFETCH(QUrl, fileUrl);
225
226 QQuickView window;
227 window.setSource(fileUrl);
228 window.show();
229 QTRY_VERIFY(window.isExposed());
230
231 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: window.rootObject());
232 QVERIFY(anim);
233
234 int width = anim->property(name: "width").toInt();
235 QImage screenshot = window.grabWindow();
236
237 QTransform transform;
238 transform.translate(dx: width, dy: 0).scale(sx: -1, sy: 1.0);
239 QImage expected = screenshot.transformed(matrix: transform);
240
241 int frame = anim->currentFrame();
242 bool playing = anim->isPlaying();
243 bool paused = anim->isPaused();
244
245 anim->setProperty(name: "mirror", value: true);
246 screenshot = window.grabWindow();
247
248 screenshot.save(fileName: "screen.png");
249 if (window.devicePixelRatio() != 1.0 && window.rendererInterface()->graphicsApi() == QSGRendererInterface::Software)
250 QSKIP("QTBUG-53823");
251 QCOMPARE(screenshot, expected);
252
253 // mirroring should not change the current frame or playing status
254 QCOMPARE(anim->currentFrame(), frame);
255 QCOMPARE(anim->isPlaying(), playing);
256 QCOMPARE(anim->isPaused(), paused);
257
258 delete anim;
259}
260
261void tst_qquickanimatedimage::mirror_notRunning_data()
262{
263 QTest::addColumn<QUrl>(name: "fileUrl");
264
265 QTest::newRow(dataTag: "paused") << testFileUrl(fileName: "stickmanpause.qml");
266 QTest::newRow(dataTag: "stopped") << testFileUrl(fileName: "stickmanstopped.qml");
267}
268
269void tst_qquickanimatedimage::remote_data()
270{
271 QTest::addColumn<QString>(name: "fileName");
272 QTest::addColumn<bool>(name: "paused");
273
274 QTest::newRow(dataTag: "playing") << "stickman.qml" << false;
275 QTest::newRow(dataTag: "paused") << "stickmanpause.qml" << true;
276}
277
278void tst_qquickanimatedimage::remote()
279{
280 QFETCH(QString, fileName);
281 QFETCH(bool, paused);
282
283 ThreadedTestHTTPServer server(dataDirectory());
284
285 QQmlEngine engine;
286 QQmlComponent component(&engine, server.url(documentPath: fileName));
287 QTRY_VERIFY(component.isReady());
288
289 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
290 QVERIFY(anim);
291
292 QTRY_VERIFY(anim->isPlaying());
293 if (paused) {
294 QTRY_VERIFY(anim->isPaused());
295 QCOMPARE(anim->currentFrame(), 2);
296 }
297 QVERIFY(anim->status() != QQuickAnimatedImage::Error);
298
299 delete anim;
300}
301
302void tst_qquickanimatedimage::sourceSize()
303{
304 QQmlEngine engine;
305 QQmlComponent component(&engine, testFileUrl(fileName: "stickmanscaled.qml"));
306 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
307 QVERIFY(anim);
308 QCOMPARE(anim->width(),240.0);
309 QCOMPARE(anim->height(),180.0);
310 QCOMPARE(anim->sourceSize(),QSize(160,120));
311
312 delete anim;
313}
314
315void tst_qquickanimatedimage::sourceSizeReadOnly()
316{
317 QQmlEngine engine;
318 QQmlComponent component(&engine, testFileUrl(fileName: "stickmanerror1.qml"));
319 QVERIFY(component.isError());
320 QCOMPARE(component.errors().at(0).description(), QString("Invalid property assignment: \"sourceSize\" is a read-only property"));
321}
322
323void tst_qquickanimatedimage::invalidSource()
324{
325 QQmlEngine engine;
326 QQmlComponent component(&engine);
327 component.setData("import QtQuick 2.0\n AnimatedImage { source: \"no-such-file.gif\" }", baseUrl: QUrl::fromLocalFile(localfile: "relative"));
328 QVERIFY(component.isReady());
329
330 QTest::ignoreMessage(type: QtWarningMsg, message: "file:relative:2:2: QML AnimatedImage: Error Reading Animated Image File file:no-such-file.gif");
331
332 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
333 QVERIFY(anim);
334
335 QVERIFY(anim->isPlaying());
336 QVERIFY(!anim->isPaused());
337 QCOMPARE(anim->currentFrame(), 0);
338 QCOMPARE(anim->frameCount(), 0);
339 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Error);
340
341 delete anim;
342}
343
344void tst_qquickanimatedimage::sourceSizeChanges()
345{
346 TestHTTPServer server;
347 QVERIFY2(server.listen(), qPrintable(server.errorString()));
348 server.serveDirectory(dataDirectory());
349
350 QQmlEngine engine;
351 QQmlComponent component(&engine);
352 component.setData("import QtQuick 2.0\nAnimatedImage { source: srcImage }", baseUrl: QUrl::fromLocalFile(localfile: ""));
353 QTRY_VERIFY(component.isReady());
354 QQmlContext *ctxt = engine.rootContext();
355 ctxt->setContextProperty("srcImage", "");
356 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage*>(object: component.create());
357 QVERIFY(anim != nullptr);
358
359 QSignalSpy sourceSizeSpy(anim, SIGNAL(sourceSizeChanged()));
360
361 // Local
362 ctxt->setContextProperty("srcImage", QUrl(""));
363 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Null);
364 QTRY_COMPARE(sourceSizeSpy.count(), 0);
365
366 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "hearts.gif"));
367 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
368 QTRY_COMPARE(sourceSizeSpy.count(), 1);
369
370 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "hearts.gif"));
371 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
372 QTRY_COMPARE(sourceSizeSpy.count(), 1);
373
374 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "hearts_copy.gif"));
375 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
376 QTRY_COMPARE(sourceSizeSpy.count(), 1);
377
378 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "colors.gif"));
379 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
380 QTRY_COMPARE(sourceSizeSpy.count(), 2);
381
382 ctxt->setContextProperty("srcImage", QUrl(""));
383 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Null);
384 QTRY_COMPARE(sourceSizeSpy.count(), 3);
385
386 // Remote
387 ctxt->setContextProperty("srcImage", server.url(documentPath: "/hearts.gif"));
388 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
389 QTRY_COMPARE(sourceSizeSpy.count(), 4);
390
391 ctxt->setContextProperty("srcImage", server.url(documentPath: "/hearts.gif"));
392 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
393 QTRY_COMPARE(sourceSizeSpy.count(), 4);
394
395 ctxt->setContextProperty("srcImage", server.url(documentPath: "/hearts_copy.gif"));
396 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
397 QTRY_COMPARE(sourceSizeSpy.count(), 4);
398
399 ctxt->setContextProperty("srcImage", server.url(documentPath: "/colors.gif"));
400 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
401 QTRY_COMPARE(sourceSizeSpy.count(), 5);
402
403 ctxt->setContextProperty("srcImage", QUrl(""));
404 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Null);
405 QTRY_COMPARE(sourceSizeSpy.count(), 6);
406
407 delete anim;
408}
409
410void tst_qquickanimatedimage::sourceSizeChanges_intermediate()
411{
412 QQmlEngine engine;
413 QQmlComponent component(&engine);
414 component.setData("import QtQuick 2.0\nAnimatedImage { readonly property int testWidth: status === AnimatedImage.Ready ? sourceSize.width : -1; source: srcImage }", baseUrl: QUrl::fromLocalFile(localfile: ""));
415 QTRY_VERIFY(component.isReady());
416 QQmlContext *ctxt = engine.rootContext();
417 ctxt->setContextProperty("srcImage", "");
418
419 QScopedPointer<QQuickAnimatedImage> anim(qobject_cast<QQuickAnimatedImage*>(object: component.create()));
420 QVERIFY(anim != nullptr);
421
422 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "hearts.gif"));
423 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
424 QTRY_COMPARE(anim->property("testWidth").toInt(), anim->sourceSize().width());
425
426 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "hearts_copy.gif"));
427 QTRY_COMPARE(anim->status(), QQuickAnimatedImage::Ready);
428 QTRY_COMPARE(anim->property("testWidth").toInt(), anim->sourceSize().width());
429}
430
431
432void tst_qquickanimatedimage::qtbug_16520()
433{
434 TestHTTPServer server;
435 QVERIFY2(server.listen(), qPrintable(server.errorString()));
436 server.serveDirectory(dataDirectory());
437
438 QQmlEngine engine;
439 QQmlComponent component(&engine, testFileUrl(fileName: "qtbug-16520.qml"));
440 QTRY_VERIFY(component.isReady());
441
442 QQuickRectangle *root = qobject_cast<QQuickRectangle *>(object: component.create());
443 QVERIFY(root);
444 QQuickAnimatedImage *anim = root->findChild<QQuickAnimatedImage*>(aName: "anim");
445 QVERIFY(anim != nullptr);
446
447 anim->setProperty(name: "source", value: server.urlString(documentPath: "/stickman.gif"));
448 QTRY_COMPARE(anim->opacity(), qreal(0));
449 QTRY_COMPARE(anim->opacity(), qreal(1));
450
451 delete anim;
452 delete root;
453}
454
455void tst_qquickanimatedimage::progressAndStatusChanges()
456{
457 TestHTTPServer server;
458 QVERIFY2(server.listen(), qPrintable(server.errorString()));
459 server.serveDirectory(dataDirectory());
460
461 QQmlEngine engine;
462 QString componentStr = "import QtQuick 2.0\nAnimatedImage { source: srcImage }";
463 QQmlContext *ctxt = engine.rootContext();
464 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "stickman.gif"));
465 QQmlComponent component(&engine);
466 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
467 QQuickImage *obj = qobject_cast<QQuickImage*>(object: component.create());
468 QVERIFY(obj != nullptr);
469 QCOMPARE(obj->status(), QQuickImage::Ready);
470 QTRY_COMPARE(obj->progress(), 1.0);
471
472 qRegisterMetaType<QQuickImageBase::Status>();
473 QSignalSpy sourceSpy(obj, SIGNAL(sourceChanged(QUrl)));
474 QSignalSpy progressSpy(obj, SIGNAL(progressChanged(qreal)));
475 QSignalSpy statusSpy(obj, SIGNAL(statusChanged(QQuickImageBase::Status)));
476
477 // Same image
478 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "stickman.gif"));
479 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
480 QTRY_COMPARE(obj->progress(), 1.0);
481 QTRY_COMPARE(sourceSpy.count(), 0);
482 QTRY_COMPARE(progressSpy.count(), 0);
483 QTRY_COMPARE(statusSpy.count(), 0);
484
485 // Loading local file
486 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "colors.gif"));
487 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
488 QTRY_COMPARE(obj->progress(), 1.0);
489 QTRY_COMPARE(sourceSpy.count(), 1);
490 QTRY_COMPARE(progressSpy.count(), 0);
491 QTRY_COMPARE(statusSpy.count(), 1);
492
493 // Loading remote file
494 ctxt->setContextProperty("srcImage", server.url(documentPath: "/stickman.gif"));
495 QTRY_COMPARE(obj->status(), QQuickImage::Loading);
496 QTRY_COMPARE(obj->progress(), 0.0);
497 QTRY_COMPARE(obj->status(), QQuickImage::Ready);
498 QTRY_COMPARE(obj->progress(), 1.0);
499 QTRY_COMPARE(sourceSpy.count(), 2);
500 QTRY_VERIFY(progressSpy.count() > 1);
501 QTRY_COMPARE(statusSpy.count(), 3);
502
503 ctxt->setContextProperty("srcImage", "");
504 QTRY_COMPARE(obj->status(), QQuickImage::Null);
505 QTRY_COMPARE(obj->progress(), 0.0);
506 QTRY_COMPARE(sourceSpy.count(), 3);
507 QTRY_VERIFY(progressSpy.count() > 2);
508 QTRY_COMPARE(statusSpy.count(), 4);
509
510 delete obj;
511}
512
513void tst_qquickanimatedimage::playingAndPausedChanges()
514{
515 QQmlEngine engine;
516 QString componentStr = "import QtQuick 2.0\nAnimatedImage { source: srcImage }";
517 QQmlContext *ctxt = engine.rootContext();
518 ctxt->setContextProperty("srcImage", QUrl(""));
519 QQmlComponent component(&engine);
520 component.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
521 QQuickAnimatedImage *obj = qobject_cast<QQuickAnimatedImage*>(object: component.create());
522 QVERIFY(obj != nullptr);
523 QCOMPARE(obj->status(), QQuickAnimatedImage::Null);
524 QTRY_VERIFY(obj->isPlaying());
525 QTRY_VERIFY(!obj->isPaused());
526 QSignalSpy playingSpy(obj, SIGNAL(playingChanged()));
527 QSignalSpy pausedSpy(obj, SIGNAL(pausedChanged()));
528
529 // initial state
530 obj->setProperty(name: "playing", value: true);
531 obj->setProperty(name: "paused", value: false);
532 QTRY_VERIFY(obj->isPlaying());
533 QTRY_VERIFY(!obj->isPaused());
534 QTRY_COMPARE(playingSpy.count(), 0);
535 QTRY_COMPARE(pausedSpy.count(), 0);
536
537 obj->setProperty(name: "playing", value: false);
538 obj->setProperty(name: "paused", value: true);
539 QTRY_VERIFY(!obj->isPlaying());
540 QTRY_VERIFY(obj->isPaused());
541 QTRY_COMPARE(playingSpy.count(), 1);
542 QTRY_COMPARE(pausedSpy.count(), 1);
543
544 obj->setProperty(name: "playing", value: true);
545 obj->setProperty(name: "paused", value: false);
546 QTRY_VERIFY(obj->isPlaying());
547 QTRY_VERIFY(!obj->isPaused());
548 QTRY_COMPARE(playingSpy.count(), 2);
549 QTRY_COMPARE(pausedSpy.count(), 2);
550
551 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "stickman.gif"));
552 QTRY_VERIFY(obj->isPlaying());
553 QTRY_VERIFY(!obj->isPaused());
554 QTRY_COMPARE(playingSpy.count(), 2);
555 QTRY_COMPARE(pausedSpy.count(), 2);
556
557 obj->setProperty(name: "paused", value: true);
558 QTRY_VERIFY(obj->isPlaying());
559 QTRY_VERIFY(obj->isPaused());
560 QTRY_COMPARE(playingSpy.count(), 2);
561 QTRY_COMPARE(pausedSpy.count(), 3);
562
563 obj->setProperty(name: "playing", value: false);
564 QTRY_VERIFY(!obj->isPlaying());
565 QTRY_VERIFY(!obj->isPaused());
566 QTRY_COMPARE(playingSpy.count(), 3);
567 QTRY_COMPARE(pausedSpy.count(), 4);
568
569 obj->setProperty(name: "playing", value: true);
570
571 // Cannot animate this image, playing will be false
572 ctxt->setContextProperty("srcImage", testFileUrl(fileName: "green.png"));
573 QTRY_VERIFY(!obj->isPlaying());
574 QTRY_VERIFY(!obj->isPaused());
575 QTRY_COMPARE(playingSpy.count(), 5);
576 QTRY_COMPARE(pausedSpy.count(), 4);
577
578 delete obj;
579}
580
581void tst_qquickanimatedimage::noCaching()
582{
583 QQuickView window, window_nocache;
584 window.setSource(testFileUrl(fileName: "colors.qml"));
585 window_nocache.setSource(testFileUrl(fileName: "colors_nocache.qml"));
586 window.show();
587 window_nocache.show();
588 QVERIFY(QTest::qWaitForWindowExposed(&window));
589 QVERIFY(QTest::qWaitForWindowExposed(&window_nocache));
590
591 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: window.rootObject());
592 QVERIFY(anim);
593
594 QQuickAnimatedImage *anim_nocache = qobject_cast<QQuickAnimatedImage *>(object: window_nocache.rootObject());
595 QVERIFY(anim_nocache);
596
597 QCOMPARE(anim->frameCount(), anim_nocache->frameCount());
598
599 // colors.gif only has 3 frames so this should be fast
600 for (int loops = 0; loops <= 2; ++loops) {
601 for (int frame = 0; frame < anim->frameCount(); ++frame) {
602 anim->setCurrentFrame(frame);
603 anim_nocache->setCurrentFrame(frame);
604
605 QImage image_cache = window.grabWindow();
606 QImage image_nocache = window_nocache.grabWindow();
607
608 QCOMPARE(image_cache, image_nocache);
609 }
610 }
611}
612
613void tst_qquickanimatedimage::sourceChangesOnFrameChanged()
614{
615 QQmlEngine engine;
616 QQmlComponent component(&engine, testFileUrl(fileName: "colors.qml"));
617 QVector<QQuickAnimatedImage*> images;
618
619 // Run multiple animations in parallel, this should be fast
620 for (int loops = 0; loops < 25; ++loops) {
621 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: component.create());
622
623 // QTBUG-67427: this should not produce a segfault
624 QObject::connect(sender: anim,
625 signal: &QQuickAnimatedImage::frameChanged,
626 slot: [this, anim]() { anim->setSource(testFileUrl(fileName: "hearts.gif")); });
627
628 QVERIFY(anim);
629 QVERIFY(anim->isPlaying());
630
631 images.append(t: anim);
632 }
633
634 for (auto *anim : images)
635 QTRY_COMPARE(anim->source(), testFileUrl("hearts.gif"));
636
637 qDeleteAll(c: images);
638}
639
640void tst_qquickanimatedimage::currentFrame()
641{
642 QQuickView window;
643 window.setSource(testFileUrl(fileName: "currentframe.qml"));
644 window.show();
645 QVERIFY(QTest::qWaitForWindowExposed(&window));
646
647 QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(object: window.rootObject());
648 QVERIFY(anim);
649 QSignalSpy frameChangedSpy(anim, SIGNAL(frameChanged()));
650 QSignalSpy currentFrameChangedSpy(anim, SIGNAL(currentFrameChanged()));
651
652 anim->setCurrentFrame(1);
653 QCOMPARE(anim->currentFrame(), 1);
654 QCOMPARE(frameChangedSpy.count(), 1);
655 QCOMPARE(currentFrameChangedSpy.count(), 1);
656 QCOMPARE(anim->property("currentFrameChangeCount"), 1);
657 QCOMPARE(anim->property("frameChangeCount"), 1);
658
659 evaluate<void>(scope: anim, expression: "scriptedSetCurrentFrame(2)");
660 QCOMPARE(anim->currentFrame(), 2);
661 QCOMPARE(frameChangedSpy.count(), 2);
662 QCOMPARE(currentFrameChangedSpy.count(), 2);
663 QCOMPARE(anim->property("currentFrameChangeCount"), 2);
664 QCOMPARE(anim->property("frameChangeCount"), 2);
665}
666
667QTEST_MAIN(tst_qquickanimatedimage)
668
669#include "tst_qquickanimatedimage.moc"
670

source code of qtdeclarative/tests/auto/quick/qquickanimatedimage/tst_qquickanimatedimage.cpp