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 <QtTest/QSignalSpy> |
29 | #include <qtest.h> |
30 | #include <QtQml/qqmlengine.h> |
31 | #include <QtQml/qqmlcomponent.h> |
32 | #include <QtQml/private/qqmltimer_p.h> |
33 | #include <QtQuick/qquickitem.h> |
34 | #include <QDebug> |
35 | #include <QtCore/QPauseAnimation> |
36 | #include <private/qabstractanimation_p.h> |
37 | |
38 | void consistentWait(int ms) |
39 | { |
40 | //Use animations for timing, because we enabled consistentTiming |
41 | //This function will qWait for >= ms worth of consistent timing to elapse |
42 | QPauseAnimation waitTimer(ms); |
43 | waitTimer.start(); |
44 | while (waitTimer.state() == QAbstractAnimation::Running) |
45 | QTest::qWait(ms: 20); |
46 | } |
47 | |
48 | void eventLoopWait(int ms) |
49 | { |
50 | // QTest::qWait() always calls sendPostedEvents before exiting, so we can't use it to stop |
51 | // between an event is posted and it is received; But we can use an event loop instead |
52 | |
53 | QPauseAnimation waitTimer(ms); |
54 | waitTimer.start(); |
55 | while (waitTimer.state() == QAbstractAnimation::Running) |
56 | { |
57 | QTimer timer; |
58 | QEventLoop eventLoop; |
59 | timer.start(msec: 0); |
60 | timer.connect(sender: &timer, signal: &QTimer::timeout, receiver: &eventLoop, slot: &QEventLoop::quit); |
61 | eventLoop.exec(); |
62 | } |
63 | } |
64 | |
65 | class tst_qqmltimer : public QObject |
66 | { |
67 | Q_OBJECT |
68 | public: |
69 | tst_qqmltimer(); |
70 | |
71 | private slots: |
72 | void initTestCase(); |
73 | void notRepeating(); |
74 | void notRepeatingStart(); |
75 | void repeat(); |
76 | void noTriggerIfNotRunning(); |
77 | void triggeredOnStart(); |
78 | void triggeredOnStartRepeat(); |
79 | void changeDuration(); |
80 | void restart(); |
81 | void restartFromTriggered(); |
82 | void runningFromTriggered(); |
83 | void parentProperty(); |
84 | void stopWhenEventPosted(); |
85 | void restartWhenEventPosted(); |
86 | }; |
87 | |
88 | class TimerHelper : public QObject |
89 | { |
90 | Q_OBJECT |
91 | public: |
92 | TimerHelper() { } |
93 | |
94 | int count = 0; |
95 | |
96 | public slots: |
97 | void timeout() { |
98 | ++count; |
99 | } |
100 | }; |
101 | |
102 | tst_qqmltimer::tst_qqmltimer() { } |
103 | |
104 | void tst_qqmltimer::initTestCase() |
105 | { |
106 | QUnifiedTimer::instance()->setConsistentTiming(true); |
107 | } |
108 | |
109 | void tst_qqmltimer::notRepeating() |
110 | { |
111 | QQmlEngine engine; |
112 | QQmlComponent component(&engine); |
113 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 100; running: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
114 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
115 | QVERIFY(timer != nullptr); |
116 | QVERIFY(timer->isRunning()); |
117 | QVERIFY(!timer->isRepeating()); |
118 | QCOMPARE(timer->interval(), 100); |
119 | |
120 | TimerHelper helper; |
121 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
122 | |
123 | |
124 | consistentWait(ms: 200); |
125 | QCOMPARE(helper.count, 1); |
126 | consistentWait(ms: 200); |
127 | QCOMPARE(helper.count, 1); |
128 | QVERIFY(!timer->isRunning()); |
129 | } |
130 | |
131 | void tst_qqmltimer::notRepeatingStart() |
132 | { |
133 | QQmlEngine engine; |
134 | QQmlComponent component(&engine); |
135 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 100 }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
136 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
137 | QVERIFY(timer != nullptr); |
138 | QVERIFY(!timer->isRunning()); |
139 | |
140 | TimerHelper helper; |
141 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
142 | |
143 | consistentWait(ms: 200); |
144 | QCOMPARE(helper.count, 0); |
145 | |
146 | timer->start(); |
147 | consistentWait(ms: 200); |
148 | QCOMPARE(helper.count, 1); |
149 | consistentWait(ms: 200); |
150 | QCOMPARE(helper.count, 1); |
151 | QVERIFY(!timer->isRunning()); |
152 | |
153 | delete timer; |
154 | } |
155 | |
156 | void tst_qqmltimer::repeat() |
157 | { |
158 | QQmlEngine engine; |
159 | QQmlComponent component(&engine); |
160 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 100; repeat: true; running: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
161 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
162 | QVERIFY(timer != nullptr); |
163 | |
164 | TimerHelper helper; |
165 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
166 | QCOMPARE(helper.count, 0); |
167 | |
168 | consistentWait(ms: 200); |
169 | QVERIFY(helper.count > 0); |
170 | int oldCount = helper.count; |
171 | |
172 | consistentWait(ms: 200); |
173 | QVERIFY(helper.count > oldCount); |
174 | QVERIFY(timer->isRunning()); |
175 | |
176 | oldCount = helper.count; |
177 | timer->stop(); |
178 | |
179 | consistentWait(ms: 200); |
180 | QCOMPARE(helper.count, oldCount); |
181 | QVERIFY(!timer->isRunning()); |
182 | |
183 | QSignalSpy spy(timer, SIGNAL(repeatChanged())); |
184 | |
185 | timer->setRepeating(false); |
186 | QVERIFY(!timer->isRepeating()); |
187 | QCOMPARE(spy.count(),1); |
188 | |
189 | timer->setRepeating(false); |
190 | QCOMPARE(spy.count(),1); |
191 | |
192 | timer->setRepeating(true); |
193 | QCOMPARE(spy.count(),2); |
194 | |
195 | delete timer; |
196 | } |
197 | |
198 | void tst_qqmltimer::triggeredOnStart() |
199 | { |
200 | QQmlEngine engine; |
201 | QQmlComponent component(&engine); |
202 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 100; running: true; triggeredOnStart: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
203 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
204 | QVERIFY(timer != nullptr); |
205 | QVERIFY(timer->triggeredOnStart()); |
206 | |
207 | TimerHelper helper; |
208 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
209 | consistentWait(ms: 1); |
210 | QCOMPARE(helper.count, 1); |
211 | consistentWait(ms: 200); |
212 | QCOMPARE(helper.count, 2); |
213 | consistentWait(ms: 200); |
214 | QCOMPARE(helper.count, 2); |
215 | QVERIFY(!timer->isRunning()); |
216 | |
217 | QSignalSpy spy(timer, SIGNAL(triggeredOnStartChanged())); |
218 | |
219 | timer->setTriggeredOnStart(false); |
220 | QVERIFY(!timer->triggeredOnStart()); |
221 | QCOMPARE(spy.count(),1); |
222 | |
223 | timer->setTriggeredOnStart(false); |
224 | QCOMPARE(spy.count(),1); |
225 | |
226 | timer->setTriggeredOnStart(true); |
227 | QCOMPARE(spy.count(),2); |
228 | |
229 | delete timer; |
230 | } |
231 | |
232 | void tst_qqmltimer::triggeredOnStartRepeat() |
233 | { |
234 | QQmlEngine engine; |
235 | QQmlComponent component(&engine); |
236 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 100; running: true; triggeredOnStart: true; repeat: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
237 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
238 | QVERIFY(timer != nullptr); |
239 | |
240 | TimerHelper helper; |
241 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
242 | consistentWait(ms: 1); |
243 | QCOMPARE(helper.count, 1); |
244 | |
245 | consistentWait(ms: 200); |
246 | QVERIFY(helper.count > 1); |
247 | int oldCount = helper.count; |
248 | consistentWait(ms: 200); |
249 | QVERIFY(helper.count > oldCount); |
250 | QVERIFY(timer->isRunning()); |
251 | |
252 | delete timer; |
253 | } |
254 | |
255 | void tst_qqmltimer::noTriggerIfNotRunning() |
256 | { |
257 | QQmlEngine engine; |
258 | QQmlComponent component(&engine); |
259 | component.setData(QByteArray( |
260 | "import QtQml 2.0\n" |
261 | "QtObject { property bool ok: true\n" |
262 | "property Timer timer1: Timer { id: t1; interval: 100; repeat: true; running: true; onTriggered: if (!running) ok=false }" |
263 | "property Timer timer2: Timer { interval: 10; running: true; onTriggered: t1.running=false }" |
264 | "}" |
265 | ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
266 | QObject *item = component.create(); |
267 | QVERIFY(item != nullptr); |
268 | consistentWait(ms: 200); |
269 | QCOMPARE(item->property("ok" ).toBool(), true); |
270 | |
271 | delete item; |
272 | } |
273 | |
274 | void tst_qqmltimer::changeDuration() |
275 | { |
276 | QQmlEngine engine; |
277 | QQmlComponent component(&engine); |
278 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 200; repeat: true; running: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
279 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
280 | QVERIFY(timer != nullptr); |
281 | |
282 | TimerHelper helper; |
283 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
284 | QCOMPARE(helper.count, 0); |
285 | |
286 | consistentWait(ms: 500); |
287 | QCOMPARE(helper.count, 2); |
288 | |
289 | timer->setInterval(500); |
290 | |
291 | consistentWait(ms: 600); |
292 | QCOMPARE(helper.count, 3); |
293 | QVERIFY(timer->isRunning()); |
294 | |
295 | QSignalSpy spy(timer, SIGNAL(intervalChanged())); |
296 | |
297 | timer->setInterval(200); |
298 | QCOMPARE(timer->interval(), 200); |
299 | QCOMPARE(spy.count(),1); |
300 | |
301 | timer->setInterval(200); |
302 | QCOMPARE(spy.count(),1); |
303 | |
304 | timer->setInterval(300); |
305 | QCOMPARE(spy.count(),2); |
306 | |
307 | delete timer; |
308 | } |
309 | |
310 | void tst_qqmltimer::restart() |
311 | { |
312 | QQmlEngine engine; |
313 | QQmlComponent component(&engine); |
314 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 500; repeat: true; running: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
315 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
316 | QVERIFY(timer != nullptr); |
317 | |
318 | TimerHelper helper; |
319 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
320 | QCOMPARE(helper.count, 0); |
321 | |
322 | consistentWait(ms: 600); |
323 | QCOMPARE(helper.count, 1); |
324 | |
325 | consistentWait(ms: 300); |
326 | |
327 | timer->restart(); |
328 | |
329 | consistentWait(ms: 700); |
330 | |
331 | QCOMPARE(helper.count, 2); |
332 | QVERIFY(timer->isRunning()); |
333 | |
334 | delete timer; |
335 | } |
336 | |
337 | void tst_qqmltimer::restartFromTriggered() |
338 | { |
339 | QQmlEngine engine; |
340 | QQmlComponent component(&engine); |
341 | component.setData(QByteArray("import QtQml 2.0\nTimer { " |
342 | "interval: 500; " |
343 | "repeat: false; " |
344 | "running: true; " |
345 | "onTriggered: restart()" |
346 | " }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
347 | QScopedPointer<QObject> object(component.create()); |
348 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: object.data()); |
349 | QVERIFY(timer != nullptr); |
350 | |
351 | TimerHelper helper; |
352 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
353 | QCOMPARE(helper.count, 0); |
354 | |
355 | consistentWait(ms: 600); |
356 | QCOMPARE(helper.count, 1); |
357 | QVERIFY(timer->isRunning()); |
358 | |
359 | consistentWait(ms: 600); |
360 | QCOMPARE(helper.count, 2); |
361 | QVERIFY(timer->isRunning()); |
362 | } |
363 | |
364 | void tst_qqmltimer::runningFromTriggered() |
365 | { |
366 | QQmlEngine engine; |
367 | QQmlComponent component(&engine); |
368 | component.setData(QByteArray("import QtQml 2.0\nTimer { " |
369 | "property bool ok: false; " |
370 | "interval: 500; " |
371 | "repeat: false; " |
372 | "running: true; " |
373 | "onTriggered: { ok = !running; running = true }" |
374 | " }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
375 | QScopedPointer<QObject> object(component.create()); |
376 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: object.data()); |
377 | QVERIFY(timer != nullptr); |
378 | |
379 | TimerHelper helper; |
380 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
381 | QCOMPARE(helper.count, 0); |
382 | |
383 | consistentWait(ms: 600); |
384 | QCOMPARE(helper.count, 1); |
385 | QVERIFY(timer->property("ok" ).toBool()); |
386 | QVERIFY(timer->isRunning()); |
387 | |
388 | consistentWait(ms: 600); |
389 | QCOMPARE(helper.count, 2); |
390 | QVERIFY(timer->property("ok" ).toBool()); |
391 | QVERIFY(timer->isRunning()); |
392 | } |
393 | |
394 | void tst_qqmltimer::parentProperty() |
395 | { |
396 | QQmlEngine engine; |
397 | QQmlComponent component(&engine); |
398 | component.setData(QByteArray("import QtQuick 2.0\nItem { Timer { objectName: \"timer\"; running: parent.visible } }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
399 | QQuickItem *item = qobject_cast<QQuickItem*>(object: component.create()); |
400 | QVERIFY(item != nullptr); |
401 | QQmlTimer *timer = item->findChild<QQmlTimer*>(aName: "timer" ); |
402 | QVERIFY(timer != nullptr); |
403 | |
404 | QVERIFY(timer->isRunning()); |
405 | |
406 | delete timer; |
407 | } |
408 | |
409 | void tst_qqmltimer::stopWhenEventPosted() |
410 | { |
411 | QQmlEngine engine; |
412 | QQmlComponent component(&engine); |
413 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 200; running: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
414 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
415 | |
416 | TimerHelper helper; |
417 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
418 | QCOMPARE(helper.count, 0); |
419 | |
420 | eventLoopWait(ms: 200); |
421 | QCOMPARE(helper.count, 0); |
422 | QVERIFY(timer->isRunning()); |
423 | timer->stop(); |
424 | QVERIFY(!timer->isRunning()); |
425 | |
426 | consistentWait(ms: 300); |
427 | QCOMPARE(helper.count, 0); |
428 | } |
429 | |
430 | |
431 | void tst_qqmltimer::restartWhenEventPosted() |
432 | { |
433 | QQmlEngine engine; |
434 | QQmlComponent component(&engine); |
435 | component.setData(QByteArray("import QtQml 2.0\nTimer { interval: 200; running: true }" ), baseUrl: QUrl::fromLocalFile(localfile: "" )); |
436 | QQmlTimer *timer = qobject_cast<QQmlTimer*>(object: component.create()); |
437 | |
438 | TimerHelper helper; |
439 | connect(sender: timer, SIGNAL(triggered()), receiver: &helper, SLOT(timeout())); |
440 | QCOMPARE(helper.count, 0); |
441 | |
442 | eventLoopWait(ms: 200); |
443 | QCOMPARE(helper.count, 0); |
444 | timer->restart(); |
445 | |
446 | consistentWait(ms: 100); |
447 | QCOMPARE(helper.count, 0); |
448 | QVERIFY(timer->isRunning()); |
449 | |
450 | consistentWait(ms: 200); |
451 | QCOMPARE(helper.count, 1); |
452 | } |
453 | |
454 | QTEST_MAIN(tst_qqmltimer) |
455 | |
456 | #include "tst_qqmltimer.moc" |
457 | |