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 | |
46 | class MyIC : public QObject, public QQmlIncubationController |
47 | { |
48 | Q_OBJECT |
49 | public: |
50 | MyIC() { startTimer(interval: 5); } |
51 | protected: |
52 | virtual void timerEvent(QTimerEvent*) { |
53 | incubateFor(msecs: 5); |
54 | } |
55 | }; |
56 | |
57 | class ComponentWatcher : public QObject |
58 | { |
59 | Q_OBJECT |
60 | public: |
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 | |
70 | public 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 | |
88 | static void gc(QQmlEngine &engine) |
89 | { |
90 | engine.collectGarbage(); |
91 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
92 | QCoreApplication::processEvents(); |
93 | } |
94 | |
95 | class tst_qqmlcomponent : public QQmlDataTest |
96 | { |
97 | Q_OBJECT |
98 | public: |
99 | tst_qqmlcomponent() { engine.setIncubationController(&ic); } |
100 | |
101 | private 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 | |
129 | private: |
130 | QQmlEngine engine; |
131 | MyIC ic; |
132 | }; |
133 | |
134 | void 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 | |
148 | void 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 | |
162 | void 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 | |
175 | void 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 | |
184 | void 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 | |
193 | void 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 | |
253 | void 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 | |
306 | void 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 | |
328 | void 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 | |
348 | void 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 | |
391 | void 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 | |
420 | void 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 | |
471 | void 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 | |
481 | void 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 | |
514 | void 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 | |
527 | void 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 | |
542 | void 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 | |
565 | class CallingContextCheckingClass : public QObject |
566 | { |
567 | Q_OBJECT |
568 | Q_PROPERTY(int value READ value WRITE setValue) |
569 | public: |
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 | |
599 | void 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 | |
622 | void 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 | |
639 | void 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 | |
652 | void 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 | |
663 | void 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 | |
674 | class RequiredDefaultCpp : public QObject |
675 | { |
676 | Q_OBJECT |
677 | public: |
678 | Q_PROPERTY(QQuickItem *defaultProperty MEMBER m_defaultProperty NOTIFY defaultPropertyChanged REQUIRED) |
679 | Q_SIGNAL void defaultPropertyChanged(); |
680 | Q_CLASSINFO("DefaultProperty" , "defaultProperty" ) |
681 | private: |
682 | QQuickItem *m_defaultProperty = nullptr; |
683 | }; |
684 | |
685 | void 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 | |
709 | void 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 | |
727 | void 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 | |
749 | struct 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 | |
758 | void 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 | |
835 | QTEST_MAIN(tst_qqmlcomponent) |
836 | |
837 | #include "tst_qqmlcomponent.moc" |
838 | |