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 | |
29 | #include "../../shared/util.h" |
30 | #include <QQmlEngine> |
31 | #include <QQmlContext> |
32 | #include <QNetworkAccessManager> |
33 | #include <QPointer> |
34 | #include <QDir> |
35 | #include <QStandardPaths> |
36 | #include <QSignalSpy> |
37 | #include <QDebug> |
38 | #include <QBuffer> |
39 | #include <QCryptographicHash> |
40 | #include <QQmlComponent> |
41 | #include <QQmlNetworkAccessManagerFactory> |
42 | #include <QQmlExpression> |
43 | #include <QQmlIncubationController> |
44 | #include <QTemporaryDir> |
45 | #include <private/qqmlengine_p.h> |
46 | #include <private/qqmltypedata_p.h> |
47 | #include <QQmlAbstractUrlInterceptor> |
48 | |
49 | class tst_qqmlengine : public QQmlDataTest |
50 | { |
51 | Q_OBJECT |
52 | public: |
53 | tst_qqmlengine() {} |
54 | |
55 | private slots: |
56 | void initTestCase() override; |
57 | void rootContext(); |
58 | void networkAccessManager(); |
59 | void synchronousNetworkAccessManager(); |
60 | void baseUrl(); |
61 | void contextForObject(); |
62 | void offlineStoragePath(); |
63 | void offlineDatabaseStoragePath(); |
64 | void clearComponentCache(); |
65 | void trimComponentCache(); |
66 | void trimComponentCache_data(); |
67 | void repeatedCompilation(); |
68 | void failedCompilation(); |
69 | void failedCompilation_data(); |
70 | void outputWarningsToStandardError(); |
71 | void objectOwnership(); |
72 | void multipleEngines(); |
73 | void qtqmlModule_data(); |
74 | void qtqmlModule(); |
75 | void urlInterceptor_data(); |
76 | void urlInterceptor(); |
77 | void qmlContextProperties(); |
78 | void testGCCorruption(); |
79 | void testGroupedPropertyRevisions(); |
80 | void componentFromEval(); |
81 | void qrcUrls(); |
82 | void cppSignalAndEval(); |
83 | void singletonInstance(); |
84 | void aggressiveGc(); |
85 | void cachedGetterLookup_qtbug_75335(); |
86 | void createComponentOnSingletonDestruction(); |
87 | void uiLanguage(); |
88 | |
89 | public slots: |
90 | QObject *createAQObjectForOwnershipTest () |
91 | { |
92 | static QObject *ptr = new QObject(); |
93 | return ptr; |
94 | } |
95 | |
96 | private: |
97 | QTemporaryDir m_tempDir; |
98 | }; |
99 | |
100 | void tst_qqmlengine::initTestCase() |
101 | { |
102 | QVERIFY2(m_tempDir.isValid(), qPrintable(m_tempDir.errorString())); |
103 | QQmlDataTest::initTestCase(); |
104 | } |
105 | |
106 | void tst_qqmlengine::rootContext() |
107 | { |
108 | QQmlEngine engine; |
109 | |
110 | QVERIFY(engine.rootContext()); |
111 | |
112 | QCOMPARE(engine.rootContext()->engine(), &engine); |
113 | QVERIFY(!engine.rootContext()->parentContext()); |
114 | } |
115 | |
116 | class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory |
117 | { |
118 | public: |
119 | NetworkAccessManagerFactory() : manager(nullptr) {} |
120 | |
121 | QNetworkAccessManager *create(QObject *parent) { |
122 | manager = new QNetworkAccessManager(parent); |
123 | return manager; |
124 | } |
125 | |
126 | QNetworkAccessManager *manager; |
127 | }; |
128 | |
129 | void tst_qqmlengine::networkAccessManager() |
130 | { |
131 | QQmlEngine *engine = new QQmlEngine; |
132 | |
133 | // Test QQmlEngine created manager |
134 | QPointer<QNetworkAccessManager> manager = engine->networkAccessManager(); |
135 | QVERIFY(manager != nullptr); |
136 | delete engine; |
137 | |
138 | // Test factory created manager |
139 | engine = new QQmlEngine; |
140 | NetworkAccessManagerFactory factory; |
141 | engine->setNetworkAccessManagerFactory(&factory); |
142 | QCOMPARE(engine->networkAccessManagerFactory(), &factory); |
143 | QNetworkAccessManager *engineNam = engine->networkAccessManager(); // calls NetworkAccessManagerFactory::create() |
144 | QCOMPARE(engineNam, factory.manager); |
145 | delete engine; |
146 | } |
147 | |
148 | class ImmediateReply : public QNetworkReply { |
149 | |
150 | Q_OBJECT |
151 | |
152 | public: |
153 | ImmediateReply() { |
154 | setFinished(true); |
155 | } |
156 | virtual qint64 readData(char* , qint64 ) { |
157 | return 0; |
158 | } |
159 | virtual void abort() { } |
160 | }; |
161 | |
162 | class ImmediateManager : public QNetworkAccessManager { |
163 | |
164 | Q_OBJECT |
165 | |
166 | public: |
167 | ImmediateManager(QObject *parent = nullptr) : QNetworkAccessManager(parent) { |
168 | } |
169 | |
170 | QNetworkReply *createRequest(Operation, const QNetworkRequest & , QIODevice * outgoingData = nullptr) { |
171 | Q_UNUSED(outgoingData); |
172 | return new ImmediateReply; |
173 | } |
174 | }; |
175 | |
176 | class ImmediateFactory : public QQmlNetworkAccessManagerFactory { |
177 | |
178 | public: |
179 | QNetworkAccessManager *create(QObject *) { |
180 | return new ImmediateManager; |
181 | } |
182 | }; |
183 | |
184 | void tst_qqmlengine::synchronousNetworkAccessManager() |
185 | { |
186 | ImmediateFactory factory; |
187 | QQmlEngine engine; |
188 | engine.setNetworkAccessManagerFactory(&factory); |
189 | QQmlComponent c(&engine, QUrl("myScheme://test.qml" )); |
190 | // reply is finished, so should not be in loading state. |
191 | QVERIFY(!c.isLoading()); |
192 | } |
193 | |
194 | |
195 | void tst_qqmlengine::baseUrl() |
196 | { |
197 | QQmlEngine engine; |
198 | |
199 | QUrl cwd = QUrl::fromLocalFile(localfile: QDir::currentPath() + QDir::separator()); |
200 | |
201 | QCOMPARE(engine.baseUrl(), cwd); |
202 | QCOMPARE(engine.rootContext()->resolvedUrl(QUrl("main.qml" )), cwd.resolved(QUrl("main.qml" ))); |
203 | |
204 | QDir dir = QDir::current(); |
205 | dir.cdUp(); |
206 | QVERIFY(dir != QDir::current()); |
207 | QDir::setCurrent(dir.path()); |
208 | QCOMPARE(QDir::current(), dir); |
209 | |
210 | QUrl cwd2 = QUrl::fromLocalFile(localfile: QDir::currentPath() + QDir::separator()); |
211 | QCOMPARE(engine.baseUrl(), cwd2); |
212 | QCOMPARE(engine.rootContext()->resolvedUrl(QUrl("main.qml" )), cwd2.resolved(QUrl("main.qml" ))); |
213 | |
214 | engine.setBaseUrl(cwd); |
215 | QCOMPARE(engine.baseUrl(), cwd); |
216 | QCOMPARE(engine.rootContext()->resolvedUrl(QUrl("main.qml" )), cwd.resolved(QUrl("main.qml" ))); |
217 | |
218 | |
219 | const QString testPath = QDir::currentPath() + QLatin1String("/" ); |
220 | const QString rootPath = QDir::rootPath(); |
221 | engine.setBaseUrl(QUrl()); |
222 | |
223 | // Check that baseUrl returns a url to a localFile |
224 | QCOMPARE(engine.baseUrl().toLocalFile(), testPath); |
225 | |
226 | QDir::setCurrent(QDir::rootPath()); |
227 | |
228 | // Make sure this also works when in the rootPath |
229 | QCOMPARE(engine.baseUrl().toLocalFile(), rootPath); |
230 | } |
231 | |
232 | void tst_qqmlengine::contextForObject() |
233 | { |
234 | QQmlEngine *engine = new QQmlEngine; |
235 | |
236 | // Test null-object |
237 | QVERIFY(!QQmlEngine::contextForObject(nullptr)); |
238 | |
239 | // Test an object with no context |
240 | QObject object; |
241 | QVERIFY(!QQmlEngine::contextForObject(&object)); |
242 | |
243 | // Test setting null-object |
244 | QQmlEngine::setContextForObject(nullptr, engine->rootContext()); |
245 | |
246 | // Test setting null-context |
247 | QQmlEngine::setContextForObject(&object, nullptr); |
248 | |
249 | // Test setting context |
250 | QQmlEngine::setContextForObject(&object, engine->rootContext()); |
251 | QCOMPARE(QQmlEngine::contextForObject(&object), engine->rootContext()); |
252 | |
253 | QQmlContext context(engine->rootContext()); |
254 | |
255 | // Try changing context |
256 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlEngine::setContextForObject(): Object already has a QQmlContext" ); |
257 | QQmlEngine::setContextForObject(&object, &context); |
258 | QCOMPARE(QQmlEngine::contextForObject(&object), engine->rootContext()); |
259 | |
260 | // Delete context |
261 | delete engine; engine = nullptr; |
262 | QVERIFY(!QQmlEngine::contextForObject(&object)); |
263 | } |
264 | |
265 | void tst_qqmlengine::offlineStoragePath() |
266 | { |
267 | // Without these set, QDesktopServices::storageLocation returns |
268 | // strings with extra "//" at the end. We set them to ignore this problem. |
269 | qApp->setApplicationName("tst_qqmlengine" ); |
270 | qApp->setOrganizationName("QtProject" ); |
271 | qApp->setOrganizationDomain("www.qt-project.org" ); |
272 | |
273 | QQmlEngine engine; |
274 | |
275 | QString dataLocation = QStandardPaths::writableLocation(type: QStandardPaths::DataLocation); |
276 | |
277 | QCOMPARE(dataLocation.isEmpty(), engine.offlineStoragePath().isEmpty()); |
278 | |
279 | QDir dir(dataLocation); |
280 | dir.mkpath(dirPath: "QML" ); |
281 | dir.cd(dirName: "QML" ); |
282 | dir.mkpath(dirPath: "OfflineStorage" ); |
283 | dir.cd(dirName: "OfflineStorage" ); |
284 | |
285 | QCOMPARE(QDir::fromNativeSeparators(engine.offlineStoragePath()), dir.path()); |
286 | |
287 | engine.setOfflineStoragePath(QDir::homePath()); |
288 | QCOMPARE(engine.offlineStoragePath(), QDir::homePath()); |
289 | } |
290 | |
291 | void tst_qqmlengine::offlineDatabaseStoragePath() |
292 | { |
293 | // Without these set, QDesktopServices::storageLocation returns |
294 | // strings with extra "//" at the end. We set them to ignore this problem. |
295 | qApp->setApplicationName("tst_qqmlengine" ); |
296 | qApp->setOrganizationName("QtProject" ); |
297 | qApp->setOrganizationDomain("www.qt-project.org" ); |
298 | |
299 | QQmlEngine engine; |
300 | QString dataLocation = QStandardPaths::writableLocation(type: QStandardPaths::DataLocation); |
301 | const QString databaseName = QLatin1String("foo" ); |
302 | QString databaseLocation = engine.offlineStorageDatabaseFilePath(databaseName); |
303 | QCOMPARE(dataLocation.isEmpty(), databaseLocation.isEmpty()); |
304 | |
305 | QDir dir(dataLocation); |
306 | dir.mkpath(dirPath: "QML" ); |
307 | dir.cd(dirName: "QML" ); |
308 | dir.mkpath(dirPath: "OfflineStorage" ); |
309 | dir.cd(dirName: "OfflineStorage" ); |
310 | dir.mkpath(dirPath: "Databases" ); |
311 | dir.cd(dirName: "Databases" ); |
312 | QCOMPARE(QFileInfo(databaseLocation).dir().path(), dir.path()); |
313 | |
314 | QCryptographicHash md5(QCryptographicHash::Md5); |
315 | md5.addData(data: databaseName.toUtf8()); |
316 | QCOMPARE(databaseLocation, QDir::toNativeSeparators(dir.filePath(QLatin1String(md5.result().toHex())))); |
317 | } |
318 | |
319 | void tst_qqmlengine::clearComponentCache() |
320 | { |
321 | QQmlEngine engine; |
322 | |
323 | const QString fileName = m_tempDir.filePath(QStringLiteral("temp.qml" )); |
324 | const QUrl fileUrl = QUrl::fromLocalFile(localfile: fileName); |
325 | |
326 | // Create original qml file |
327 | { |
328 | QFile file(fileName); |
329 | QVERIFY(file.open(QIODevice::WriteOnly)); |
330 | file.write(data: "import QtQuick 2.0\nQtObject {\nproperty int test: 10\n}\n" ); |
331 | file.close(); |
332 | } |
333 | |
334 | // Test "test" property |
335 | { |
336 | QQmlComponent component(&engine, fileUrl); |
337 | QObject *obj = component.create(); |
338 | QVERIFY(obj != nullptr); |
339 | QCOMPARE(obj->property("test" ).toInt(), 10); |
340 | delete obj; |
341 | } |
342 | |
343 | // Modify qml file |
344 | { |
345 | // On macOS with HFS+ the precision of file times is measured in seconds, so to ensure that |
346 | // the newly written file has a modification date newer than an existing cache file, we must |
347 | // wait. |
348 | // Similar effects of lacking precision have been observed on some Linux systems. |
349 | QThread::sleep(1); |
350 | |
351 | QFile file(fileName); |
352 | QVERIFY(file.open(QIODevice::WriteOnly)); |
353 | file.write(data: "import QtQuick 2.0\nQtObject {\nproperty int test: 11\n}\n" ); |
354 | file.close(); |
355 | } |
356 | |
357 | // Test cache hit |
358 | { |
359 | QQmlComponent component(&engine, fileUrl); |
360 | QObject *obj = component.create(); |
361 | QVERIFY(obj != nullptr); |
362 | QCOMPARE(obj->property("test" ).toInt(), 10); |
363 | delete obj; |
364 | } |
365 | |
366 | // Clear cache |
367 | engine.clearComponentCache(); |
368 | |
369 | // Test cache refresh |
370 | { |
371 | QQmlComponent component(&engine, fileUrl); |
372 | QObject *obj = component.create(); |
373 | QVERIFY(obj != nullptr); |
374 | QCOMPARE(obj->property("test" ).toInt(), 11); |
375 | delete obj; |
376 | } |
377 | |
378 | // Regular Synchronous loading will leave us with an event posted |
379 | // to the gui thread and an extra refcount that will only be dropped after the |
380 | // event delivery. Call sendPostedEvents() to get rid of it so that |
381 | // the temporary directory can be removed. |
382 | QCoreApplication::sendPostedEvents(); |
383 | } |
384 | |
385 | struct ComponentCacheFunctions : public QObject, public QQmlIncubationController |
386 | { |
387 | Q_OBJECT |
388 | public: |
389 | QQmlEngine *engine; |
390 | |
391 | ComponentCacheFunctions(QQmlEngine &e) : engine(&e) {} |
392 | |
393 | Q_INVOKABLE void trim() |
394 | { |
395 | // Wait for any pending deletions to occur |
396 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
397 | QCoreApplication::processEvents(); |
398 | |
399 | // There might be JS function objects around that hold a last ref to the compilation unit that's |
400 | // keeping the type compilation data (CompilationUnit) around. Let's collect them as well so that |
401 | // trim works well. |
402 | engine->collectGarbage(); |
403 | |
404 | engine->trimComponentCache(); |
405 | } |
406 | |
407 | Q_INVOKABLE bool isTypeLoaded(QString file) |
408 | { |
409 | return QQmlEnginePrivate::get(e: engine)->isTypeLoaded(url: tst_qqmlengine::instance()->testFileUrl(fileName: file)); |
410 | } |
411 | |
412 | Q_INVOKABLE bool isScriptLoaded(QString file) |
413 | { |
414 | return QQmlEnginePrivate::get(e: engine)->isScriptLoaded(url: tst_qqmlengine::instance()->testFileUrl(fileName: file)); |
415 | } |
416 | |
417 | Q_INVOKABLE void beginIncubation() |
418 | { |
419 | startTimer(interval: 0); |
420 | } |
421 | |
422 | Q_INVOKABLE void waitForIncubation() |
423 | { |
424 | while (incubatingObjectCount() > 0) { |
425 | QCoreApplication::processEvents(); |
426 | } |
427 | } |
428 | |
429 | private: |
430 | virtual void timerEvent(QTimerEvent *) |
431 | { |
432 | incubateFor(msecs: 1000); |
433 | } |
434 | }; |
435 | |
436 | void tst_qqmlengine::trimComponentCache() |
437 | { |
438 | QFETCH(QString, file); |
439 | |
440 | QQmlEngine engine; |
441 | ComponentCacheFunctions componentCache(engine); |
442 | engine.setIncubationController(&componentCache); |
443 | |
444 | QQmlComponent component(&engine, testFileUrl(fileName: file)); |
445 | QVERIFY2(component.isReady(), qPrintable(component.errorString())); |
446 | QScopedPointer<QObject> object(component.createWithInitialProperties(initialProperties: { |
447 | {"componentCache" , QVariant::fromValue(value: &componentCache)} |
448 | })); |
449 | QVERIFY(object != nullptr); |
450 | QCOMPARE(object->property("success" ).toBool(), true); |
451 | } |
452 | |
453 | void tst_qqmlengine::trimComponentCache_data() |
454 | { |
455 | QTest::addColumn<QString>(name: "file" ); |
456 | |
457 | // The various tests here are for two types of components: those that are |
458 | // empty apart from their inherited elements, and those that define new properties. |
459 | // For each there are five types of composition: extension, aggregation, |
460 | // aggregation via component, property and object-created-via-transient-component. |
461 | foreach (const QString &test, (QStringList() << "EmptyComponent" |
462 | << "VMEComponent" |
463 | << "EmptyExtendEmptyComponent" |
464 | << "VMEExtendEmptyComponent" |
465 | << "EmptyExtendVMEComponent" |
466 | << "VMEExtendVMEComponent" |
467 | << "EmptyAggregateEmptyComponent" |
468 | << "VMEAggregateEmptyComponent" |
469 | << "EmptyAggregateVMEComponent" |
470 | << "VMEAggregateVMEComponent" |
471 | << "EmptyPropertyEmptyComponent" |
472 | << "VMEPropertyEmptyComponent" |
473 | << "EmptyPropertyVMEComponent" |
474 | << "VMEPropertyVMEComponent" |
475 | << "VMETransientEmptyComponent" |
476 | << "VMETransientVMEComponent" )) { |
477 | // For these cases, we first test that the component instance keeps the components |
478 | // referenced, and then that the instantiated object keeps the components referenced |
479 | for (int i = 1; i <= 2; ++i) { |
480 | QString name(QString("%1-%2" ).arg(a: test).arg(a: i)); |
481 | QString file(QString("test%1.%2.qml" ).arg(a: test).arg(a: i)); |
482 | QTest::newRow(dataTag: name.toLatin1().constData()) << file; |
483 | } |
484 | } |
485 | |
486 | // Test that a transient component is correctly referenced |
487 | QTest::newRow(dataTag: "TransientComponent-1" ) << "testTransientComponent.1.qml" ; |
488 | QTest::newRow(dataTag: "TransientComponent-2" ) << "testTransientComponent.2.qml" ; |
489 | |
490 | // Test that components can be reloaded after unloading |
491 | QTest::newRow(dataTag: "ReloadComponent" ) << "testReloadComponent.qml" ; |
492 | |
493 | // Test that components are correctly referenced when dynamically loaded |
494 | QTest::newRow(dataTag: "LoaderComponent" ) << "testLoaderComponent.qml" ; |
495 | |
496 | // Test that components are correctly referenced when incubated |
497 | QTest::newRow(dataTag: "IncubatedComponent" ) << "testIncubatedComponent.qml" ; |
498 | |
499 | // Test that a top-level omponents is correctly referenced |
500 | QTest::newRow(dataTag: "TopLevelComponent" ) << "testTopLevelComponent.qml" ; |
501 | |
502 | // TODO: |
503 | // Test that scripts are unloaded when no longer referenced |
504 | QTest::newRow(dataTag: "ScriptComponent" ) << "testScriptComponent.qml" ; |
505 | } |
506 | |
507 | void tst_qqmlengine::repeatedCompilation() |
508 | { |
509 | QQmlEngine engine; |
510 | |
511 | for (int i = 0; i < 100; ++i) { |
512 | engine.collectGarbage(); |
513 | engine.trimComponentCache(); |
514 | |
515 | QQmlComponent component(&engine, testFileUrl(fileName: "repeatedCompilation.qml" )); |
516 | QVERIFY(component.isReady()); |
517 | QScopedPointer<QObject> object(component.create()); |
518 | QVERIFY(object != nullptr); |
519 | QCOMPARE(object->property("success" ).toBool(), true); |
520 | } |
521 | } |
522 | |
523 | void tst_qqmlengine::failedCompilation() |
524 | { |
525 | QFETCH(QString, file); |
526 | |
527 | QQmlEngine engine; |
528 | |
529 | QQmlComponent component(&engine, testFileUrl(fileName: file)); |
530 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
531 | QVERIFY(!component.isReady()); |
532 | QScopedPointer<QObject> object(component.create()); |
533 | QVERIFY(object.isNull()); |
534 | |
535 | engine.collectGarbage(); |
536 | engine.trimComponentCache(); |
537 | engine.clearComponentCache(); |
538 | } |
539 | |
540 | void tst_qqmlengine::failedCompilation_data() |
541 | { |
542 | QTest::addColumn<QString>(name: "file" ); |
543 | |
544 | QTest::newRow(dataTag: "Invalid URL" ) << "failedCompilation.does.not.exist.qml" ; |
545 | QTest::newRow(dataTag: "Invalid content" ) << "failedCompilation.1.qml" ; |
546 | } |
547 | |
548 | void tst_qqmlengine::outputWarningsToStandardError() |
549 | { |
550 | QQmlEngine engine; |
551 | |
552 | QCOMPARE(engine.outputWarningsToStandardError(), true); |
553 | |
554 | QQmlComponent c(&engine); |
555 | c.setData("import QtQuick 2.0; QtObject { property int a: undefined }" , baseUrl: QUrl()); |
556 | |
557 | QVERIFY(c.isReady()); |
558 | |
559 | QQmlTestMessageHandler messageHandler; |
560 | |
561 | QObject *o = c.create(); |
562 | |
563 | QVERIFY(o != nullptr); |
564 | delete o; |
565 | |
566 | QCOMPARE(messageHandler.messages().count(), 1); |
567 | QCOMPARE(messageHandler.messages().at(0), QLatin1String("<Unknown File>:1:32: Unable to assign [undefined] to int" )); |
568 | messageHandler.clear(); |
569 | |
570 | engine.setOutputWarningsToStandardError(false); |
571 | QCOMPARE(engine.outputWarningsToStandardError(), false); |
572 | |
573 | o = c.create(); |
574 | |
575 | QVERIFY(o != nullptr); |
576 | delete o; |
577 | |
578 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
579 | } |
580 | |
581 | void tst_qqmlengine::objectOwnership() |
582 | { |
583 | { |
584 | QCOMPARE(QQmlEngine::objectOwnership(nullptr), QQmlEngine::CppOwnership); |
585 | QQmlEngine::setObjectOwnership(nullptr, QQmlEngine::JavaScriptOwnership); |
586 | QCOMPARE(QQmlEngine::objectOwnership(nullptr), QQmlEngine::CppOwnership); |
587 | } |
588 | |
589 | { |
590 | QObject o; |
591 | QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::CppOwnership); |
592 | QQmlEngine::setObjectOwnership(&o, QQmlEngine::CppOwnership); |
593 | QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::CppOwnership); |
594 | QQmlEngine::setObjectOwnership(&o, QQmlEngine::JavaScriptOwnership); |
595 | QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::JavaScriptOwnership); |
596 | QQmlEngine::setObjectOwnership(&o, QQmlEngine::CppOwnership); |
597 | QCOMPARE(QQmlEngine::objectOwnership(&o), QQmlEngine::CppOwnership); |
598 | } |
599 | |
600 | { |
601 | QQmlEngine engine; |
602 | QQmlComponent c(&engine); |
603 | c.setData("import QtQuick 2.0; QtObject { property QtObject object: QtObject {} }" , baseUrl: QUrl()); |
604 | |
605 | QObject *o = c.create(); |
606 | QVERIFY(o != nullptr); |
607 | |
608 | QCOMPARE(QQmlEngine::objectOwnership(o), QQmlEngine::CppOwnership); |
609 | |
610 | QObject *o2 = qvariant_cast<QObject *>(v: o->property(name: "object" )); |
611 | QCOMPARE(QQmlEngine::objectOwnership(o2), QQmlEngine::JavaScriptOwnership); |
612 | |
613 | delete o; |
614 | } |
615 | { |
616 | QObject *ptr = createAQObjectForOwnershipTest(); |
617 | QSignalSpy spy(ptr, SIGNAL(destroyed())); |
618 | { |
619 | QQmlEngine engine; |
620 | QQmlComponent c(&engine); |
621 | QQmlEngine::setObjectOwnership(ptr, QQmlEngine::JavaScriptOwnership); |
622 | c.setData("import QtQuick 2.0; Item { required property QtObject test; property int data: test.createAQObjectForOwnershipTest() ? 0 : 1 }" , baseUrl: QUrl()); |
623 | QVERIFY(c.isReady()); |
624 | QObject *o = c.createWithInitialProperties( initialProperties: {{"test" , QVariant::fromValue(value: this)}} ); |
625 | QVERIFY(o != nullptr); |
626 | } |
627 | QTRY_VERIFY(spy.count()); |
628 | } |
629 | { |
630 | QObject *ptr = new QObject(); |
631 | QSignalSpy spy(ptr, SIGNAL(destroyed())); |
632 | { |
633 | QQmlEngine engine; |
634 | QQmlComponent c(&engine); |
635 | QQmlEngine::setObjectOwnership(ptr, QQmlEngine::JavaScriptOwnership); |
636 | c.setData("import QtQuick 2.0; QtObject { required property QtObject test; property var object: { var i = test; test ? 0 : 1 } }" , baseUrl: QUrl()); |
637 | QVERIFY(c.isReady()); |
638 | QObject *o = c.createWithInitialProperties(initialProperties: {{"test" , QVariant::fromValue(value: ptr)}}); |
639 | QVERIFY(o != nullptr); |
640 | QQmlProperty testProp(o, "test" ); |
641 | testProp.write(QVariant::fromValue<QObject*>(value: nullptr)); |
642 | } |
643 | QTRY_VERIFY(spy.count()); |
644 | } |
645 | } |
646 | |
647 | // Test an object can be accessed by multiple engines |
648 | void tst_qqmlengine::multipleEngines() |
649 | { |
650 | QObject o; |
651 | o.setObjectName("TestName" ); |
652 | |
653 | // Simultaneous engines |
654 | { |
655 | QQmlEngine engine1; |
656 | QQmlEngine engine2; |
657 | engine1.rootContext()->setContextProperty("object" , &o); |
658 | engine2.rootContext()->setContextProperty("object" , &o); |
659 | |
660 | QQmlExpression expr1(engine1.rootContext(), nullptr, QString("object.objectName" )); |
661 | QQmlExpression expr2(engine2.rootContext(), nullptr, QString("object.objectName" )); |
662 | |
663 | QCOMPARE(expr1.evaluate().toString(), QString("TestName" )); |
664 | QCOMPARE(expr2.evaluate().toString(), QString("TestName" )); |
665 | } |
666 | |
667 | // Serial engines |
668 | { |
669 | QQmlEngine engine1; |
670 | engine1.rootContext()->setContextProperty("object" , &o); |
671 | QQmlExpression expr1(engine1.rootContext(), nullptr, QString("object.objectName" )); |
672 | QCOMPARE(expr1.evaluate().toString(), QString("TestName" )); |
673 | } |
674 | { |
675 | QQmlEngine engine1; |
676 | engine1.rootContext()->setContextProperty("object" , &o); |
677 | QQmlExpression expr1(engine1.rootContext(), nullptr, QString("object.objectName" )); |
678 | QCOMPARE(expr1.evaluate().toString(), QString("TestName" )); |
679 | } |
680 | } |
681 | |
682 | void tst_qqmlengine::qtqmlModule_data() |
683 | { |
684 | QTest::addColumn<QUrl>(name: "testFile" ); |
685 | QTest::addColumn<QString>(name: "expectedError" ); |
686 | QTest::addColumn<QStringList>(name: "expectedWarnings" ); |
687 | |
688 | QTest::newRow(dataTag: "import QtQml of correct version (2.0)" ) |
689 | << testFileUrl(fileName: "qtqmlModule.1.qml" ) |
690 | << QString() |
691 | << QStringList(); |
692 | |
693 | QTest::newRow(dataTag: "import QtQml of incorrect version (3.0)" ) |
694 | << testFileUrl(fileName: "qtqmlModule.2.qml" ) |
695 | << QString(testFileUrl(fileName: "qtqmlModule.2.qml" ).toString() + QLatin1String(":1 module \"QtQml\" version 3.0 is not installed\n" )) |
696 | << QStringList(); |
697 | |
698 | QTest::newRow(dataTag: "import QtQml of incorrect version (1.0)" ) |
699 | << testFileUrl(fileName: "qtqmlModule.3.qml" ) |
700 | << QString(testFileUrl(fileName: "qtqmlModule.3.qml" ).toString() + QLatin1String(":1 module \"QtQml\" version 1.0 is not installed\n" )) |
701 | << QStringList(); |
702 | |
703 | QTest::newRow(dataTag: "import QtQml of incorrect version (2.50)" ) |
704 | << testFileUrl(fileName: "qtqmlModule.4.qml" ) |
705 | << QString(testFileUrl(fileName: "qtqmlModule.4.qml" ).toString() + QLatin1String(":1 module \"QtQml\" version 2.50 is not installed\n" )) |
706 | << QStringList(); |
707 | |
708 | QTest::newRow(dataTag: "QtQml 2.0 module provides Component, QtObject, Connections, Binding and Timer" ) |
709 | << testFileUrl(fileName: "qtqmlModule.5.qml" ) |
710 | << QString() |
711 | << QStringList(); |
712 | |
713 | QTest::newRow(dataTag: "can import QtQml then QtQuick" ) |
714 | << testFileUrl(fileName: "qtqmlModule.6.qml" ) |
715 | << QString() |
716 | << QStringList(); |
717 | |
718 | QTest::newRow(dataTag: "can import QtQuick then QtQml" ) |
719 | << testFileUrl(fileName: "qtqmlModule.7.qml" ) |
720 | << QString() |
721 | << QStringList(); |
722 | |
723 | QTest::newRow(dataTag: "no import results in no QtObject availability" ) |
724 | << testFileUrl(fileName: "qtqmlModule.8.qml" ) |
725 | << QString(testFileUrl(fileName: "qtqmlModule.8.qml" ).toString() + QLatin1String(":4 QtObject is not a type\n" )) |
726 | << QStringList(); |
727 | |
728 | QTest::newRow(dataTag: "importing QtQml only results in no Item availability" ) |
729 | << testFileUrl(fileName: "qtqmlModule.9.qml" ) |
730 | << QString(testFileUrl(fileName: "qtqmlModule.9.qml" ).toString() + QLatin1String(":4 Item is not a type\n" )) |
731 | << QStringList(); |
732 | } |
733 | |
734 | // Test that the engine registers the QtQml module |
735 | void tst_qqmlengine::qtqmlModule() |
736 | { |
737 | QFETCH(QUrl, testFile); |
738 | QFETCH(QString, expectedError); |
739 | QFETCH(QStringList, expectedWarnings); |
740 | |
741 | foreach (const QString &w, expectedWarnings) |
742 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w)); |
743 | |
744 | QQmlEngine e; |
745 | QQmlComponent c(&e, testFile); |
746 | if (expectedError.isEmpty()) { |
747 | QObject *o = c.create(); |
748 | QVERIFY(o); |
749 | delete o; |
750 | } else { |
751 | QCOMPARE(c.errorString(), expectedError); |
752 | } |
753 | } |
754 | |
755 | class CustomSelector : public QQmlAbstractUrlInterceptor |
756 | { |
757 | public: |
758 | CustomSelector(const QUrl &base):m_base(base){} |
759 | virtual QUrl intercept(const QUrl &url, QQmlAbstractUrlInterceptor::DataType d) |
760 | { |
761 | if ((url.scheme() != QStringLiteral("file" ) && url.scheme() != QStringLiteral("qrc" )) |
762 | || url.path().contains(s: "QtQml" )) |
763 | return url; |
764 | if (!m_interceptionPoints.contains(t: d)) |
765 | return url; |
766 | |
767 | if (url.path().endsWith(s: "Test.2/qmldir" )) {//Special case |
768 | QUrl url = m_base; |
769 | url.setPath(path: m_base.path() + "interception/module/intercepted/qmldir" ); |
770 | return url; |
771 | } |
772 | // Special case: with 5.10 we always add the implicit import, so we need to explicitly handle this case now |
773 | if (url.path().endsWith(s: "intercepted/qmldir" )) |
774 | return url; |
775 | |
776 | QString alteredPath = url.path(); |
777 | int a = alteredPath.lastIndexOf(c: '/'); |
778 | if (a < 0) |
779 | a = 0; |
780 | alteredPath.insert(i: a, QStringLiteral("/intercepted" )); |
781 | |
782 | QUrl ret = url; |
783 | ret.setPath(path: alteredPath); |
784 | return ret; |
785 | } |
786 | QList<QQmlAbstractUrlInterceptor::DataType> m_interceptionPoints; |
787 | QUrl m_base; |
788 | }; |
789 | |
790 | Q_DECLARE_METATYPE(QList<QQmlAbstractUrlInterceptor::DataType>); |
791 | |
792 | void tst_qqmlengine::urlInterceptor_data() |
793 | { |
794 | QTest::addColumn<QUrl>(name: "testFile" ); |
795 | QTest::addColumn<QList<QQmlAbstractUrlInterceptor::DataType> >(name: "interceptionPoint" ); |
796 | QTest::addColumn<QString>(name: "expectedFilePath" ); |
797 | QTest::addColumn<QString>(name: "expectedChildString" ); |
798 | QTest::addColumn<QString>(name: "expectedScriptString" ); |
799 | QTest::addColumn<QString>(name: "expectedResolvedUrl" ); |
800 | QTest::addColumn<QString>(name: "expectedAbsoluteUrl" ); |
801 | |
802 | QTest::newRow(dataTag: "InterceptTypes" ) |
803 | << testFileUrl(fileName: "interception/types/urlInterceptor.qml" ) |
804 | << (QList<QQmlAbstractUrlInterceptor::DataType>() << QQmlAbstractUrlInterceptor::QmlFile << QQmlAbstractUrlInterceptor::JavaScriptFile << QQmlAbstractUrlInterceptor::UrlString) |
805 | << testFileUrl(fileName: "interception/types/intercepted/doesNotExist.file" ).toString() |
806 | << QStringLiteral("intercepted" ) |
807 | << QStringLiteral("intercepted" ) |
808 | << testFileUrl(fileName: "interception/types/intercepted/doesNotExist.file" ).toString() |
809 | << QStringLiteral("file:///intercepted/doesNotExist.file" ); |
810 | |
811 | QTest::newRow(dataTag: "InterceptQmlDir" ) |
812 | << testFileUrl(fileName: "interception/qmldir/urlInterceptor.qml" ) |
813 | << (QList<QQmlAbstractUrlInterceptor::DataType>() << QQmlAbstractUrlInterceptor::QmldirFile << QQmlAbstractUrlInterceptor::UrlString) |
814 | << testFileUrl(fileName: "interception/qmldir/intercepted/doesNotExist.file" ).toString() |
815 | << QStringLiteral("intercepted" ) |
816 | << QStringLiteral("base file" ) |
817 | << testFileUrl(fileName: "interception/qmldir/intercepted/doesNotExist.file" ).toString() |
818 | << QStringLiteral("file:///intercepted/doesNotExist.file" ); |
819 | |
820 | QTest::newRow(dataTag: "InterceptModule" )//just a Test{}, needs to intercept the module import for it to work |
821 | << testFileUrl(fileName: "interception/module/urlInterceptor.qml" ) |
822 | << (QList<QQmlAbstractUrlInterceptor::DataType>() << QQmlAbstractUrlInterceptor::QmldirFile ) |
823 | << testFileUrl(fileName: "interception/module/intercepted/doesNotExist.file" ).toString() |
824 | << QStringLiteral("intercepted" ) |
825 | << QStringLiteral("intercepted" ) |
826 | << testFileUrl(fileName: "interception/module/intercepted/doesNotExist.file" ).toString() |
827 | << QStringLiteral("file:///doesNotExist.file" ); |
828 | |
829 | QTest::newRow(dataTag: "InterceptStrings" ) |
830 | << testFileUrl(fileName: "interception/strings/urlInterceptor.qml" ) |
831 | << (QList<QQmlAbstractUrlInterceptor::DataType>() << QQmlAbstractUrlInterceptor::UrlString) |
832 | << testFileUrl(fileName: "interception/strings/intercepted/doesNotExist.file" ).toString() |
833 | << QStringLiteral("base file" ) |
834 | << QStringLiteral("base file" ) |
835 | << testFileUrl(fileName: "interception/strings/intercepted/doesNotExist.file" ).toString() |
836 | << QStringLiteral("file:///intercepted/doesNotExist.file" ); |
837 | |
838 | QTest::newRow(dataTag: "InterceptIncludes" ) |
839 | << testFileUrl(fileName: "interception/includes/urlInterceptor.qml" ) |
840 | << (QList<QQmlAbstractUrlInterceptor::DataType>() << QQmlAbstractUrlInterceptor::JavaScriptFile) |
841 | << testFileUrl(fileName: "interception/includes/doesNotExist.file" ).toString() |
842 | << QStringLiteral("base file" ) |
843 | << QStringLiteral("intercepted include file" ) |
844 | << testFileUrl(fileName: "interception/includes/doesNotExist.file" ).toString() |
845 | << QStringLiteral("file:///doesNotExist.file" ); |
846 | } |
847 | |
848 | void tst_qqmlengine::urlInterceptor() |
849 | { |
850 | |
851 | QFETCH(QUrl, testFile); |
852 | QFETCH(QList<QQmlAbstractUrlInterceptor::DataType>, interceptionPoint); |
853 | QFETCH(QString, expectedFilePath); |
854 | QFETCH(QString, expectedChildString); |
855 | QFETCH(QString, expectedScriptString); |
856 | QFETCH(QString, expectedResolvedUrl); |
857 | QFETCH(QString, expectedAbsoluteUrl); |
858 | |
859 | QQmlEngine e; |
860 | e.addImportPath(dir: testFileUrl(fileName: "interception/imports" ).url()); |
861 | CustomSelector cs(testFileUrl(fileName: "" )); |
862 | cs.m_interceptionPoints = interceptionPoint; |
863 | e.setUrlInterceptor(&cs); |
864 | QQmlComponent c(&e, testFile); //Note that this can get intercepted too |
865 | QObject *o = c.create(); |
866 | if (!o) |
867 | qDebug() << c.errorString(); |
868 | QVERIFY(o); |
869 | //Test a URL as a property initialization |
870 | QCOMPARE(o->property("filePath" ).toString(), expectedFilePath); |
871 | //Test a URL as a Type location |
872 | QCOMPARE(o->property("childString" ).toString(), expectedChildString); |
873 | //Test a URL as a Script location |
874 | QCOMPARE(o->property("scriptString" ).toString(), expectedScriptString); |
875 | //Test a URL as a resolveUrl() call |
876 | QCOMPARE(o->property("resolvedUrl" ).toString(), expectedResolvedUrl); |
877 | QCOMPARE(o->property("absoluteUrl" ).toString(), expectedAbsoluteUrl); |
878 | } |
879 | |
880 | void tst_qqmlengine::qmlContextProperties() |
881 | { |
882 | QQmlEngine e; |
883 | |
884 | QQmlComponent c(&e, testFileUrl(fileName: "TypeofQmlProperty.qml" )); |
885 | QObject *o = c.create(); |
886 | if (!o) { |
887 | qDebug() << c.errorString(); |
888 | } |
889 | QVERIFY(o); |
890 | } |
891 | |
892 | void tst_qqmlengine::testGCCorruption() |
893 | { |
894 | #ifdef SKIP_GCCORRUPTION_TEST |
895 | QSKIP("Test too heavy for qemu" ); |
896 | #endif |
897 | |
898 | QQmlEngine e; |
899 | |
900 | QQmlComponent c(&e, testFileUrl(fileName: "testGCCorruption.qml" )); |
901 | QObject *o = c.create(); |
902 | QVERIFY2(o, qPrintable(c.errorString())); |
903 | } |
904 | |
905 | void tst_qqmlengine::testGroupedPropertyRevisions() |
906 | { |
907 | QQmlEngine e; |
908 | |
909 | QQmlComponent c(&e, testFileUrl(fileName: "testGroupedPropertiesRevision.1.qml" )); |
910 | QScopedPointer<QObject> object(c.create()); |
911 | QVERIFY2(object.data(), qPrintable(c.errorString())); |
912 | QQmlComponent c2(&e, testFileUrl(fileName: "testGroupedPropertiesRevision.2.qml" )); |
913 | QVERIFY(!c2.errorString().isEmpty()); |
914 | } |
915 | |
916 | void tst_qqmlengine::componentFromEval() |
917 | { |
918 | QQmlEngine engine; |
919 | const QUrl testUrl = testFileUrl(fileName: "EmptyComponent.qml" ); |
920 | QJSValue result = engine.evaluate(program: "Qt.createComponent(\"" + testUrl.toString() + "\");" ); |
921 | QPointer<QQmlComponent> component(qobject_cast<QQmlComponent*>(object: result.toQObject())); |
922 | QVERIFY(!component.isNull()); |
923 | QScopedPointer<QObject> item(component->create()); |
924 | QVERIFY(!item.isNull()); |
925 | } |
926 | |
927 | void tst_qqmlengine::qrcUrls() |
928 | { |
929 | QQmlEngine engine; |
930 | QQmlEnginePrivate *pEngine = QQmlEnginePrivate::get(e: &engine); |
931 | |
932 | { |
933 | QQmlRefPointer<QQmlTypeData> oneQml(pEngine->typeLoader.getType(unNormalizedUrl: QUrl("qrc:/qrcurls.qml" ))); |
934 | QVERIFY(oneQml.data() != nullptr); |
935 | QQmlRefPointer<QQmlTypeData> twoQml(pEngine->typeLoader.getType(unNormalizedUrl: QUrl("qrc:///qrcurls.qml" ))); |
936 | QVERIFY(twoQml.data() != nullptr); |
937 | QCOMPARE(oneQml.data(), twoQml.data()); |
938 | } |
939 | |
940 | { |
941 | QQmlRefPointer<QQmlTypeData> oneJS(pEngine->typeLoader.getType(unNormalizedUrl: QUrl("qrc:/qrcurls.js" ))); |
942 | QVERIFY(oneJS.data() != nullptr); |
943 | QQmlRefPointer<QQmlTypeData> twoJS(pEngine->typeLoader.getType(unNormalizedUrl: QUrl("qrc:///qrcurls.js" ))); |
944 | QVERIFY(twoJS.data() != nullptr); |
945 | QCOMPARE(oneJS.data(), twoJS.data()); |
946 | } |
947 | } |
948 | |
949 | class ObjectCaller : public QObject |
950 | { |
951 | Q_OBJECT |
952 | signals: |
953 | void doubleReply(const double a); |
954 | }; |
955 | |
956 | void tst_qqmlengine::cppSignalAndEval() |
957 | { |
958 | ObjectCaller objectCaller; |
959 | QQmlEngine engine; |
960 | qmlRegisterSingletonInstance(uri: "Test" , versionMajor: 1, versionMinor: 0, typeName: "CallerCpp" , cppObject: &objectCaller); |
961 | QQmlComponent c(&engine); |
962 | c.setData("import QtQuick 2.9\n" |
963 | "import Test 1.0\n" |
964 | "Item {\n" |
965 | " property var r: 0\n" |
966 | " Connections {\n" |
967 | " target: CallerCpp;\n" |
968 | " function onDoubleReply() {\n" |
969 | " eval('var z = 1');\n" |
970 | " r = a;\n" |
971 | " }\n" |
972 | " }\n" |
973 | "}" , |
974 | baseUrl: QUrl(QStringLiteral("qrc:/main.qml" ))); |
975 | QScopedPointer<QObject> object(c.create()); |
976 | QVERIFY(!object.isNull()); |
977 | emit objectCaller.doubleReply(a: 1.1234); |
978 | QCOMPARE(object->property("r" ), 1.1234); |
979 | } |
980 | |
981 | class CppSingleton : public QObject { |
982 | Q_OBJECT |
983 | public: |
984 | CppSingleton() {} |
985 | |
986 | static QObject *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) |
987 | { |
988 | Q_UNUSED(qmlEngine); |
989 | Q_UNUSED(jsEngine); |
990 | return new CppSingleton(); |
991 | } |
992 | }; |
993 | |
994 | class JsSingleton : public QObject { |
995 | Q_OBJECT |
996 | public: |
997 | JsSingleton() {} |
998 | |
999 | static QJSValue create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) |
1000 | { |
1001 | Q_UNUSED(qmlEngine); |
1002 | QJSValue value = jsEngine->newQObject(object: new JsSingleton()); |
1003 | return value; |
1004 | } |
1005 | }; |
1006 | |
1007 | class SomeQObjectClass : public QObject { |
1008 | Q_OBJECT |
1009 | public: |
1010 | SomeQObjectClass() : QObject(nullptr){} |
1011 | }; |
1012 | |
1013 | class Dayfly : public QObject |
1014 | { |
1015 | Q_OBJECT |
1016 | }; |
1017 | |
1018 | void tst_qqmlengine::singletonInstance() |
1019 | { |
1020 | QQmlEngine engine; |
1021 | |
1022 | int cppSingletonTypeId = qmlRegisterSingletonType<CppSingleton>(uri: "Test" , versionMajor: 1, versionMinor: 0, typeName: "CppSingleton" , callback: &CppSingleton::create); |
1023 | int jsValueSingletonTypeId = qmlRegisterSingletonType(uri: "Test" , versionMajor: 1, versionMinor: 0, typeName: "JsSingleton" , callback: &JsSingleton::create); |
1024 | |
1025 | { |
1026 | // Cpp QObject singleton type |
1027 | QJSValue value = engine.singletonInstance<QJSValue>(qmlTypeId: cppSingletonTypeId); |
1028 | QVERIFY(!value.isUndefined()); |
1029 | QVERIFY(value.isQObject()); |
1030 | QObject *instance = value.toQObject(); |
1031 | QVERIFY(instance); |
1032 | QCOMPARE(instance->metaObject()->className(), "CppSingleton" ); |
1033 | } |
1034 | |
1035 | { |
1036 | // QJSValue QObject singleton type |
1037 | QJSValue value = engine.singletonInstance<QJSValue>(qmlTypeId: jsValueSingletonTypeId); |
1038 | QVERIFY(!value.isUndefined()); |
1039 | QVERIFY(value.isQObject()); |
1040 | QObject *instance = value.toQObject(); |
1041 | QVERIFY(instance); |
1042 | QCOMPARE(instance->metaObject()->className(), "JsSingleton" ); |
1043 | } |
1044 | |
1045 | { |
1046 | int data = 30; |
1047 | auto id = qmlRegisterSingletonType<CppSingleton>(uri: "Qt.test" ,versionMajor: 1,versionMinor: 0,typeName: "CapturingLambda" ,callback: [data](QQmlEngine*, QJSEngine*){ // register qobject singleton with capturing lambda |
1048 | auto o = new CppSingleton; |
1049 | o->setProperty(name: "data" , value: data); |
1050 | return o; |
1051 | }); |
1052 | QJSValue value = engine.singletonInstance<QJSValue>(qmlTypeId: id); |
1053 | QVERIFY(!value.isUndefined()); |
1054 | QVERIFY(value.isQObject()); |
1055 | QObject *instance = value.toQObject(); |
1056 | QVERIFY(instance); |
1057 | QCOMPARE(instance->metaObject()->className(), "CppSingleton" ); |
1058 | QCOMPARE(instance->property("data" ), data); |
1059 | } |
1060 | { |
1061 | qmlRegisterSingletonType<CppSingleton>(uri: "Qt.test" ,versionMajor: 1,versionMinor: 0,typeName: "NotAmbiguous" , callback: [](QQmlEngine* qeng, QJSEngine* jeng) -> QObject* {return CppSingleton::create(qmlEngine: qeng, jsEngine: jeng);}); // test that overloads for qmlRegisterSingleton are not ambiguous |
1062 | } |
1063 | { |
1064 | // Register QObject* directly |
1065 | CppSingleton single; |
1066 | int id = qmlRegisterSingletonInstance(uri: "Qt.test" , versionMajor: 1, versionMinor: 0, typeName: "CppOwned" , |
1067 | cppObject: &single); |
1068 | QQmlEngine engine2; |
1069 | CppSingleton *singlePtr = engine2.singletonInstance<CppSingleton *>(qmlTypeId: id); |
1070 | QVERIFY(singlePtr); |
1071 | QCOMPARE(&single, singlePtr); |
1072 | QVERIFY(engine2.objectOwnership(singlePtr) == QQmlEngine::CppOwnership); |
1073 | } |
1074 | |
1075 | { |
1076 | CppSingleton single; |
1077 | QQmlEngine engineA; |
1078 | QQmlEngine engineB; |
1079 | int id = qmlRegisterSingletonInstance(uri: "Qt.test" , versionMajor: 1, versionMinor: 0, typeName: "CppOwned" , cppObject: &single); |
1080 | auto singlePtr = engineA.singletonInstance<CppSingleton *>(qmlTypeId: id); |
1081 | QVERIFY(singlePtr); |
1082 | singlePtr = engineA.singletonInstance<CppSingleton *>(qmlTypeId: id); // accessing the singleton multiple times from the same engine is fine |
1083 | QVERIFY(singlePtr); |
1084 | QTest::ignoreMessage(type: QtMsgType::QtCriticalMsg, message: "<Unknown File>: qmlRegisterSingletonType(): \"CppOwned\" is not available because the callback function returns a null pointer." ); |
1085 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: "<Unknown File>: Singleton registered by registerSingletonInstance must only be accessed from one engine" ); |
1086 | QCOMPARE(&single, singlePtr); |
1087 | auto noSinglePtr = engineB.singletonInstance<CppSingleton *>(qmlTypeId: id); |
1088 | QVERIFY(!noSinglePtr); |
1089 | } |
1090 | |
1091 | { |
1092 | CppSingleton single; |
1093 | QThread newThread {}; |
1094 | single.moveToThread(thread: &newThread); |
1095 | QCOMPARE(single.thread(), &newThread); |
1096 | QQmlEngine engineB; |
1097 | int id = qmlRegisterSingletonInstance(uri: "Qt.test" , versionMajor: 1, versionMinor: 0, typeName: "CppOwned" , cppObject: &single); |
1098 | QTest::ignoreMessage(type: QtMsgType::QtCriticalMsg, message: "<Unknown File>: qmlRegisterSingletonType(): \"CppOwned\" is not available because the callback function returns a null pointer." ); |
1099 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: "<Unknown File>: Registered object must live in the same thread as the engine it was registered with" ); |
1100 | auto noSinglePtr = engineB.singletonInstance<CppSingleton *>(qmlTypeId: id); |
1101 | QVERIFY(!noSinglePtr); |
1102 | } |
1103 | |
1104 | { |
1105 | // Invalid types |
1106 | QJSValue value; |
1107 | value = engine.singletonInstance<QJSValue>(qmlTypeId: -4711); |
1108 | QVERIFY(value.isUndefined()); |
1109 | value = engine.singletonInstance<QJSValue>(qmlTypeId: 1701); |
1110 | QVERIFY(value.isUndefined()); |
1111 | } |
1112 | |
1113 | { |
1114 | // Valid, but non-singleton type |
1115 | int typeId = qmlRegisterType<CppSingleton>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "NotASingleton" ); |
1116 | QJSValue value = engine.singletonInstance<QJSValue>(qmlTypeId: typeId); |
1117 | QVERIFY(value.isUndefined()); |
1118 | } |
1119 | |
1120 | { |
1121 | // Cpp QObject singleton type |
1122 | CppSingleton *instance = engine.singletonInstance<CppSingleton*>(qmlTypeId: cppSingletonTypeId); |
1123 | QVERIFY(instance); |
1124 | QCOMPARE(instance->metaObject()->className(), "CppSingleton" ); |
1125 | QCOMPARE(instance, engine.singletonInstance<QJSValue>(cppSingletonTypeId).toQObject()); |
1126 | } |
1127 | |
1128 | { |
1129 | // Wrong destination type |
1130 | SomeQObjectClass * instance = engine.singletonInstance<SomeQObjectClass*>(qmlTypeId: cppSingletonTypeId); |
1131 | QVERIFY(!instance); |
1132 | } |
1133 | |
1134 | { |
1135 | // deleted object |
1136 | auto dayfly = new Dayfly{}; |
1137 | auto id = qmlRegisterSingletonInstance(uri: "Vanity" , versionMajor: 1, versionMinor: 0, typeName: "Dayfly" , cppObject: dayfly); |
1138 | delete dayfly; |
1139 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: "<Unknown File>: The registered singleton has already been deleted. Ensure that it outlives the engine." ); |
1140 | QObject *instance = engine.singletonInstance<QObject*>(qmlTypeId: id); |
1141 | QVERIFY(!instance); |
1142 | } |
1143 | } |
1144 | |
1145 | void tst_qqmlengine::aggressiveGc() |
1146 | { |
1147 | const QByteArray origAggressiveGc = qgetenv(varName: "QV4_MM_AGGRESSIVE_GC" ); |
1148 | qputenv(varName: "QV4_MM_AGGRESSIVE_GC" , value: "true" ); |
1149 | { |
1150 | QQmlEngine engine; // freezing should not run into infinite recursion |
1151 | QJSValue obj = engine.newObject(); |
1152 | QVERIFY(obj.isObject()); |
1153 | } |
1154 | qputenv(varName: "QV4_MM_AGGRESSIVE_GC" , value: origAggressiveGc); |
1155 | } |
1156 | |
1157 | void tst_qqmlengine::cachedGetterLookup_qtbug_75335() |
1158 | { |
1159 | QQmlEngine engine; |
1160 | const QUrl testUrl = testFileUrl(fileName: "CachedGetterLookup.qml" ); |
1161 | QQmlComponent component(&engine, testUrl); |
1162 | QVERIFY(component.isReady()); |
1163 | QScopedPointer<QObject> object(component.create()); |
1164 | QVERIFY(object != nullptr); |
1165 | } |
1166 | |
1167 | class EvilSingleton : public QObject |
1168 | { |
1169 | Q_OBJECT |
1170 | public: |
1171 | QPointer<QQmlEngine> m_engine; |
1172 | EvilSingleton(QQmlEngine *engine) : m_engine(engine) { |
1173 | connect(sender: this, signal: &QObject::destroyed, context: this, slot: [this]() { |
1174 | QQmlComponent component(m_engine); |
1175 | component.setData("import QtQml 2.0\nQtObject {}" , baseUrl: QUrl("file://Stuff.qml" )); |
1176 | QVERIFY(component.isReady()); |
1177 | QScopedPointer<QObject> obj(component.create()); |
1178 | QVERIFY(obj); |
1179 | }); |
1180 | } |
1181 | }; |
1182 | |
1183 | void tst_qqmlengine::createComponentOnSingletonDestruction() |
1184 | { |
1185 | qmlRegisterSingletonType<EvilSingleton>(uri: "foo.foo" , versionMajor: 1, versionMinor: 0, typeName: "Singleton" , |
1186 | callback: [](QQmlEngine *engine, QJSEngine *) { |
1187 | return new EvilSingleton(engine); |
1188 | }); |
1189 | |
1190 | QQmlEngine engine; |
1191 | QQmlComponent component(&engine, testFileUrl(fileName: "evilSingletonInstantiation.qml" )); |
1192 | QVERIFY(component.isReady()); |
1193 | QScopedPointer<QObject> obj(component.create()); |
1194 | QVERIFY(obj); |
1195 | } |
1196 | |
1197 | void tst_qqmlengine::uiLanguage() |
1198 | { |
1199 | QQmlEngine engine; |
1200 | |
1201 | QObject::connect(sender: &engine, signal: &QJSEngine::uiLanguageChanged, slot: [&engine]() { |
1202 | engine.retranslate(); |
1203 | }); |
1204 | |
1205 | QSignalSpy uiLanguageChangeSpy(&engine, SIGNAL(uiLanguageChanged())); |
1206 | |
1207 | QQmlComponent component(&engine, testFileUrl(fileName: "uiLanguage.qml" )); |
1208 | |
1209 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"" ).toLatin1()); |
1210 | QScopedPointer<QObject> object(component.create()); |
1211 | QVERIFY(!object.isNull()); |
1212 | |
1213 | QVERIFY(engine.uiLanguage().isEmpty()); |
1214 | QCOMPARE(object->property("numberOfTranslationBindingEvaluations" ).toInt(), 1); |
1215 | |
1216 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"" ).toLatin1()); |
1217 | engine.setUiLanguage("TestLanguage" ); |
1218 | QCOMPARE(object->property("numberOfTranslationBindingEvaluations" ).toInt(), 2); |
1219 | QCOMPARE(object->property("chosenLanguage" ).toString(), "TestLanguage" ); |
1220 | |
1221 | |
1222 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: (component.url().toString() + ":2:1: QML QtObject: Binding loop detected for property \"textToTranslate\"" ).toLatin1()); |
1223 | engine.evaluate(program: "Qt.uiLanguage = \"anotherLanguage\"" ); |
1224 | QCOMPARE(engine.uiLanguage(), QString("anotherLanguage" )); |
1225 | QCOMPARE(object->property("numberOfTranslationBindingEvaluations" ).toInt(), 3); |
1226 | QCOMPARE(object->property("chosenLanguage" ).toString(), "anotherLanguage" ); |
1227 | } |
1228 | |
1229 | QTEST_MAIN(tst_qqmlengine) |
1230 | |
1231 | #include "tst_qqmlengine.moc" |
1232 | |