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 <QDebug>
30
31#include <QtQml/qqmlengine.h>
32#include <QtQml/qqmlcomponent.h>
33#include <QtQml/qqmlproperty.h>
34#include <QtQml/qqmlincubator.h>
35#include <QtQuick>
36#include <QtQuick/private/qquickrectangle_p.h>
37#include <QtQuick/private/qquickmousearea_p.h>
38#include <private/qqmlcontext_p.h>
39#include <private/qv4qmlcontext_p.h>
40#include <private/qv4scopedvalue_p.h>
41#include <private/qv4qmlcontext_p.h>
42#include <qcolor.h>
43#include "../../shared/util.h"
44#include "testhttpserver.h"
45
46class MyIC : public QObject, public QQmlIncubationController
47{
48 Q_OBJECT
49public:
50 MyIC() { startTimer(interval: 5); }
51protected:
52 virtual void timerEvent(QTimerEvent*) {
53 incubateFor(msecs: 5);
54 }
55};
56
57class ComponentWatcher : public QObject
58{
59 Q_OBJECT
60public:
61 ComponentWatcher(QQmlComponent *comp) : loading(0), error(0), ready(0) {
62 connect(sender: comp, SIGNAL(statusChanged(QQmlComponent::Status)),
63 receiver: this, SLOT(statusChanged(QQmlComponent::Status)));
64 }
65
66 int loading;
67 int error;
68 int ready;
69
70public slots:
71 void statusChanged(QQmlComponent::Status status) {
72 switch (status) {
73 case QQmlComponent::Loading:
74 ++loading;
75 break;
76 case QQmlComponent::Error:
77 ++error;
78 break;
79 case QQmlComponent::Ready:
80 ++ready;
81 break;
82 default:
83 break;
84 }
85 }
86};
87
88static void gc(QQmlEngine &engine)
89{
90 engine.collectGarbage();
91 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
92 QCoreApplication::processEvents();
93}
94
95class tst_qqmlcomponent : public QQmlDataTest
96{
97 Q_OBJECT
98public:
99 tst_qqmlcomponent() { engine.setIncubationController(&ic); }
100
101private slots:
102 void null();
103 void loadEmptyUrl();
104 void qmlCreateWindow();
105 void qmlCreateObjectAutoParent_data();
106 void qmlCreateObjectAutoParent();
107 void qmlCreateObjectWithProperties();
108 void qmlIncubateObject();
109 void qmlCreateParentReference();
110 void async();
111 void asyncHierarchy();
112 void asyncForceSync();
113 void componentUrlCanonicalization();
114 void onDestructionLookup();
115 void onDestructionCount();
116 void recursion();
117 void recursionContinuation();
118 void partialComponentCreation();
119 void callingContextForInitialProperties();
120 void setNonExistentInitialProperty();
121 void relativeUrl_data();
122 void relativeUrl();
123 void setDataNoEngineNoSegfault();
124 void testRequiredProperties_data();
125 void testRequiredProperties();
126 void testRequiredPropertiesFromQml();
127 void testSetInitialProperties();
128
129private:
130 QQmlEngine engine;
131 MyIC ic;
132};
133
134void tst_qqmlcomponent::null()
135{
136 {
137 QQmlComponent c;
138 QVERIFY(c.isNull());
139 }
140
141 {
142 QQmlComponent c(&engine);
143 QVERIFY(c.isNull());
144 }
145}
146
147
148void tst_qqmlcomponent::loadEmptyUrl()
149{
150 QQmlComponent c(&engine);
151 c.loadUrl(url: QUrl());
152
153 QVERIFY(c.isError());
154 QCOMPARE(c.errors().count(), 1);
155 QQmlError error = c.errors().first();
156 QCOMPARE(error.url(), QUrl());
157 QCOMPARE(error.line(), -1);
158 QCOMPARE(error.column(), -1);
159 QCOMPARE(error.description(), QLatin1String("Invalid empty URL"));
160}
161
162void tst_qqmlcomponent::qmlIncubateObject()
163{
164 QQmlComponent component(&engine, testFileUrl(fileName: "incubateObject.qml"));
165 QObject *object = component.create();
166 QVERIFY(object != nullptr);
167 QCOMPARE(object->property("test1").toBool(), true);
168 QCOMPARE(object->property("test2").toBool(), false);
169
170 QTRY_VERIFY(object->property("test2").toBool());
171
172 delete object;
173}
174
175void tst_qqmlcomponent::qmlCreateWindow()
176{
177 QQmlEngine engine;
178 QQmlComponent component(&engine);
179 component.loadUrl(url: testFileUrl(fileName: "createWindow.qml"));
180 QScopedPointer<QQuickWindow> window(qobject_cast<QQuickWindow *>(object: component.create()));
181 QVERIFY(!window.isNull());
182}
183
184void tst_qqmlcomponent::qmlCreateObjectAutoParent_data()
185{
186 QTest::addColumn<QString>(name: "testFile");
187
188 QTest::newRow(dataTag: "createObject") << QStringLiteral("createObject.qml");
189 QTest::newRow(dataTag: "createQmlObject") << QStringLiteral("createQmlObject.qml");
190}
191
192
193void tst_qqmlcomponent::qmlCreateObjectAutoParent()
194{
195 QFETCH(QString, testFile);
196
197 QQmlEngine engine;
198 QQmlComponent component(&engine, testFileUrl(fileName: testFile));
199 QScopedPointer<QObject> root(qobject_cast<QQuickItem *>(object: component.create()));
200 QVERIFY(!root.isNull());
201 QObject *qtobjectParent = root->property(name: "qtobjectParent").value<QObject*>();
202 QQuickItem *itemParent = qobject_cast<QQuickItem *>(object: root->property(name: "itemParent").value<QObject*>());
203 QQuickWindow *windowParent = qobject_cast<QQuickWindow *>(object: root->property(name: "windowParent").value<QObject*>());
204 QVERIFY(qtobjectParent);
205 QVERIFY(itemParent);
206 QVERIFY(windowParent);
207
208 QObject *qtobject_qtobject = root->property(name: "qtobject_qtobject").value<QObject*>();
209 QObject *qtobject_item = root->property(name: "qtobject_item").value<QObject*>();
210 QObject *qtobject_window = root->property(name: "qtobject_window").value<QObject*>();
211 QObject *item_qtobject = root->property(name: "item_qtobject").value<QObject*>();
212 QObject *item_item = root->property(name: "item_item").value<QObject*>();
213 QObject *item_window = root->property(name: "item_window").value<QObject*>();
214 QObject *window_qtobject = root->property(name: "window_qtobject").value<QObject*>();
215 QObject *window_item = root->property(name: "window_item").value<QObject*>();
216 QObject *window_window = root->property(name: "window_window").value<QObject*>();
217
218 QVERIFY(qtobject_qtobject);
219 QVERIFY(qtobject_item);
220 QVERIFY(qtobject_window);
221 QVERIFY(item_qtobject);
222 QVERIFY(item_item);
223 QVERIFY(item_window);
224 QVERIFY(window_qtobject);
225 QVERIFY(window_item);
226 QVERIFY(window_window);
227
228 QVERIFY(QByteArray(qtobject_item->metaObject()->className()).startsWith("QQuickItem"));
229 QVERIFY(QByteArray(qtobject_window->metaObject()->className()).startsWith("QQuickWindow"));
230 QVERIFY(QByteArray(item_item->metaObject()->className()).startsWith("QQuickItem"));
231 QVERIFY(QByteArray(item_window->metaObject()->className()).startsWith("QQuickWindow"));
232 QVERIFY(QByteArray(window_item->metaObject()->className()).startsWith("QQuickItem"));
233 QVERIFY(QByteArray(window_window->metaObject()->className()).startsWith("QQuickWindow"));
234
235 QCOMPARE(qtobject_qtobject->parent(), qtobjectParent);
236 QCOMPARE(qtobject_item->parent(), qtobjectParent);
237 QCOMPARE(qtobject_window->parent(), qtobjectParent);
238 QCOMPARE(item_qtobject->parent(), itemParent);
239 QCOMPARE(item_item->parent(), itemParent);
240 QCOMPARE(item_window->parent(), itemParent);
241 QCOMPARE(window_qtobject->parent(), windowParent);
242 QCOMPARE(window_item->parent(), windowParent);
243 QCOMPARE(window_window->parent(), windowParent);
244
245 QCOMPARE(qobject_cast<QQuickItem *>(qtobject_item)->parentItem(), (QQuickItem *)nullptr);
246 QCOMPARE(qobject_cast<QQuickWindow *>(qtobject_window)->transientParent(), (QQuickWindow *)nullptr);
247 QCOMPARE(qobject_cast<QQuickItem *>(item_item)->parentItem(), itemParent);
248 QCOMPARE(qobject_cast<QQuickWindow *>(item_window)->transientParent(), itemParent->window());
249 QCOMPARE(qobject_cast<QQuickItem *>(window_item)->parentItem(), windowParent->contentItem());
250 QCOMPARE(qobject_cast<QQuickWindow *>(window_window)->transientParent(), windowParent);
251}
252
253void tst_qqmlcomponent::qmlCreateObjectWithProperties()
254{
255 QQmlEngine engine;
256 QQmlComponent component(&engine, testFileUrl(fileName: "createObjectWithScript.qml"));
257 QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
258 QScopedPointer<QObject> object(component.create());
259 QVERIFY(!object.isNull());
260
261 {
262 QScopedPointer<QObject> testObject1(object->property(name: "declarativerectangle")
263 .value<QObject*>());
264 QVERIFY(testObject1);
265 QCOMPARE(testObject1->parent(), object.data());
266 QCOMPARE(testObject1->property("x").value<int>(), 17);
267 QCOMPARE(testObject1->property("y").value<int>(), 17);
268 QCOMPARE(testObject1->property("color").value<QColor>(), QColor(255,255,255));
269 QCOMPARE(QQmlProperty::read(testObject1.data(),"border.width").toInt(), 3);
270 QCOMPARE(QQmlProperty::read(testObject1.data(),"innerRect.border.width").toInt(), 20);
271 }
272
273 {
274 QScopedPointer<QObject> testObject2(object->property(name: "declarativeitem").value<QObject*>());
275 QVERIFY(testObject2);
276 QCOMPARE(testObject2->parent(), object.data());
277 //QCOMPARE(testObject2->metaObject()->className(), "QDeclarativeItem_QML_2");
278 QCOMPARE(testObject2->property("x").value<int>(), 17);
279 QCOMPARE(testObject2->property("y").value<int>(), 17);
280 QCOMPARE(testObject2->property("testBool").value<bool>(), true);
281 QCOMPARE(testObject2->property("testInt").value<int>(), 17);
282 QCOMPARE(testObject2->property("testObject").value<QObject*>(), object.data());
283 }
284
285 {
286 QScopedPointer<QObject> testBindingObj(object->property(name: "bindingTestObject")
287 .value<QObject*>());
288 QVERIFY(testBindingObj);
289 QCOMPARE(testBindingObj->parent(), object.data());
290 QCOMPARE(testBindingObj->property("testValue").value<int>(), 300);
291 object->setProperty(name: "width", value: 150);
292 QCOMPARE(testBindingObj->property("testValue").value<int>(), 150 * 3);
293 }
294
295 {
296 QScopedPointer<QObject> testBindingThisObj(object->property(name: "bindingThisTestObject")
297 .value<QObject*>());
298 QVERIFY(testBindingThisObj);
299 QCOMPARE(testBindingThisObj->parent(), object.data());
300 QCOMPARE(testBindingThisObj->property("testValue").value<int>(), 900);
301 testBindingThisObj->setProperty(name: "width", value: 200);
302 QCOMPARE(testBindingThisObj->property("testValue").value<int>(), 200 * 3);
303 }
304}
305
306void tst_qqmlcomponent::qmlCreateParentReference()
307{
308 QQmlEngine engine;
309
310 QCOMPARE(engine.outputWarningsToStandardError(), true);
311
312 QQmlTestMessageHandler messageHandler;
313
314 QQmlComponent component(&engine, testFileUrl(fileName: "createParentReference.qml"));
315 QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
316 QObject *object = component.create();
317 QVERIFY(object != nullptr);
318
319 QVERIFY(QMetaObject::invokeMethod(object, "createChild"));
320 delete object;
321
322 engine.setOutputWarningsToStandardError(false);
323 QCOMPARE(engine.outputWarningsToStandardError(), false);
324
325 QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
326}
327
328void tst_qqmlcomponent::async()
329{
330 TestHTTPServer server;
331 QVERIFY2(server.listen(), qPrintable(server.errorString()));
332 server.serveDirectory(dataDirectory());
333
334 QQmlComponent component(&engine);
335 ComponentWatcher watcher(&component);
336 component.loadUrl(url: server.url(documentPath: "/TestComponent.qml"), mode: QQmlComponent::Asynchronous);
337 QCOMPARE(watcher.loading, 1);
338 QTRY_VERIFY(component.isReady());
339 QCOMPARE(watcher.ready, 1);
340 QCOMPARE(watcher.error, 0);
341
342 QObject *object = component.create();
343 QVERIFY(object != nullptr);
344
345 delete object;
346}
347
348void tst_qqmlcomponent::asyncHierarchy()
349{
350 TestHTTPServer server;
351 QVERIFY2(server.listen(), qPrintable(server.errorString()));
352 server.serveDirectory(dataDirectory());
353
354 // ensure that the item hierarchy is compiled correctly.
355 QQmlComponent component(&engine);
356 ComponentWatcher watcher(&component);
357 component.loadUrl(url: server.url(documentPath: "/TestComponent.2.qml"), mode: QQmlComponent::Asynchronous);
358 QCOMPARE(watcher.loading, 1);
359 QTRY_VERIFY(component.isReady());
360 QCOMPARE(watcher.ready, 1);
361 QCOMPARE(watcher.error, 0);
362
363 QObject *root = component.create();
364 QVERIFY(root != nullptr);
365
366 // ensure that the parent-child relationship hierarchy is correct
367 // (use QQuickItem* for all children rather than types which are not publicly exported)
368 QQuickItem *c1 = root->findChild<QQuickItem*>(aName: "c1", options: Qt::FindDirectChildrenOnly);
369 QVERIFY(c1);
370 QQuickItem *c1c1 = c1->findChild<QQuickItem*>(aName: "c1c1", options: Qt::FindDirectChildrenOnly);
371 QVERIFY(c1c1);
372 QQuickItem *c1c2 = c1->findChild<QQuickItem*>(aName: "c1c2", options: Qt::FindDirectChildrenOnly);
373 QVERIFY(c1c2);
374 QQuickItem *c1c2c3 = c1c2->findChild<QQuickItem*>(aName: "c1c2c3", options: Qt::FindDirectChildrenOnly);
375 QVERIFY(c1c2c3);
376 QQuickItem *c2 = root->findChild<QQuickItem*>(aName: "c2", options: Qt::FindDirectChildrenOnly);
377 QVERIFY(c2);
378 QQuickItem *c2c1 = c2->findChild<QQuickItem*>(aName: "c2c1", options: Qt::FindDirectChildrenOnly);
379 QVERIFY(c2c1);
380 QQuickItem *c2c1c1 = c2c1->findChild<QQuickItem*>(aName: "c2c1c1", options: Qt::FindDirectChildrenOnly);
381 QVERIFY(c2c1c1);
382 QQuickItem *c2c1c2 = c2c1->findChild<QQuickItem*>(aName: "c2c1c2", options: Qt::FindDirectChildrenOnly);
383 QVERIFY(c2c1c2);
384
385 // ensure that values and bindings are assigned correctly
386 QVERIFY(root->property("success").toBool());
387
388 delete root;
389}
390
391void tst_qqmlcomponent::asyncForceSync()
392{
393 {
394 // 1) make sure that HTTP URLs cannot be completed synchronously
395 TestHTTPServer server;
396 QVERIFY2(server.listen(), qPrintable(server.errorString()));
397 server.serveDirectory(dataDirectory());
398
399 // ensure that the item hierarchy is compiled correctly.
400 QQmlComponent component(&engine);
401 component.loadUrl(url: server.url(documentPath: "/TestComponent.2.qml"), mode: QQmlComponent::Asynchronous);
402 QCOMPARE(component.status(), QQmlComponent::Loading);
403 QQmlComponent component2(&engine, server.url(documentPath: "/TestComponent.2.qml"), QQmlComponent::PreferSynchronous);
404 QCOMPARE(component2.status(), QQmlComponent::Loading);
405 }
406 {
407 // 2) make sure that file:// URL can be completed synchronously
408
409 // ensure that the item hierarchy is compiled correctly.
410 QQmlComponent component(&engine);
411 component.loadUrl(url: testFileUrl(fileName: "/TestComponent.2.qml"), mode: QQmlComponent::Asynchronous);
412 QCOMPARE(component.status(), QQmlComponent::Loading);
413 QQmlComponent component2(&engine, testFileUrl(fileName: "/TestComponent.2.qml"), QQmlComponent::PreferSynchronous);
414 QCOMPARE(component2.status(), QQmlComponent::Ready);
415 QCOMPARE(component.status(), QQmlComponent::Loading);
416 QTRY_COMPARE_WITH_TIMEOUT(component.status(), QQmlComponent::Ready, 0);
417 }
418}
419
420void tst_qqmlcomponent::componentUrlCanonicalization()
421{
422 // ensure that url canonicalization succeeds so that type information
423 // is not generated multiple times for the same component.
424 {
425 // load components via import
426 QQmlEngine engine;
427 QQmlComponent component(&engine, testFileUrl(fileName: "componentUrlCanonicalization.qml"));
428 QScopedPointer<QObject> object(component.create());
429 QVERIFY(object != nullptr);
430 QVERIFY(object->property("success").toBool());
431 }
432
433 {
434 // load one of the components dynamically, which would trigger
435 // import of the other if it were not already loaded.
436 QQmlEngine engine;
437 QQmlComponent component(&engine, testFileUrl(fileName: "componentUrlCanonicalization.2.qml"));
438 QScopedPointer<QObject> object(component.create());
439 QVERIFY(object != nullptr);
440 QVERIFY(object->property("success").toBool());
441 }
442
443 {
444 // load components with more deeply nested imports
445 QQmlEngine engine;
446 QQmlComponent component(&engine, testFileUrl(fileName: "componentUrlCanonicalization.3.qml"));
447 QScopedPointer<QObject> object(component.create());
448 QVERIFY(object != nullptr);
449 QVERIFY(object->property("success").toBool());
450 }
451
452 {
453 // load components with unusually specified import paths
454 QQmlEngine engine;
455 QQmlComponent component(&engine, testFileUrl(fileName: "componentUrlCanonicalization.4.qml"));
456 QScopedPointer<QObject> object(component.create());
457 QVERIFY(object != nullptr);
458 QVERIFY(object->property("success").toBool());
459 }
460
461 {
462 // Do not crash with various nonsense import paths
463 QQmlEngine engine;
464 QQmlComponent component(&engine, testFileUrl(fileName: "componentUrlCanonicalization.5.qml"));
465 QTest::ignoreMessage(type: QtWarningMsg, message: QLatin1String("QQmlComponent: Component is not ready").data());
466 QScopedPointer<QObject> object(component.create());
467 QVERIFY(object.isNull());
468 }
469}
470
471void tst_qqmlcomponent::onDestructionLookup()
472{
473 QQmlEngine engine;
474 QQmlComponent component(&engine, testFileUrl(fileName: "onDestructionLookup.qml"));
475 QScopedPointer<QObject> object(component.create());
476 gc(engine);
477 QVERIFY(object != nullptr);
478 QVERIFY(object->property("success").toBool());
479}
480
481void tst_qqmlcomponent::onDestructionCount()
482{
483 QQmlEngine engine;
484 QQmlComponent component(&engine, testFileUrl(fileName: "onDestructionCount.qml"));
485
486 QLatin1String warning("Component.onDestruction");
487
488 {
489 // Warning should be emitted during create()
490 QTest::ignoreMessage(type: QtWarningMsg, message: warning.data());
491
492 QScopedPointer<QObject> object(component.create());
493 QVERIFY(object != nullptr);
494 }
495
496 // Warning should not be emitted any further
497 QCOMPARE(engine.outputWarningsToStandardError(), true);
498
499 QStringList warnings;
500 {
501 QQmlTestMessageHandler messageHandler;
502
503 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
504 QCoreApplication::processEvents();
505 warnings = messageHandler.messages();
506 }
507
508 engine.setOutputWarningsToStandardError(false);
509 QCOMPARE(engine.outputWarningsToStandardError(), false);
510
511 QCOMPARE(warnings.count(), 0);
512}
513
514void tst_qqmlcomponent::recursion()
515{
516 QQmlEngine engine;
517 QQmlComponent component(&engine, testFileUrl(fileName: "recursion.qml"));
518
519 QTest::ignoreMessage(type: QtWarningMsg, message: QLatin1String("QQmlComponent: Component creation is recursing - aborting").data());
520 QScopedPointer<QObject> object(component.create());
521 QVERIFY(object != nullptr);
522
523 // Sub-object creation does not succeed
524 QCOMPARE(object->property("success").toBool(), false);
525}
526
527void tst_qqmlcomponent::recursionContinuation()
528{
529 QQmlEngine engine;
530 QQmlComponent component(&engine, testFileUrl(fileName: "recursionContinuation.qml"));
531
532 for (int i = 0; i < 10; ++i)
533 QTest::ignoreMessage(type: QtWarningMsg, message: QLatin1String("QQmlComponent: Component creation is recursing - aborting").data());
534
535 QScopedPointer<QObject> object(component.create());
536 QVERIFY(object != nullptr);
537
538 // Eventual sub-object creation succeeds
539 QVERIFY(object->property("success").toBool());
540}
541
542void tst_qqmlcomponent::partialComponentCreation()
543{
544 const int maxCount = 17;
545 QQmlEngine engine;
546 QScopedPointer<QQmlComponent> components[maxCount];
547 QScopedPointer<QObject> objects[maxCount];
548 QQmlTestMessageHandler messageHandler;
549
550 QCOMPARE(engine.outputWarningsToStandardError(), true);
551
552 for (int i = 0; i < maxCount; i++) {
553 components[i].reset(other: new QQmlComponent(&engine, testFileUrl(fileName: "QtObjectComponent.qml")));
554 objects[i].reset(other: components[i]->beginCreate(engine.rootContext()));
555 QVERIFY(objects[i].isNull() == false);
556 }
557 QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
558
559 for (int i = 0; i < maxCount; i++) {
560 components[i]->completeCreate();
561 }
562 QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
563}
564
565class CallingContextCheckingClass : public QObject
566{
567 Q_OBJECT
568 Q_PROPERTY(int value READ value WRITE setValue)
569public:
570 CallingContextCheckingClass()
571 : m_value(0)
572 {}
573
574 int value() const { return m_value; }
575 void setValue(int v) {
576 scopeObject.clear();
577 callingContextData.setContextData(nullptr);
578
579 m_value = v;
580 QJSEngine *jsEngine = qjsEngine(this);
581 if (!jsEngine)
582 return;
583 QV4::ExecutionEngine *v4 = jsEngine->handle();
584 if (!v4)
585 return;
586 QV4::Scope scope(v4);
587 QV4::Scoped<QV4::QmlContext> qmlContext(scope, v4->qmlContext());
588 if (!qmlContext)
589 return;
590 callingContextData = qmlContext->qmlContext();
591 scopeObject = qmlContext->qmlScope();
592 }
593
594 int m_value;
595 QQmlGuardedContextData callingContextData;
596 QPointer<QObject> scopeObject;
597};
598
599void tst_qqmlcomponent::callingContextForInitialProperties()
600{
601 qmlRegisterType<CallingContextCheckingClass>(uri: "qqmlcomponenttest", versionMajor: 1, versionMinor: 0, qmlName: "CallingContextCheckingClass");
602
603 QQmlComponent testFactory(&engine, testFileUrl(fileName: "callingQmlContextComponent.qml"));
604
605 QQmlComponent component(&engine, testFileUrl(fileName: "callingQmlContext.qml"));
606 QScopedPointer<QObject> root(component.beginCreate(engine.rootContext()));
607 QVERIFY(!root.isNull());
608 root->setProperty(name: "factory", value: QVariant::fromValue(value: &testFactory));
609 component.completeCreate();
610 QTRY_VERIFY(qvariant_cast<QObject *>(root->property("incubatedObject")));
611 QObject *o = qvariant_cast<QObject *>(v: root->property(name: "incubatedObject"));
612 CallingContextCheckingClass *checker = qobject_cast<CallingContextCheckingClass*>(object: o);
613 QVERIFY(checker);
614
615 QVERIFY(!checker->callingContextData.isNull());
616 QVERIFY(checker->callingContextData->urlString().endsWith(QStringLiteral("callingQmlContext.qml")));
617
618 QVERIFY(!checker->scopeObject.isNull());
619 QVERIFY(checker->scopeObject->metaObject()->indexOfProperty("incubatedObject") != -1);
620}
621
622void tst_qqmlcomponent::setNonExistentInitialProperty()
623{
624 QQmlIncubationController controller;
625 QQmlEngine engine;
626 engine.setIncubationController(&controller);
627 QQmlComponent component(&engine, testFileUrl(fileName: "nonExistentInitialProperty.qml"));
628 QScopedPointer<QObject> obj(component.create());
629 QVERIFY(!obj.isNull());
630 QMetaObject::invokeMethod(obj: obj.data(), member: "startIncubation");
631 QJSValue incubatorStatus = obj->property(name: "incubator").value<QJSValue>();
632 incubatorStatus.property(name: "forceCompletion").callWithInstance(instance: incubatorStatus);
633 QJSValue objectWrapper = incubatorStatus.property(name: "object");
634 QVERIFY(objectWrapper.isQObject());
635 QPointer<QObject> object(objectWrapper.toQObject());
636 QVERIFY(object->property("ok").toBool());
637}
638
639void tst_qqmlcomponent::relativeUrl_data()
640{
641 QTest::addColumn<QUrl>(name: "url");
642
643#if !defined(Q_OS_ANDROID)
644 QTest::addRow(format: "fromLocalFile") << QUrl::fromLocalFile(localfile: "data/QtObjectComponent.qml");
645 QTest::addRow(format: "fromLocalFileHash") << QUrl::fromLocalFile(localfile: "data/QtObjectComponent#2.qml");
646 QTest::addRow(format: "constructor") << QUrl("data/QtObjectComponent.qml");
647#endif
648 QTest::addRow(format: "absolute") << QUrl::fromLocalFile(QFINDTESTDATA("data/QtObjectComponent.qml"));
649 QTest::addRow(format: "qrc") << QUrl("qrc:/data/QtObjectComponent.qml");
650}
651
652void tst_qqmlcomponent::relativeUrl()
653{
654 QFETCH(QUrl, url);
655
656 QQmlComponent component(&engine);
657 // Shouldn't assert in QQmlTypeLoader; we want QQmlComponent to assume that
658 // data/QtObjectComponent.qml refers to the data/QtObjectComponent.qml in the current working directory.
659 component.loadUrl(url);
660 QVERIFY2(!component.isError(), qPrintable(component.errorString()));
661}
662
663void tst_qqmlcomponent::setDataNoEngineNoSegfault()
664{
665 QQmlEngine eng;
666 QQmlComponent comp;
667 QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Must provide an engine before calling setData");
668 comp.setData("import QtQuick 1.0; QtObject { }", baseUrl: QUrl(""));
669 QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Must provide an engine before calling create");
670 auto c = comp.create();
671 QVERIFY(!c);
672}
673
674class RequiredDefaultCpp : public QObject
675{
676 Q_OBJECT
677public:
678 Q_PROPERTY(QQuickItem *defaultProperty MEMBER m_defaultProperty NOTIFY defaultPropertyChanged REQUIRED)
679 Q_SIGNAL void defaultPropertyChanged();
680 Q_CLASSINFO("DefaultProperty", "defaultProperty")
681private:
682 QQuickItem *m_defaultProperty = nullptr;
683};
684
685void tst_qqmlcomponent::testRequiredProperties_data()
686{
687 qmlRegisterType<RequiredDefaultCpp>(uri: "qt.test", versionMajor: 1, versionMinor: 0, qmlName: "RequiredDefaultCpp");
688 QTest::addColumn<QUrl>(name: "testFile");
689 QTest::addColumn<bool>(name: "shouldSucceed");
690 QTest::addColumn<QString>(name: "errorMsg");
691
692 QTest::addRow(format: "requiredSetViaChainedAlias") << testFileUrl(fileName: "requiredSetViaChainedAlias.qml") << true << "";
693 QTest::addRow(format: "requiredNotSet") << testFileUrl(fileName: "requiredNotSet.qml") << false << "Required property i was not initialized";
694 QTest::addRow(format: "requiredSetInSameFile") << testFileUrl(fileName: "requiredSetInSameFile.qml") << true << "";
695 QTest::addRow(format: "requiredSetViaAlias1") << testFileUrl(fileName: "requiredSetViaAliasBeforeSameFile.qml") << true << "";
696 QTest::addRow(format: "requiredSetViaAlias2") << testFileUrl(fileName: "requiredSetViaAliasAfterSameFile.qml") << true << "";
697 QTest::addRow(format: "requiredSetViaAlias3") << testFileUrl(fileName: "requiredSetViaAliasParentFile.qml") << true << "";
698 QTest::addRow(format: "shadowing") << testFileUrl(fileName: "shadowing.qml") << false << "Required property i was not initialized";
699 QTest::addRow(format: "setLater") << testFileUrl(fileName: "requiredSetLater.qml") << true << "";
700 QTest::addRow(format: "setViaAliasToSubcomponent") << testFileUrl(fileName: "setViaAliasToSubcomponent.qml") << true << "";
701 QTest::addRow(format: "aliasToSubcomponentNotSet") << testFileUrl(fileName: "aliasToSubcomponentNotSet.qml") << false << "It can be set via the alias property i_alias";
702 QTest::addRow(format: "required default set") << testFileUrl(fileName: "requiredDefault.1.qml") << true << "";
703 QTest::addRow(format: "required default not set") << testFileUrl(fileName: "requiredDefault.2.qml") << false << "Required property requiredDefault was not initialized";
704 QTest::addRow(format: "required default set (C++)") << testFileUrl(fileName: "requiredDefault.3.qml") << true << "";
705 QTest::addRow(format: "required default not set (C++)") << testFileUrl(fileName: "requiredDefault.4.qml") << false << "Required property defaultProperty was not initialized";
706}
707
708
709void tst_qqmlcomponent::testRequiredProperties()
710{
711 QQmlEngine eng;
712 using QScopedObjPointer = QScopedPointer<QObject>;
713 QFETCH(QUrl, testFile);
714 QFETCH(bool, shouldSucceed);
715 QQmlComponent comp(&eng);
716 comp.loadUrl(url: testFile);
717 QScopedObjPointer obj {comp.create()};
718 if (shouldSucceed)
719 QVERIFY(obj);
720 else {
721 QVERIFY(!obj);
722 QFETCH(QString, errorMsg);
723 QVERIFY(comp.errorString().contains(errorMsg));
724 }
725}
726
727void tst_qqmlcomponent::testRequiredPropertiesFromQml()
728{
729 QQmlEngine eng;
730 {
731 QQmlComponent comp(&eng);
732 comp.loadUrl(url: testFileUrl(fileName: "createdFromQml.qml"));
733 QScopedPointer<QObject> obj { comp.create() };
734 QVERIFY(obj);
735 auto root = qvariant_cast<QQuickItem*>(v: obj->property(name: "it"));
736 QVERIFY(root);
737 QCOMPARE(root->property("i").toInt(), 42);
738 }
739 {
740 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, messagePattern: QRegularExpression(".*requiredNotSet.qml:4:5: Required property i was not initialized"));
741 QQmlComponent comp(&eng);
742 comp.loadUrl(url: testFileUrl(fileName: "createdFromQmlFail.qml"));
743 QScopedPointer<QObject> obj { comp.create() };
744 QVERIFY(obj);
745 QCOMPARE(qvariant_cast<QQuickItem *>(obj->property("it")), nullptr);
746 }
747}
748
749struct ComponentWithPublicSetInitial : QQmlComponent
750{
751 using QQmlComponent::QQmlComponent;
752 void setInitialProperties(QObject *o, QVariantMap map)
753 {
754 QQmlComponent::setInitialProperties(component: o, properties: map);
755 }
756};
757
758void tst_qqmlcomponent::testSetInitialProperties()
759{
760 QQmlEngine eng;
761 {
762 // QVariant
763 ComponentWithPublicSetInitial comp(&eng);
764 comp.loadUrl(url: testFileUrl(fileName: "variantBasedInitialization.qml"));
765 QScopedPointer<QObject> obj { comp.beginCreate(eng.rootContext()) };
766 QVERIFY(obj);
767 QUrl myurl = comp.url();
768 QFont myfont;
769 QDateTime mydate = QDateTime::currentDateTime();
770 QPoint mypoint {1,2};
771 QSizeF mysize {0.5, 0.3};
772 QMatrix4x4 matrix {};
773 QQuaternion quat {5.0f, 0.3f, 0.2f, 0.1f};
774 QVector2D vec2 {2.0f, 3.1f};
775 QVector3D vec3 {1.0f, 2.0, 3.0f};
776 QVector4D vec4 {1.0f, 2.0f, 3.0f, 4.0f};
777#define ASJSON(NAME) {QLatin1String(#NAME), NAME}
778 comp.setInitialProperties(o: obj.get(), map: QVariantMap {
779 {QLatin1String("i"), 42},
780 {QLatin1String("b"), true},
781 {QLatin1String("d"), 3.1416},
782 {QLatin1String("s"), QLatin1String("hello world")},
783 {QLatin1String("nothing"), QVariant::fromValue( value: nullptr)},
784 ASJSON(myurl),
785 ASJSON(myfont),
786 ASJSON(mydate),
787 ASJSON(mypoint),
788 ASJSON(mysize),
789 ASJSON(matrix),
790 ASJSON(quat),
791 ASJSON(vec2), ASJSON(vec3), ASJSON(vec4)
792 });
793#undef ASJSON
794 comp.completeCreate();
795 QVERIFY(comp.errors().empty());
796 QCOMPARE(obj->property("i"), 42);
797 QCOMPARE(obj->property("b"), true);
798 QCOMPARE(obj->property("d"), 3.1416);
799 QCOMPARE(obj->property("s"), QLatin1String("hello world"));
800 QCOMPARE(obj->property("nothing"), QVariant::fromValue(nullptr));
801#define COMPARE(NAME) QCOMPARE(obj->property(#NAME), NAME)
802 COMPARE(myurl);
803 COMPARE(myfont);
804 COMPARE(mydate);
805 COMPARE(mypoint);
806 COMPARE(mysize);
807 COMPARE(matrix);
808 COMPARE(quat);
809 COMPARE(vec2);
810 COMPARE(vec3);
811 COMPARE(vec4);
812#undef COMPARE
813
814 }
815 {
816 // createWithInitialProperties convenience function
817 QQmlComponent comp(&eng);
818 comp.loadUrl(url: testFileUrl(fileName: "requiredNotSet.qml"));
819 QScopedPointer<QObject> obj {comp.createWithInitialProperties( initialProperties: QVariantMap { {QLatin1String("i"), QJsonValue{42}} })};
820 QVERIFY(obj);
821 QCOMPARE(obj->property("i"), 42);
822 }
823 {
824 // createWithInitialProperties: setting a nonexistent property
825 QQmlComponent comp(&eng);
826 comp.loadUrl(url: testFileUrl(fileName: "allJSONTypes.qml"));
827 QScopedPointer<QObject> obj {
828 comp.createWithInitialProperties(initialProperties: QVariantMap { {"notThePropertiesYoureLookingFor", 42} })
829 };
830 QVERIFY(obj);
831 QVERIFY(comp.errorString().contains("Setting initial properties failed: Item does not have a property called notThePropertiesYoureLookingFor"));
832 }
833}
834
835QTEST_MAIN(tst_qqmlcomponent)
836
837#include "tst_qqmlcomponent.moc"
838

source code of qtdeclarative/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp