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
30#include <QSignalSpy>
31
32#include <QtQml/QQmlContext>
33#include <QtQml/qqmlengine.h>
34#include <QtQml/qqmlcomponent.h>
35#include <QtQml/qqmlincubator.h>
36#include <QtQuick/qquickview.h>
37#include <private/qquickloader_p.h>
38#include <private/qquickwindowmodule_p.h>
39#include "testhttpserver.h"
40#include "../../shared/util.h"
41#include "../shared/geometrytestutil.h"
42#include <QQmlApplicationEngine>
43
44Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests")
45
46class SlowComponent : public QQmlComponent
47{
48 Q_OBJECT
49public:
50 SlowComponent() {
51 QTest::qSleep(ms: 500);
52 }
53};
54
55class PeriodicIncubationController : public QObject,
56 public QQmlIncubationController
57{
58public:
59 PeriodicIncubationController() {}
60
61 void start() { startTimer(interval: 20); }
62
63 bool incubated = false;
64
65protected:
66 virtual void timerEvent(QTimerEvent *) {
67 incubateFor(msecs: 15);
68 }
69
70 virtual void incubatingObjectCountChanged(int count) {
71 if (count)
72 incubated = true;
73 }
74};
75
76class tst_QQuickLoader : public QQmlDataTest
77
78{
79 Q_OBJECT
80public:
81 tst_QQuickLoader();
82
83private slots:
84 void sourceOrComponent();
85 void sourceOrComponent_data();
86 void clear();
87 void urlToComponent();
88 void componentToUrl();
89 void anchoredLoader();
90 void sizeLoaderToItem();
91 void sizeItemToLoader();
92 void noResize();
93 void networkRequestUrl();
94 void failNetworkRequest();
95 void networkComponent();
96 void active();
97 void initialPropertyValues_data();
98 void initialPropertyValues();
99 void initialPropertyValuesBinding();
100 void initialPropertyValuesError_data();
101 void initialPropertyValuesError();
102
103 void deleteComponentCrash();
104 void nonItem();
105 void vmeErrors();
106 void creationContext();
107 void QTBUG_16928();
108 void implicitSize();
109 void QTBUG_17114();
110 void asynchronous_data();
111 void asynchronous();
112 void asynchronous_clear();
113 void simultaneousSyncAsync();
114 void asyncToSync1();
115 void asyncToSync2();
116 void loadedSignal();
117
118 void parented();
119 void sizeBound();
120 void QTBUG_30183();
121 void transientWindow();
122 void nestedTransientWindow();
123
124 void sourceComponentGarbageCollection();
125
126 void bindings();
127 void parentErrors();
128
129 void rootContext();
130 void sourceURLKeepComponent();
131
132 void statusChangeOnlyEmittedOnce();
133
134 void setSourceAndCheckStatus();
135 void asyncLoaderRace();
136};
137
138Q_DECLARE_METATYPE(QList<QQmlError>)
139
140tst_QQuickLoader::tst_QQuickLoader()
141{
142 qmlRegisterType<SlowComponent>(uri: "LoaderTest", versionMajor: 1, versionMinor: 0, qmlName: "SlowComponent");
143 qRegisterMetaType<QList<QQmlError>>();
144}
145
146void tst_QQuickLoader::sourceOrComponent()
147{
148 QFETCH(QString, sourceOrComponent);
149 QFETCH(QString, sourceDefinition);
150 QFETCH(QUrl, sourceUrl);
151 QFETCH(QString, errorString);
152
153 bool error = !errorString.isEmpty();
154 if (error)
155 QTest::ignoreMessage(type: QtWarningMsg, message: errorString.toUtf8().constData());
156
157 QQmlEngine engine;
158 QQmlComponent component(&engine);
159 component.setData(QByteArray(
160 "import QtQuick 2.0\n"
161 "Loader {\n"
162 " property int onItemChangedCount: 0\n"
163 " property int onSourceChangedCount: 0\n"
164 " property int onSourceComponentChangedCount: 0\n"
165 " property int onStatusChangedCount: 0\n"
166 " property int onProgressChangedCount: 0\n"
167 " property int onLoadedCount: 0\n")
168 + sourceDefinition.toUtf8()
169 + QByteArray(
170 " onItemChanged: onItemChangedCount += 1\n"
171 " onSourceChanged: onSourceChangedCount += 1\n"
172 " onSourceComponentChanged: onSourceComponentChangedCount += 1\n"
173 " onStatusChanged: onStatusChangedCount += 1\n"
174 " onProgressChanged: onProgressChangedCount += 1\n"
175 " onLoaded: onLoadedCount += 1\n"
176 "}")
177 , baseUrl: dataDirectoryUrl());
178
179 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
180 QVERIFY(loader != nullptr);
181 QCOMPARE(loader->item() == nullptr, error);
182 QCOMPARE(loader->source(), sourceUrl);
183 QCOMPARE(loader->progress(), 1.0);
184
185 QCOMPARE(loader->status(), error ? QQuickLoader::Error : QQuickLoader::Ready);
186 QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), error ? 0: 1);
187
188 if (!error) {
189 bool sourceComponentIsChildOfLoader = false;
190 for (int ii = 0; ii < loader->children().size(); ++ii) {
191 QQmlComponent *c = qobject_cast<QQmlComponent*>(object: loader->children().at(i: ii));
192 if (c && c == loader->sourceComponent()) {
193 sourceComponentIsChildOfLoader = true;
194 }
195 }
196 QVERIFY(sourceComponentIsChildOfLoader);
197 }
198
199 if (sourceOrComponent == "component") {
200 QCOMPARE(loader->property("onSourceComponentChangedCount").toInt(), 1);
201 QCOMPARE(loader->property("onSourceChangedCount").toInt(), 0);
202 } else {
203 QCOMPARE(loader->property("onSourceComponentChangedCount").toInt(), 0);
204 QCOMPARE(loader->property("onSourceChangedCount").toInt(), 1);
205 }
206 QCOMPARE(loader->property("onStatusChangedCount").toInt(), 1);
207 QCOMPARE(loader->property("onProgressChangedCount").toInt(), 1);
208
209 QCOMPARE(loader->property("onItemChangedCount").toInt(), 1);
210 QCOMPARE(loader->property("onLoadedCount").toInt(), error ? 0 : 1);
211}
212
213void tst_QQuickLoader::sourceOrComponent_data()
214{
215 QTest::addColumn<QString>(name: "sourceOrComponent");
216 QTest::addColumn<QString>(name: "sourceDefinition");
217 QTest::addColumn<QUrl>(name: "sourceUrl");
218 QTest::addColumn<QString>(name: "errorString");
219
220 QTest::newRow(dataTag: "source") << "source" << "source: 'Rect120x60.qml'\n" << testFileUrl(fileName: "Rect120x60.qml") << "";
221 QTest::newRow(dataTag: "source with subdir") << "source" << "source: 'subdir/Test.qml'\n" << testFileUrl(fileName: "subdir/Test.qml") << "";
222 QTest::newRow(dataTag: "source with encoded subdir literal") << "source" << "source: 'subdir%2fTest.qml'\n" << testFileUrl(fileName: "subdir/Test.qml") << "";
223 QTest::newRow(dataTag: "source with encoded subdir optimized binding") << "source" << "source: 'subdir' + '%2fTest.qml'\n" << testFileUrl(fileName: "subdir/Test.qml") << "";
224 QTest::newRow(dataTag: "source with encoded subdir binding") << "source" << "source: encodeURIComponent('subdir/Test.qml')\n" << testFileUrl(fileName: "subdir/Test.qml") << "";
225 QTest::newRow(dataTag: "sourceComponent") << "component" << "Component { id: comp; Rectangle { width: 100; height: 50 } }\n sourceComponent: comp\n" << QUrl() << "";
226 QTest::newRow(dataTag: "invalid source") << "source" << "source: 'IDontExist.qml'\n" << testFileUrl(fileName: "IDontExist.qml")
227 << QString(testFileUrl(fileName: "IDontExist.qml").toString() + ": No such file or directory");
228}
229
230void tst_QQuickLoader::clear()
231{
232 QQmlEngine engine;
233
234 {
235 QQmlComponent component(&engine);
236 component.setData(QByteArray(
237 "import QtQuick 2.0\n"
238 " Loader { id: loader\n"
239 " source: 'Rect120x60.qml'\n"
240 " Timer { interval: 200; running: true; onTriggered: loader.source = '' }\n"
241 " }")
242 , baseUrl: dataDirectoryUrl());
243 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
244 QVERIFY(loader != nullptr);
245 QVERIFY(loader->item());
246 QCOMPARE(loader->progress(), 1.0);
247 QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1);
248
249 QTRY_VERIFY(!loader->item());
250 QCOMPARE(loader->progress(), 0.0);
251 QCOMPARE(loader->status(), QQuickLoader::Null);
252 QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 0);
253 }
254 {
255 QQmlComponent component(&engine, testFileUrl(fileName: "/SetSourceComponent.qml"));
256 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
257 QVERIFY(item);
258
259 QQuickLoader *loader = qobject_cast<QQuickLoader*>(object: item->QQuickItem::childItems().at(i: 0));
260 QVERIFY(loader);
261 QVERIFY(loader->item());
262 QCOMPARE(loader->progress(), 1.0);
263 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
264
265 loader->setSourceComponent(nullptr);
266
267 QVERIFY(!loader->item());
268 QCOMPARE(loader->progress(), 0.0);
269 QCOMPARE(loader->status(), QQuickLoader::Null);
270 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0);
271 }
272 {
273 QQmlComponent component(&engine, testFileUrl(fileName: "/SetSourceComponent.qml"));
274 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
275 QVERIFY(item);
276
277 QQuickLoader *loader = qobject_cast<QQuickLoader*>(object: item->QQuickItem::childItems().at(i: 0));
278 QVERIFY(loader);
279 QVERIFY(loader->item());
280 QCOMPARE(loader->progress(), 1.0);
281 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
282
283 QMetaObject::invokeMethod(obj: item.data(), member: "clear");
284
285 QVERIFY(!loader->item());
286 QCOMPARE(loader->progress(), 0.0);
287 QCOMPARE(loader->status(), QQuickLoader::Null);
288 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0);
289 }
290}
291
292void tst_QQuickLoader::urlToComponent()
293{
294 QQmlEngine engine;
295 QQmlComponent component(&engine);
296 component.setData(QByteArray("import QtQuick 2.0\n"
297 "Loader {\n"
298 " id: loader\n"
299 " Component { id: myComp; Rectangle { width: 10; height: 10 } }\n"
300 " source: \"Rect120x60.qml\"\n"
301 " Timer { interval: 100; running: true; onTriggered: loader.sourceComponent = myComp }\n"
302 "}" )
303 , baseUrl: dataDirectoryUrl());
304 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
305 QTest::qWait(ms: 200);
306 QTRY_VERIFY(loader != nullptr);
307 QVERIFY(loader->item());
308 QCOMPARE(loader->progress(), 1.0);
309 QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1);
310 QCOMPARE(loader->width(), 10.0);
311 QCOMPARE(loader->height(), 10.0);
312}
313
314void tst_QQuickLoader::componentToUrl()
315{
316 QQmlEngine engine;
317 QQmlComponent component(&engine, testFileUrl(fileName: "/SetSourceComponent.qml"));
318 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
319 QVERIFY(item);
320
321 QQuickLoader *loader = qobject_cast<QQuickLoader*>(object: item->QQuickItem::childItems().at(i: 0));
322 QVERIFY(loader);
323 QVERIFY(loader->item());
324 QCOMPARE(loader->progress(), 1.0);
325 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
326
327 loader->setSource(testFileUrl(fileName: "/Rect120x60.qml"));
328 QVERIFY(loader->item());
329 QCOMPARE(loader->progress(), 1.0);
330 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
331 QCOMPARE(loader->width(), 120.0);
332 QCOMPARE(loader->height(), 60.0);
333}
334
335void tst_QQuickLoader::anchoredLoader()
336{
337 QQmlEngine engine;
338 QQmlComponent component(&engine, testFileUrl(fileName: "/AnchoredLoader.qml"));
339 QScopedPointer<QQuickItem> rootItem(qobject_cast<QQuickItem*>(object: component.create()));
340 QVERIFY(rootItem != nullptr);
341 QQuickItem *loader = rootItem->findChild<QQuickItem*>(aName: "loader");
342 QQuickItem *sourceElement = rootItem->findChild<QQuickItem*>(aName: "sourceElement");
343
344 QVERIFY(loader != nullptr);
345 QVERIFY(sourceElement != nullptr);
346
347 QCOMPARE(rootItem->width(), 300.0);
348 QCOMPARE(rootItem->height(), 200.0);
349
350 QCOMPARE(loader->width(), 300.0);
351 QCOMPARE(loader->height(), 200.0);
352
353 QCOMPARE(sourceElement->width(), 300.0);
354 QCOMPARE(sourceElement->height(), 200.0);
355}
356
357void tst_QQuickLoader::sizeLoaderToItem()
358{
359 QQmlEngine engine;
360 QQmlComponent component(&engine, testFileUrl(fileName: "/SizeToItem.qml"));
361 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
362 QVERIFY(loader != nullptr);
363 QCOMPARE(loader->width(), 120.0);
364 QCOMPARE(loader->height(), 60.0);
365
366 // Check resize
367 QQuickItem *rect = qobject_cast<QQuickItem*>(object: loader->item());
368 QVERIFY(rect);
369 rect->setWidth(150);
370 rect->setHeight(45);
371 QCOMPARE(loader->width(), 150.0);
372 QCOMPARE(loader->height(), 45.0);
373
374 // Check explicit width
375 loader->setWidth(200.0);
376 QCOMPARE(loader->width(), 200.0);
377 QCOMPARE(rect->width(), 200.0);
378 rect->setWidth(100.0); // when rect changes ...
379 QCOMPARE(rect->width(), 100.0); // ... it changes
380 QCOMPARE(loader->width(), 200.0); // ... but loader stays the same
381
382 // Check explicit height
383 loader->setHeight(200.0);
384 QCOMPARE(loader->height(), 200.0);
385 QCOMPARE(rect->height(), 200.0);
386 rect->setHeight(100.0); // when rect changes ...
387 QCOMPARE(rect->height(), 100.0); // ... it changes
388 QCOMPARE(loader->height(), 200.0); // ... but loader stays the same
389
390 // Switch mode
391 loader->setWidth(180);
392 loader->setHeight(30);
393 QCOMPARE(rect->width(), 180.0);
394 QCOMPARE(rect->height(), 30.0);
395}
396
397void tst_QQuickLoader::sizeItemToLoader()
398{
399 QQmlEngine engine;
400 QQmlComponent component(&engine, testFileUrl(fileName: "/SizeToLoader.qml"));
401 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
402 QVERIFY(loader != nullptr);
403 QCOMPARE(loader->width(), 200.0);
404 QCOMPARE(loader->height(), 80.0);
405
406 QQuickItem *rect = qobject_cast<QQuickItem*>(object: loader->item());
407 QVERIFY(rect);
408 QCOMPARE(rect->width(), 200.0);
409 QCOMPARE(rect->height(), 80.0);
410
411 // Check resize
412 QSizeChangeListener sizeListener(rect);
413 const QSizeF size(180, 30);
414 loader->setSize(size);
415 QVERIFY2(!sizeListener.isEmpty(), "There should be at least one signal about the size changed");
416 for (const QSizeF sizeOnGeometryChanged : sizeListener) {
417 // Check that we have the correct size on all signals
418 QCOMPARE(sizeOnGeometryChanged, size);
419 }
420 QCOMPARE(rect->width(), size.width());
421 QCOMPARE(rect->height(), size.height());
422
423 // Switch mode
424 loader->resetWidth(); // reset explicit size
425 loader->resetHeight();
426 rect->setWidth(160);
427 rect->setHeight(45);
428 QCOMPARE(loader->width(), 160.0);
429 QCOMPARE(loader->height(), 45.0);
430}
431
432void tst_QQuickLoader::noResize()
433{
434 QQmlEngine engine;
435 QQmlComponent component(&engine, testFileUrl(fileName: "/NoResize.qml"));
436 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
437 QVERIFY(item != nullptr);
438 QCOMPARE(item->width(), 200.0);
439 QCOMPARE(item->height(), 80.0);
440}
441
442void tst_QQuickLoader::networkRequestUrl()
443{
444 ThreadedTestHTTPServer server(dataDirectory());
445
446 QQmlEngine engine;
447 QQmlComponent component(&engine);
448 const QString qml = "import QtQuick 2.0\nLoader { property int signalCount : 0; source: \"" + server.baseUrl().toString() + "/Rect120x60.qml\"; onLoaded: signalCount += 1 }";
449 component.setData(qml.toUtf8(), baseUrl: testFileUrl(fileName: "../dummy.qml"));
450 if (component.isError())
451 qDebug() << component.errors();
452 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
453 QVERIFY(loader != nullptr);
454
455 QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
456
457 QVERIFY(loader->item());
458 QCOMPARE(loader->progress(), 1.0);
459 QCOMPARE(loader->property("signalCount").toInt(), 1);
460 QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1);
461}
462
463/* XXX Component waits until all dependencies are loaded. Is this actually possible? */
464void tst_QQuickLoader::networkComponent()
465{
466 ThreadedTestHTTPServer server(dataDirectory(), TestHTTPServer::Delay);
467
468 QQmlEngine engine;
469 QQmlComponent component(&engine);
470 const QString qml = "import QtQuick 2.0\n"
471 "import \"" + server.baseUrl().toString() + "/\" as NW\n"
472 "Item {\n"
473 " Component { id: comp; NW.Rect120x60 {} }\n"
474 " Loader { sourceComponent: comp } }";
475 component.setData(qml.toUtf8(), baseUrl: dataDirectory());
476 // The component may be loaded synchronously or asynchronously, so we cannot test for
477 // status == Loading here. Also, it makes no sense to instruct the server to send here
478 // because in the synchronous case we're already done loading.
479 QTRY_COMPARE(component.status(), QQmlComponent::Ready);
480
481 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
482 QVERIFY(item);
483
484 QQuickLoader *loader = qobject_cast<QQuickLoader*>(object: item->children().at(i: 1));
485 QVERIFY(loader);
486 QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
487
488 QVERIFY(loader->item());
489 QCOMPARE(loader->progress(), 1.0);
490 QCOMPARE(loader->status(), QQuickLoader::Ready);
491 QCOMPARE(static_cast<QQuickItem*>(loader)->children().count(), 1);
492
493}
494
495void tst_QQuickLoader::failNetworkRequest()
496{
497 ThreadedTestHTTPServer server(dataDirectory());
498
499 QTest::ignoreMessage(type: QtWarningMsg, message: QString(server.baseUrl().toString() + "/IDontExist.qml: File not found").toUtf8());
500
501 QQmlEngine engine;
502 QQmlComponent component(&engine);
503 const QString qml = "import QtQuick 2.0\nLoader { property int did_load: 123; source: \"" + server.baseUrl().toString() + "/IDontExist.qml\"; onLoaded: did_load=456 }";
504 component.setData(qml.toUtf8(), baseUrl: server.url(documentPath: "/dummy.qml"));
505 QTRY_COMPARE(component.status(), QQmlComponent::Ready);
506 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
507 QVERIFY(loader != nullptr);
508
509 QTRY_COMPARE(loader->status(), QQuickLoader::Error);
510
511 QVERIFY(!loader->item());
512 QCOMPARE(loader->progress(), 1.0);
513 QCOMPARE(loader->property("did_load").toInt(), 123);
514 QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 0);
515}
516
517void tst_QQuickLoader::active()
518{
519 QQmlEngine engine;
520
521 // check that the item isn't instantiated until active is set to true
522 {
523 QQmlComponent component(&engine, testFileUrl(fileName: "active.1.qml"));
524 QScopedPointer<QObject> object(component.create());
525 QVERIFY(object != nullptr);
526 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
527
528 QVERIFY(loader->active() == false); // set manually to false
529 QVERIFY(!loader->item());
530 QMetaObject::invokeMethod(obj: object.data(), member: "doSetSourceComponent");
531 QVERIFY(!loader->item());
532 QMetaObject::invokeMethod(obj: object.data(), member: "doSetSource");
533 QVERIFY(!loader->item());
534 QMetaObject::invokeMethod(obj: object.data(), member: "doSetActive");
535 QVERIFY(loader->item() != nullptr);
536 }
537
538 // check that the status is Null if active is set to false
539 {
540 QQmlComponent component(&engine, testFileUrl(fileName: "active.2.qml"));
541 QScopedPointer<QObject> object(component.create());
542 QVERIFY(object != nullptr);
543 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
544
545 QVERIFY(loader->active() == true); // active is true by default
546 QCOMPARE(loader->status(), QQuickLoader::Ready);
547 int currStatusChangedCount = loader->property(name: "statusChangedCount").toInt();
548 QMetaObject::invokeMethod(obj: object.data(), member: "doSetInactive");
549 QCOMPARE(loader->status(), QQuickLoader::Null);
550 QCOMPARE(loader->property("statusChangedCount").toInt(), (currStatusChangedCount+1));
551 }
552
553 // check that the source is not cleared if active is set to false
554 {
555 QQmlComponent component(&engine, testFileUrl(fileName: "active.3.qml"));
556 QScopedPointer<QObject> object(component.create());
557 QVERIFY(object != nullptr);
558 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
559
560 QVERIFY(loader->active() == true); // active is true by default
561 QVERIFY(!loader->source().isEmpty());
562 int currSourceChangedCount = loader->property(name: "sourceChangedCount").toInt();
563 QMetaObject::invokeMethod(obj: object.data(), member: "doSetInactive");
564 QVERIFY(!loader->source().isEmpty());
565 QCOMPARE(loader->property("sourceChangedCount").toInt(), currSourceChangedCount);
566 }
567
568 // check that the sourceComponent is not cleared if active is set to false
569 {
570 QQmlComponent component(&engine, testFileUrl(fileName: "active.4.qml"));
571 QScopedPointer<QObject> object(component.create());
572 QVERIFY(object != nullptr);
573 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
574
575 QVERIFY(loader->active() == true); // active is true by default
576 QVERIFY(loader->sourceComponent() != nullptr);
577 int currSourceComponentChangedCount = loader->property(name: "sourceComponentChangedCount").toInt();
578 QMetaObject::invokeMethod(obj: object.data(), member: "doSetInactive");
579 QVERIFY(loader->sourceComponent() != nullptr);
580 QCOMPARE(loader->property("sourceComponentChangedCount").toInt(), currSourceComponentChangedCount);
581 }
582
583 // check that the item is released if active is set to false
584 {
585 QQmlComponent component(&engine, testFileUrl(fileName: "active.5.qml"));
586 QScopedPointer<QObject> object(component.create());
587 QVERIFY(object != nullptr);
588 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
589
590 QVERIFY(loader->active() == true); // active is true by default
591 QVERIFY(loader->item() != nullptr);
592 int currItemChangedCount = loader->property(name: "itemChangedCount").toInt();
593 QMetaObject::invokeMethod(obj: object.data(), member: "doSetInactive");
594 QVERIFY(!loader->item());
595 QCOMPARE(loader->property("itemChangedCount").toInt(), (currItemChangedCount+1));
596 }
597
598 // check that the activeChanged signal is emitted correctly
599 {
600 QQmlComponent component(&engine, testFileUrl(fileName: "active.6.qml"));
601 QScopedPointer<QObject> object(component.create());
602 QVERIFY(object != nullptr);
603 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
604
605 QVERIFY(loader->active() == true); // active is true by default
606 loader->setActive(true); // no effect
607 QCOMPARE(loader->property("activeChangedCount").toInt(), 0);
608 loader->setActive(false); // change signal should be emitted
609 QCOMPARE(loader->property("activeChangedCount").toInt(), 1);
610 loader->setActive(false); // no effect
611 QCOMPARE(loader->property("activeChangedCount").toInt(), 1);
612 loader->setActive(true); // change signal should be emitted
613 QCOMPARE(loader->property("activeChangedCount").toInt(), 2);
614 loader->setActive(false); // change signal should be emitted
615 QCOMPARE(loader->property("activeChangedCount").toInt(), 3);
616 QMetaObject::invokeMethod(obj: object.data(), member: "doSetActive");
617 QCOMPARE(loader->property("activeChangedCount").toInt(), 4);
618 QMetaObject::invokeMethod(obj: object.data(), member: "doSetActive");
619 QCOMPARE(loader->property("activeChangedCount").toInt(), 4);
620 QMetaObject::invokeMethod(obj: object.data(), member: "doSetInactive");
621 QCOMPARE(loader->property("activeChangedCount").toInt(), 5);
622 loader->setActive(true); // change signal should be emitted
623 QCOMPARE(loader->property("activeChangedCount").toInt(), 6);
624 }
625
626 // check that the component isn't loaded until active is set to true
627 {
628 QQmlComponent component(&engine, testFileUrl(fileName: "active.7.qml"));
629 QScopedPointer<QObject> object(component.create());
630 QVERIFY(object != nullptr);
631 QCOMPARE(object->property("success").toBool(), true);
632 }
633
634 // check that the component is loaded if active is not set (true by default)
635 {
636 QQmlComponent component(&engine, testFileUrl(fileName: "active.8.qml"));
637 QScopedPointer<QObject> object(component.create());
638 QVERIFY(object != nullptr);
639 QCOMPARE(object->property("success").toBool(), true);
640 }
641}
642
643void tst_QQuickLoader::initialPropertyValues_data()
644{
645 QTest::addColumn<QUrl>(name: "qmlFile");
646 QTest::addColumn<QStringList>(name: "expectedWarnings");
647 QTest::addColumn<QStringList>(name: "propertyNames");
648 QTest::addColumn<QVariantList>(name: "propertyValues");
649
650 QTest::newRow(dataTag: "source url with value set in onLoaded, initially active = true") << testFileUrl(fileName: "initialPropertyValues.1.qml")
651 << QStringList()
652 << (QStringList() << "initialValue" << "behaviorCount")
653 << (QVariantList() << 1 << 1);
654
655 QTest::newRow(dataTag: "set source with initial property values specified, active = true") << testFileUrl(fileName: "initialPropertyValues.2.qml")
656 << QStringList()
657 << (QStringList() << "initialValue" << "behaviorCount")
658 << (QVariantList() << 2 << 0);
659
660 QTest::newRow(dataTag: "set source with initial property values specified, active = false") << testFileUrl(fileName: "initialPropertyValues.3.qml")
661 << (QStringList() << QString(testFileUrl(fileName: "initialPropertyValues.3.qml").toString() + QLatin1String(":16: TypeError: Cannot read property 'canary' of null")))
662 << (QStringList())
663 << (QVariantList());
664
665 QTest::newRow(dataTag: "set source with initial property values specified, active = false, with active set true later") << testFileUrl(fileName: "initialPropertyValues.4.qml")
666 << QStringList()
667 << (QStringList() << "initialValue" << "behaviorCount")
668 << (QVariantList() << 4 << 0);
669
670 QTest::newRow(dataTag: "set source without initial property values specified, active = true") << testFileUrl(fileName: "initialPropertyValues.5.qml")
671 << QStringList()
672 << (QStringList() << "initialValue" << "behaviorCount")
673 << (QVariantList() << 0 << 0);
674
675 QTest::newRow(dataTag: "set source with initial property values specified with binding, active = true") << testFileUrl(fileName: "initialPropertyValues.6.qml")
676 << QStringList()
677 << (QStringList() << "initialValue" << "behaviorCount")
678 << (QVariantList() << 6 << 0);
679
680 QTest::newRow(dataTag: "ensure initial property value semantics mimic createObject") << testFileUrl(fileName: "initialPropertyValues.7.qml")
681 << QStringList()
682 << (QStringList() << "loaderValue" << "createObjectValue")
683 << (QVariantList() << 1 << 1);
684
685 QTest::newRow(dataTag: "ensure initial property values aren't disposed prior to component completion") << testFileUrl(fileName: "initialPropertyValues.8.qml")
686 << QStringList()
687 << (QStringList() << "initialValue")
688 << (QVariantList() << 6);
689
690 QTest::newRow(dataTag: "ensure required properties are set correctly") << testFileUrl(fileName: "initialPropertyValues.9.qml")
691 << QStringList()
692 << (QStringList() << "i" << "s")
693 << (QVariantList() << 42 << QLatin1String("hello world"));
694
695 QTest::newRow(dataTag: "required properties only partially set =") << testFileUrl(fileName: "initialPropertyValues.10.qml")
696 << (QStringList() << QString(testFileUrl(fileName: "RequiredPropertyValuesComponent.qml").toString() + QLatin1String(":6:5: Required property s was not initialized")))
697 << (QStringList() << "i" << "s")
698 << (QVariantList() << 0 << QLatin1String(""));
699
700 QTest::newRow(dataTag: "source url changed, previously initial properties are discared") << testFileUrl(fileName: "initialPropertyValues.11.qml")
701 << QStringList()
702 << (QStringList() << "oldi" << "i")
703 << (QVariantList() << 12 << 42);
704
705 QTest::newRow(dataTag: "ensure initial properties aren't disposed after active = true") << testFileUrl(fileName: "initialPropertyValues.12.qml")
706 << QStringList()
707 << (QStringList() << "i")
708 << (QVariantList() << 12);
709}
710
711void tst_QQuickLoader::initialPropertyValues()
712{
713 QFETCH(QUrl, qmlFile);
714 QFETCH(QStringList, expectedWarnings);
715 QFETCH(QStringList, propertyNames);
716 QFETCH(QVariantList, propertyValues);
717
718 ThreadedTestHTTPServer server(dataDirectory());
719
720 foreach (const QString &warning, expectedWarnings)
721 QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData());
722
723 QQmlEngine engine;
724 QQmlComponent component(&engine, qmlFile);
725 QScopedPointer<QObject> object(component.beginCreate(engine.rootContext()));
726 QVERIFY(object != nullptr);
727
728 const int serverBaseUrlPropertyIndex = object->metaObject()->indexOfProperty(name: "serverBaseUrl");
729 if (serverBaseUrlPropertyIndex != -1) {
730 QMetaProperty prop = object->metaObject()->property(index: serverBaseUrlPropertyIndex);
731 QVERIFY(prop.write(object.data(), server.baseUrl().toString()));
732 }
733
734 component.completeCreate();
735 if (expectedWarnings.isEmpty()) {
736 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
737 QTRY_VERIFY(loader->item());
738 }
739
740 for (int i = 0; i < propertyNames.size(); ++i)
741 QCOMPARE(object->property(propertyNames.at(i).toLatin1().constData()), propertyValues.at(i));
742}
743
744void tst_QQuickLoader::initialPropertyValuesBinding()
745{
746 QQmlEngine engine;
747 QQmlComponent component(&engine, testFileUrl(fileName: "initialPropertyValues.binding.qml"));
748 QScopedPointer<QObject> object(component.create());
749 QVERIFY(object != nullptr);
750
751 QVERIFY(object->setProperty("bindable", QVariant(8)));
752 QCOMPARE(object->property("canaryValue").toInt(), 8);
753}
754
755void tst_QQuickLoader::initialPropertyValuesError_data()
756{
757 QTest::addColumn<QUrl>(name: "qmlFile");
758 QTest::addColumn<QStringList>(name: "expectedWarnings");
759
760 QTest::newRow(dataTag: "invalid initial property values object") << testFileUrl(fileName: "initialPropertyValues.error.1.qml")
761 << (QStringList() << QString(testFileUrl(fileName: "initialPropertyValues.error.1.qml").toString() + ":6:5: QML Loader: setSource: value is not an object"));
762
763 QTest::newRow(dataTag: "nonexistent source url") << testFileUrl(fileName: "initialPropertyValues.error.2.qml")
764 << (QStringList() << QString(testFileUrl(fileName: "NonexistentSourceComponent.qml").toString() + ": No such file or directory"));
765
766 QTest::newRow(dataTag: "invalid source url") << testFileUrl(fileName: "initialPropertyValues.error.3.qml")
767 << (QStringList() << QString(testFileUrl(fileName: "InvalidSourceComponent.qml").toString() + ":5:1: Expected token `:'"));
768
769 QTest::newRow(dataTag: "invalid initial property values object with invalid property access") << testFileUrl(fileName: "initialPropertyValues.error.4.qml")
770 << (QStringList() << QString(testFileUrl(fileName: "initialPropertyValues.error.4.qml").toString() + ":7:5: QML Loader: setSource: value is not an object")
771 << QString(testFileUrl(fileName: "initialPropertyValues.error.4.qml").toString() + ":5: TypeError: Cannot read property 'canary' of null"));
772}
773
774void tst_QQuickLoader::initialPropertyValuesError()
775{
776 QFETCH(QUrl, qmlFile);
777 QFETCH(QStringList, expectedWarnings);
778
779 foreach (const QString &warning, expectedWarnings)
780 QTest::ignoreMessage(type: QtWarningMsg, message: warning.toUtf8().constData());
781
782 QQmlEngine engine;
783 QQmlComponent component(&engine, qmlFile);
784 QScopedPointer<QObject> object(component.create());
785 QVERIFY(object != nullptr);
786 QQuickLoader *loader = object->findChild<QQuickLoader*>(aName: "loader");
787 QVERIFY(loader != nullptr);
788 QVERIFY(!loader->item());
789}
790
791// QTBUG-9241
792void tst_QQuickLoader::deleteComponentCrash()
793{
794 QQmlEngine engine;
795 QQmlComponent component(&engine, testFileUrl(fileName: "crash.qml"));
796 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
797 QVERIFY(item);
798
799 item->metaObject()->invokeMethod(obj: item.data(), member: "setLoaderSource");
800
801 QQuickLoader *loader = qobject_cast<QQuickLoader*>(object: item->QQuickItem::childItems().at(i: 0));
802 QVERIFY(loader);
803 QVERIFY(loader->item());
804 QCOMPARE(loader->item()->objectName(), QLatin1String("blue"));
805 QCOMPARE(loader->progress(), 1.0);
806 QCOMPARE(loader->status(), QQuickLoader::Ready);
807 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
808 QCoreApplication::processEvents();
809 QTRY_COMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
810 QCOMPARE(loader->source(), testFileUrl("BlueRect.qml"));
811}
812
813void tst_QQuickLoader::nonItem()
814{
815 QQmlEngine engine;
816 QQmlComponent component(&engine, testFileUrl(fileName: "nonItem.qml"));
817
818 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
819 QVERIFY(loader);
820 QVERIFY(loader->item());
821
822 QCOMPARE(loader.data(), loader->item()->parent());
823
824 QPointer<QObject> item = loader->item();
825 loader->setActive(false);
826 QVERIFY(!loader->item());
827 QTRY_VERIFY(!item);
828}
829
830void tst_QQuickLoader::vmeErrors()
831{
832 QQmlEngine engine;
833 QQmlComponent component(&engine, testFileUrl(fileName: "vmeErrors.qml"));
834 QString err = testFileUrl(fileName: "VmeError.qml").toString() + ":6:26: Cannot assign object type QObject with no default method";
835 QTest::ignoreMessage(type: QtWarningMsg, message: err.toLatin1().constData());
836 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
837 QVERIFY(loader);
838 QVERIFY(!loader->item());
839}
840
841// QTBUG-13481
842void tst_QQuickLoader::creationContext()
843{
844 QQmlEngine engine;
845 QQmlComponent component(&engine, testFileUrl(fileName: "creationContext.qml"));
846
847 QScopedPointer<QObject> o(component.create());
848 QVERIFY(o != nullptr);
849
850 QCOMPARE(o->property("test").toBool(), true);
851}
852
853void tst_QQuickLoader::QTBUG_16928()
854{
855 QQmlEngine engine;
856 QQmlComponent component(&engine, testFileUrl(fileName: "QTBUG_16928.qml"));
857 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
858 QVERIFY(item);
859
860 QCOMPARE(item->width(), 250.);
861 QCOMPARE(item->height(), 250.);
862}
863
864void tst_QQuickLoader::implicitSize()
865{
866 QQmlEngine engine;
867 QQmlComponent component(&engine, testFileUrl(fileName: "implicitSize.qml"));
868 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
869 QVERIFY(item);
870
871 QCOMPARE(item->width(), 150.);
872 QCOMPARE(item->height(), 150.);
873
874 QCOMPARE(item->property("implHeight").toReal(), 100.);
875 QCOMPARE(item->property("implWidth").toReal(), 100.);
876
877 QQuickLoader *loader = item->findChild<QQuickLoader*>(aName: "loader");
878 QSignalSpy implWidthSpy(loader, SIGNAL(implicitWidthChanged()));
879 QSignalSpy implHeightSpy(loader, SIGNAL(implicitHeightChanged()));
880
881 QMetaObject::invokeMethod(obj: item.data(), member: "changeImplicitSize");
882
883 QCOMPARE(loader->property("implicitWidth").toReal(), 200.);
884 QCOMPARE(loader->property("implicitHeight").toReal(), 300.);
885
886 QCOMPARE(implWidthSpy.count(), 1);
887 QCOMPARE(implHeightSpy.count(), 1);
888}
889
890void tst_QQuickLoader::QTBUG_17114()
891{
892 QQmlEngine engine;
893 QQmlComponent component(&engine, testFileUrl(fileName: "QTBUG_17114.qml"));
894 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: component.create()));
895 QVERIFY(item);
896
897 QCOMPARE(item->property("loaderWidth").toReal(), 32.);
898 QCOMPARE(item->property("loaderHeight").toReal(), 32.);
899}
900
901void tst_QQuickLoader::asynchronous_data()
902{
903 QTest::addColumn<QUrl>(name: "qmlFile");
904 QTest::addColumn<QStringList>(name: "expectedWarnings");
905
906 QTest::newRow(dataTag: "Valid component") << testFileUrl(fileName: "BigComponent.qml")
907 << QStringList();
908
909 QTest::newRow(dataTag: "Non-existent component") << testFileUrl(fileName: "IDoNotExist.qml")
910 << (QStringList() << QString(testFileUrl(fileName: "IDoNotExist.qml").toString() + ": No such file or directory"));
911
912 QTest::newRow(dataTag: "Invalid component") << testFileUrl(fileName: "InvalidSourceComponent.qml")
913 << (QStringList() << QString(testFileUrl(fileName: "InvalidSourceComponent.qml").toString() + ":5:1: Expected token `:'"));
914}
915
916void tst_QQuickLoader::asynchronous()
917{
918 QFETCH(QUrl, qmlFile);
919 QFETCH(QStringList, expectedWarnings);
920
921 QQmlEngine engine;
922 PeriodicIncubationController *controller = new PeriodicIncubationController;
923 QQmlIncubationController *previous = engine.incubationController();
924 engine.setIncubationController(controller);
925 delete previous;
926
927 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronous.qml"));
928 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
929 QVERIFY(root);
930
931 QQuickLoader *loader = root->findChild<QQuickLoader*>(aName: "loader");
932 QVERIFY(loader);
933
934 foreach (const QString &warning, expectedWarnings)
935 QTest::ignoreMessage(type: QtWarningMsg, message: warning.toUtf8().constData());
936
937 QVERIFY(!loader->item());
938 QCOMPARE(loader->progress(), 0.0);
939 root->setProperty(name: "comp", value: qmlFile.toString());
940 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponent");
941 QVERIFY(!loader->item());
942
943 if (expectedWarnings.isEmpty()) {
944 QCOMPARE(loader->status(), QQuickLoader::Loading);
945
946 controller->start();
947 QVERIFY(!controller->incubated); // asynchronous compilation means not immediately compiled/incubating.
948 QTRY_VERIFY(controller->incubated); // but should start incubating once compilation is complete.
949 QTRY_VERIFY(loader->item());
950 QCOMPARE(loader->progress(), 1.0);
951 QCOMPARE(loader->status(), QQuickLoader::Ready);
952 } else {
953 QTRY_COMPARE(loader->progress(), 1.0);
954 QTRY_COMPARE(loader->status(), QQuickLoader::Error);
955 }
956}
957
958void tst_QQuickLoader::asynchronous_clear()
959{
960 QQmlEngine engine;
961 PeriodicIncubationController *controller = new PeriodicIncubationController;
962 QQmlIncubationController *previous = engine.incubationController();
963 engine.setIncubationController(controller);
964 delete previous;
965
966 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronous.qml"));
967 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
968 QVERIFY(root);
969
970 QQuickLoader *loader = root->findChild<QQuickLoader*>(aName: "loader");
971 QVERIFY(loader);
972
973 QVERIFY(!loader->item());
974 root->setProperty(name: "comp", value: "BigComponent.qml");
975 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponent");
976 QVERIFY(!loader->item());
977
978 controller->start();
979 QCOMPARE(loader->status(), QQuickLoader::Loading);
980 QTRY_COMPARE(engine.incubationController()->incubatingObjectCount(), 1);
981
982 // clear before component created
983 root->setProperty(name: "comp", value: "");
984 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponent");
985 QVERIFY(!loader->item());
986 QCOMPARE(engine.incubationController()->incubatingObjectCount(), 0);
987
988 QCOMPARE(loader->progress(), 0.0);
989 QCOMPARE(loader->status(), QQuickLoader::Null);
990 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0);
991
992 // check loading component
993 root->setProperty(name: "comp", value: "BigComponent.qml");
994 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponent");
995 QVERIFY(!loader->item());
996
997 QCOMPARE(loader->status(), QQuickLoader::Loading);
998 QCOMPARE(engine.incubationController()->incubatingObjectCount(), 1);
999
1000 QTRY_VERIFY(loader->item());
1001 QCOMPARE(loader->progress(), 1.0);
1002 QCOMPARE(loader->status(), QQuickLoader::Ready);
1003 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
1004}
1005
1006void tst_QQuickLoader::simultaneousSyncAsync()
1007{
1008 QQmlEngine engine;
1009 PeriodicIncubationController *controller = new PeriodicIncubationController;
1010 QQmlIncubationController *previous = engine.incubationController();
1011 engine.setIncubationController(controller);
1012 delete previous;
1013
1014 QQmlComponent component(&engine, testFileUrl(fileName: "simultaneous.qml"));
1015 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
1016 QVERIFY(root);
1017
1018 QQuickLoader *asyncLoader = root->findChild<QQuickLoader*>(aName: "asyncLoader");
1019 QQuickLoader *syncLoader = root->findChild<QQuickLoader*>(aName: "syncLoader");
1020 QVERIFY(asyncLoader);
1021 QVERIFY(syncLoader);
1022
1023 QVERIFY(!asyncLoader->item());
1024 QVERIFY(!syncLoader->item());
1025 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponents");
1026 QVERIFY(!asyncLoader->item());
1027 QVERIFY(syncLoader->item());
1028
1029 controller->start();
1030 QCOMPARE(asyncLoader->status(), QQuickLoader::Loading);
1031 QVERIFY(!controller->incubated); // asynchronous compilation means not immediately compiled/incubating.
1032 QTRY_VERIFY(controller->incubated); // but should start incubating once compilation is complete.
1033 QTRY_VERIFY(asyncLoader->item());
1034 QCOMPARE(asyncLoader->progress(), 1.0);
1035 QCOMPARE(asyncLoader->status(), QQuickLoader::Ready);
1036}
1037
1038void tst_QQuickLoader::asyncToSync1()
1039{
1040 QQmlEngine engine;
1041 PeriodicIncubationController *controller = new PeriodicIncubationController;
1042 QQmlIncubationController *previous = engine.incubationController();
1043 engine.setIncubationController(controller);
1044 delete previous;
1045
1046 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronous.qml"));
1047 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
1048 QVERIFY(root);
1049
1050 QQuickLoader *loader = root->findChild<QQuickLoader*>(aName: "loader");
1051 QVERIFY(loader);
1052
1053 QVERIFY(!loader->item());
1054 root->setProperty(name: "comp", value: "BigComponent.qml");
1055 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponent");
1056 QVERIFY(!loader->item());
1057
1058 controller->start();
1059 QCOMPARE(loader->status(), QQuickLoader::Loading);
1060 QCOMPARE(engine.incubationController()->incubatingObjectCount(), 0);
1061
1062 // force completion before component created
1063 loader->setAsynchronous(false);
1064 QVERIFY(loader->item());
1065 QCOMPARE(loader->progress(), 1.0);
1066 QCOMPARE(loader->status(), QQuickLoader::Ready);
1067 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
1068}
1069
1070void tst_QQuickLoader::asyncToSync2()
1071{
1072 QQmlEngine engine;
1073 PeriodicIncubationController *controller = new PeriodicIncubationController;
1074 QQmlIncubationController *previous = engine.incubationController();
1075 engine.setIncubationController(controller);
1076 delete previous;
1077
1078 QQmlComponent component(&engine, testFileUrl(fileName: "asynchronous.qml"));
1079 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
1080 QVERIFY(root);
1081
1082 QQuickLoader *loader = root->findChild<QQuickLoader*>(aName: "loader");
1083 QVERIFY(loader);
1084
1085 QVERIFY(!loader->item());
1086 root->setProperty(name: "comp", value: "BigComponent.qml");
1087 QMetaObject::invokeMethod(obj: root.data(), member: "loadComponent");
1088 QVERIFY(!loader->item());
1089
1090 controller->start();
1091 QCOMPARE(loader->status(), QQuickLoader::Loading);
1092 QTRY_COMPARE(engine.incubationController()->incubatingObjectCount(), 1);
1093
1094 // force completion after component created but before incubation complete
1095 loader->setAsynchronous(false);
1096 QVERIFY(loader->item());
1097 QCOMPARE(loader->progress(), 1.0);
1098 QCOMPARE(loader->status(), QQuickLoader::Ready);
1099 QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1);
1100}
1101
1102void tst_QQuickLoader::loadedSignal()
1103{
1104 QQmlEngine engine;
1105 PeriodicIncubationController *controller = new PeriodicIncubationController;
1106 QQmlIncubationController *previous = engine.incubationController();
1107 engine.setIncubationController(controller);
1108 delete previous;
1109
1110 {
1111 // ensure that triggering loading (by setting active = true)
1112 // and then immediately setting active to false, causes the
1113 // loader to be deactivated, including disabling the incubator.
1114 QQmlComponent component(&engine, testFileUrl(fileName: "loadedSignal.qml"));
1115 QScopedPointer<QObject> obj(component.create());
1116
1117 QMetaObject::invokeMethod(obj: obj.data(), member: "triggerLoading");
1118 QTest::qWait(ms: 100); // ensure that loading would have finished if it wasn't deactivated
1119 QCOMPARE(obj->property("loadCount").toInt(), 0);
1120 QVERIFY(obj->property("success").toBool());
1121
1122 QMetaObject::invokeMethod(obj: obj.data(), member: "triggerLoading");
1123 QTest::qWait(ms: 100);
1124 QCOMPARE(obj->property("loadCount").toInt(), 0);
1125 QVERIFY(obj->property("success").toBool());
1126
1127 QMetaObject::invokeMethod(obj: obj.data(), member: "triggerMultipleLoad");
1128 controller->start();
1129 QTest::qWait(ms: 100);
1130 QTRY_COMPARE(obj->property("loadCount").toInt(), 1); // only one loaded signal should be emitted.
1131 QVERIFY(obj->property("success").toBool());
1132 }
1133
1134 {
1135 // ensure that an error doesn't result in the onLoaded signal being emitted.
1136 QQmlComponent component(&engine, testFileUrl(fileName: "loadedSignal.2.qml"));
1137 QScopedPointer<QObject> obj(component.create());
1138
1139 QMetaObject::invokeMethod(obj: obj.data(), member: "triggerLoading");
1140 QTest::qWait(ms: 100);
1141 QCOMPARE(obj->property("loadCount").toInt(), 0);
1142 QVERIFY(obj->property("success").toBool());
1143 }
1144}
1145
1146void tst_QQuickLoader::parented()
1147{
1148 QQmlEngine engine;
1149 QQmlComponent component(&engine, testFileUrl(fileName: "parented.qml"));
1150 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
1151 QVERIFY(root);
1152
1153 QQuickItem *item = root->findChild<QQuickItem*>(aName: "comp");
1154 QVERIFY(item);
1155
1156 QCOMPARE(item->parentItem(), root.data());
1157
1158 QCOMPARE(item->width(), 300.);
1159 QCOMPARE(item->height(), 300.);
1160}
1161
1162void tst_QQuickLoader::sizeBound()
1163{
1164 QQmlEngine engine;
1165 QQmlComponent component(&engine, testFileUrl(fileName: "sizebound.qml"));
1166 QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(object: component.create()));
1167 QVERIFY(root);
1168 QQuickLoader *loader = root->findChild<QQuickLoader*>(aName: "loader");
1169 QVERIFY(loader != nullptr);
1170
1171 QVERIFY(loader->item());
1172
1173 QCOMPARE(loader->width(), 50.0);
1174 QCOMPARE(loader->height(), 60.0);
1175
1176 QMetaObject::invokeMethod(obj: root.data(), member: "switchComponent");
1177
1178 QCOMPARE(loader->width(), 80.0);
1179 QCOMPARE(loader->height(), 90.0);
1180}
1181
1182void tst_QQuickLoader::QTBUG_30183()
1183{
1184 QQmlEngine engine;
1185 QQmlComponent component(&engine, testFileUrl(fileName: "/QTBUG_30183.qml"));
1186 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
1187 QVERIFY(loader != nullptr);
1188 QCOMPARE(loader->width(), 240.0);
1189 QCOMPARE(loader->height(), 120.0);
1190
1191 // the loaded item must follow the size
1192 QQuickItem *rect = qobject_cast<QQuickItem*>(object: loader->item());
1193 QVERIFY(rect);
1194 QCOMPARE(rect->width(), 240.0);
1195 QCOMPARE(rect->height(), 120.0);
1196}
1197
1198void tst_QQuickLoader::transientWindow() // QTBUG-52944
1199{
1200 QQuickView view;
1201 view.setSource(testFileUrl(fileName: "itemLoaderWindow.qml"));
1202 QQuickItem *root = qobject_cast<QQuickItem*>(object: view.rootObject());
1203 QVERIFY(root);
1204 QQuickLoader *loader = root->findChild<QQuickLoader *>();
1205 QVERIFY(loader);
1206 QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
1207 QQuickWindowQmlImpl *loadedWindow = qobject_cast<QQuickWindowQmlImpl *>(object: loader->item());
1208 QVERIFY(loadedWindow);
1209 QCOMPARE(loadedWindow->visibility(), QWindow::Hidden);
1210
1211 QElapsedTimer timer;
1212 qint64 viewVisibleTime = -1;
1213 qint64 loadedWindowVisibleTime = -1;
1214 connect(sender: &view, signal: &QWindow::visibleChanged,
1215 slot: [&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } );
1216 connect(sender: loadedWindow, signal: &QQuickWindowQmlImpl::visibilityChanged,
1217 slot: [&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } );
1218 timer.start();
1219 view.show();
1220
1221 QVERIFY(QTest::qWaitForWindowExposed(&view));
1222 QTRY_VERIFY(loadedWindowVisibleTime >= 0);
1223 QVERIFY(viewVisibleTime >= 0);
1224
1225 // now that we're sure they are both visible, which one became visible first?
1226 qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item";
1227 QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0);
1228
1229 QWindowList windows = QGuiApplication::topLevelWindows();
1230 QTRY_COMPARE(windows.size(), 2);
1231
1232 // TODO Ideally we would now close the outer window and make sure the transient window closes too.
1233 // It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed()
1234 // but quitting an autotest doesn't make sense.
1235}
1236
1237void tst_QQuickLoader::nestedTransientWindow() // QTBUG-52944
1238{
1239 QQuickView view;
1240 view.setSource(testFileUrl(fileName: "itemLoaderItemWindow.qml"));
1241 QQuickItem *root = qobject_cast<QQuickItem*>(object: view.rootObject());
1242 QVERIFY(root);
1243 QQuickLoader *loader = root->findChild<QQuickLoader *>();
1244 QVERIFY(loader);
1245 QTRY_COMPARE(loader->status(), QQuickLoader::Ready);
1246 QQuickItem *loadedItem = qobject_cast<QQuickItem *>(object: loader->item());
1247 QVERIFY(loadedItem);
1248 QQuickWindowQmlImpl *loadedWindow = loadedItem->findChild<QQuickWindowQmlImpl *>();
1249 QVERIFY(loadedWindow);
1250 QCOMPARE(loadedWindow->visibility(), QWindow::Hidden);
1251
1252 QElapsedTimer timer;
1253 qint64 viewVisibleTime = -1;
1254 qint64 loadedWindowVisibleTime = -1;
1255 connect(sender: &view, signal: &QWindow::visibleChanged,
1256 slot: [&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } );
1257 connect(sender: loadedWindow, signal: &QQuickWindowQmlImpl::visibilityChanged,
1258 slot: [&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } );
1259 timer.start();
1260 view.show();
1261
1262 QVERIFY(QTest::qWaitForWindowExposed(&view));
1263 QTRY_VERIFY(loadedWindowVisibleTime >= 0);
1264 QVERIFY(viewVisibleTime >= 0);
1265
1266 // now that we're sure they are both visible, which one became visible first?
1267 qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item";
1268 QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0);
1269
1270 QWindowList windows = QGuiApplication::topLevelWindows();
1271 QTRY_COMPARE(windows.size(), 2);
1272
1273 // TODO Ideally we would now close the outer window and make sure the transient window closes too.
1274 // It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed()
1275 // but quitting an autotest doesn't make sense.
1276}
1277
1278void tst_QQuickLoader::sourceComponentGarbageCollection()
1279{
1280 QQmlEngine engine;
1281 QQmlComponent component(&engine, testFileUrl(fileName: "sourceComponentGarbageCollection.qml"));
1282 QScopedPointer<QObject> obj(component.create());
1283 QVERIFY(!obj.isNull());
1284
1285 QMetaObject::invokeMethod(obj: obj.data(), member: "setSourceComponent");
1286 engine.collectGarbage();
1287 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
1288
1289 QSignalSpy spy(obj.data(), SIGNAL(loaded()));
1290
1291 obj->setProperty(name: "active", value: true);
1292
1293 if (spy.isEmpty())
1294 QVERIFY(spy.wait());
1295
1296 QCOMPARE(spy.count(), 1);
1297}
1298
1299// QTBUG-51995
1300void tst_QQuickLoader::bindings()
1301{
1302 QQmlEngine engine;
1303 QQmlComponent component(&engine, testFileUrl(fileName: "bindings.qml"));
1304 QScopedPointer<QObject> object(component.create());
1305 QVERIFY(!object.isNull());
1306
1307 QQuickItem *game = object->property(name: "game").value<QQuickItem*>();
1308 QVERIFY(game);
1309
1310 QQuickLoader *loader = object->property(name: "loader").value<QQuickLoader*>();
1311 QVERIFY(loader);
1312
1313 QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>)));
1314
1315 // Causes the Loader to become active
1316 game->setState(QLatin1String("running"));
1317 QTRY_VERIFY(loader->item());
1318
1319 // Causes the Loader to become inactive - should not cause binding errors
1320 game->setState(QLatin1String("invalid"));
1321 QTRY_VERIFY(!loader->item());
1322
1323 QString failureMessage;
1324 if (!warningsSpy.isEmpty()) {
1325 QDebug stream(&failureMessage);
1326 stream << warningsSpy.first().first().value<QList<QQmlError>>();
1327 }
1328 QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage));
1329}
1330
1331// QTBUG-47321
1332void tst_QQuickLoader::parentErrors()
1333{
1334 QQmlEngine engine;
1335 QQmlComponent component(&engine, testFileUrl(fileName: "parentErrors.qml"));
1336 QScopedPointer<QObject> object(component.create());
1337 QVERIFY(!object.isNull());
1338
1339 QQuickLoader *loader = object->property(name: "loader").value<QQuickLoader*>();
1340 QVERIFY(loader);
1341
1342 QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>)));
1343
1344 // Give the loader a component
1345 loader->setSourceComponent(object->property(name: "component").value<QQmlComponent*>());
1346 QTRY_VERIFY(loader->item());
1347
1348 // Clear the loader's component; should not cause binding errors
1349 loader->setSourceComponent(nullptr);
1350 QTRY_VERIFY(!loader->item());
1351
1352 QString failureMessage;
1353 if (!warningsSpy.isEmpty()) {
1354 QDebug stream(&failureMessage);
1355 stream << warningsSpy.first().first().value<QList<QQmlError>>();
1356 }
1357 QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage));
1358}
1359
1360class ObjectInRootContext: public QObject
1361{
1362 Q_OBJECT
1363
1364public:
1365 int didIt = 0;
1366
1367public slots:
1368 void doIt() {
1369 didIt += 1;
1370 }
1371};
1372
1373void tst_QQuickLoader::rootContext()
1374{
1375 QQmlEngine engine;
1376 ObjectInRootContext objectInRootContext;
1377 engine.rootContext()->setContextProperty("objectInRootContext", &objectInRootContext);
1378
1379 QQmlComponent component(&engine, testFileUrl(fileName: "rootContext.qml"));
1380 QScopedPointer<QObject> object(component.create());
1381 QVERIFY(!object.isNull());
1382
1383 QQuickLoader *loader = object->property(name: "loader").value<QQuickLoader*>();
1384 QVERIFY(loader);
1385
1386 QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>)));
1387
1388 // Give the loader a component
1389 loader->setSourceComponent(object->property(name: "component").value<QQmlComponent*>());
1390 QTRY_VERIFY(loader->active());
1391 QTRY_VERIFY(loader->item());
1392
1393 QString failureMessage;
1394 if (!warningsSpy.isEmpty()) {
1395 QDebug stream(&failureMessage);
1396 stream << warningsSpy.first().first().value<QList<QQmlError>>();
1397 }
1398 QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage));
1399 QCOMPARE(objectInRootContext.didIt, 0);
1400
1401 // Deactivate the loader, which deletes the item.
1402 // Check that a) there are no errors, and b) the objectInRootContext can still be resolved even
1403 // after deactivating the loader. If it cannot, a ReferenceError for objectInRootContext is
1404 // generated (and the 'doIt' counter in objectInRootContext will be 1 for the call before
1405 // the deactivation).
1406 loader->item()->setProperty(name: "trigger", value: true);
1407 QTRY_VERIFY(!loader->active());
1408 QTRY_VERIFY(!loader->item());
1409
1410 if (!warningsSpy.isEmpty()) {
1411 QDebug stream(&failureMessage);
1412 stream << warningsSpy.first().first().value<QList<QQmlError>>();
1413 }
1414 QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage));
1415 QCOMPARE(objectInRootContext.didIt, 2);
1416}
1417
1418void tst_QQuickLoader::sourceURLKeepComponent()
1419{
1420 QQmlEngine engine;
1421 QQmlComponent component(&engine);
1422 component.setData(QByteArray(
1423 "import QtQuick 2.0\n"
1424 " Loader { id: loader\n }"),
1425 baseUrl: dataDirectoryUrl());
1426
1427 QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(object: component.create()));
1428 loader->setSource(testFileUrl(fileName: "/Rect120x60.qml"));
1429
1430 QVERIFY(loader);
1431 QVERIFY(loader->item());
1432 QVERIFY(loader->sourceComponent());
1433 QCOMPARE(loader->progress(), 1.0);
1434
1435 const QPointer<QQmlComponent> sourceComponent = loader->sourceComponent();
1436
1437 //Ensure toggling active status does not recreate component
1438 loader->setActive(false);
1439 QVERIFY(!loader->item());
1440 QVERIFY(loader->sourceComponent());
1441 QCOMPARE(sourceComponent.data(), loader->sourceComponent());
1442
1443 loader->setActive(true);
1444 QVERIFY(loader->item());
1445 QVERIFY(loader->sourceComponent());
1446 QCOMPARE(sourceComponent.data(), loader->sourceComponent());
1447
1448 loader->setActive(false);
1449 QVERIFY(!loader->item());
1450 QVERIFY(loader->sourceComponent());
1451 QCOMPARE(sourceComponent.data(), loader->sourceComponent());
1452
1453 //Ensure changing source url causes component to be recreated when inactive
1454 loader->setSource(testFileUrl(fileName: "/BlueRect.qml"));
1455
1456 loader->setActive(true);
1457 QVERIFY(loader->item());
1458 QVERIFY(loader->sourceComponent());
1459
1460 const QPointer<QQmlComponent> newSourceComponent = loader->sourceComponent();
1461 QVERIFY(sourceComponent.data() != newSourceComponent.data());
1462
1463 //Ensure changing source url causes component to be recreated when active
1464 loader->setSource(testFileUrl(fileName: "/Rect120x60.qml"));
1465 QVERIFY(loader->sourceComponent() != newSourceComponent.data());
1466
1467}
1468
1469// QTBUG-82002
1470void tst_QQuickLoader::statusChangeOnlyEmittedOnce()
1471{
1472 QQmlApplicationEngine engine;
1473 auto url = testFileUrl(fileName: "statusChanged.qml");
1474 engine.load(url);
1475 auto root = engine.rootObjects().at(i: 0);
1476 QVERIFY(root);
1477 QTRY_COMPARE(QQuickLoader::Status(root->property("status").toInt()), QQuickLoader::Ready);
1478 QCOMPARE(root->property("statusChangedCounter").toInt(), 2); // 1xLoading + 1xReady*/
1479}
1480
1481void tst_QQuickLoader::setSourceAndCheckStatus()
1482{
1483 QQmlApplicationEngine engine;
1484 auto url = testFileUrl(fileName: "setSourceAndCheckStatus.qml");
1485 engine.load(url);
1486 auto root = engine.rootObjects().at(i: 0);
1487 QVERIFY(root);
1488
1489 QQuickLoader *loader = root->findChild<QQuickLoader *>();
1490 QMetaObject::invokeMethod(obj: loader, member: "load", Q_ARG(QVariant, QVariant::fromValue(QStringLiteral("./RedRect.qml"))));
1491 QCOMPARE(loader->status(), QQuickLoader::Ready);
1492
1493 QMetaObject::invokeMethod(obj: loader, member: "load", Q_ARG(QVariant, QVariant::fromValue(QStringLiteral(""))));
1494 QCOMPARE(loader->status(), QQuickLoader::Null);
1495
1496 QMetaObject::invokeMethod(obj: loader, member: "load", Q_ARG(QVariant, QVariant()));
1497 QCOMPARE(loader->status(), QQuickLoader::Null);
1498}
1499
1500void tst_QQuickLoader::asyncLoaderRace()
1501{
1502 QQmlApplicationEngine engine;
1503 auto url = testFileUrl(fileName: "loader-async-race.qml");
1504 engine.load(url);
1505 auto root = engine.rootObjects().at(i: 0);
1506 QVERIFY(root);
1507
1508 QQuickLoader *loader = root->findChild<QQuickLoader *>();
1509 QCOMPARE(loader->active(), false);
1510 QCOMPARE(loader->status(), QQuickLoader::Null);
1511 QCOMPARE(loader->item(), nullptr);
1512
1513 QSignalSpy spy(loader, &QQuickLoader::itemChanged);
1514 QVERIFY(!spy.wait(100));
1515 QCOMPARE(loader->item(), nullptr);
1516}
1517
1518QTEST_MAIN(tst_QQuickLoader)
1519
1520#include "tst_qquickloader.moc"
1521

source code of qtdeclarative/tests/auto/quick/qquickloader/tst_qquickloader.cpp