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
49class tst_qqmlengine : public QQmlDataTest
50{
51 Q_OBJECT
52public:
53 tst_qqmlengine() {}
54
55private 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
89public slots:
90 QObject *createAQObjectForOwnershipTest ()
91 {
92 static QObject *ptr = new QObject();
93 return ptr;
94 }
95
96private:
97 QTemporaryDir m_tempDir;
98};
99
100void tst_qqmlengine::initTestCase()
101{
102 QVERIFY2(m_tempDir.isValid(), qPrintable(m_tempDir.errorString()));
103 QQmlDataTest::initTestCase();
104}
105
106void 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
116class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
117{
118public:
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
129void 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
148class ImmediateReply : public QNetworkReply {
149
150 Q_OBJECT
151
152public:
153 ImmediateReply() {
154 setFinished(true);
155 }
156 virtual qint64 readData(char* , qint64 ) {
157 return 0;
158 }
159 virtual void abort() { }
160};
161
162class ImmediateManager : public QNetworkAccessManager {
163
164 Q_OBJECT
165
166public:
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
176class ImmediateFactory : public QQmlNetworkAccessManagerFactory {
177
178public:
179 QNetworkAccessManager *create(QObject *) {
180 return new ImmediateManager;
181 }
182};
183
184void 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
195void 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
232void 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
265void 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
291void 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
319void 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
385struct ComponentCacheFunctions : public QObject, public QQmlIncubationController
386{
387 Q_OBJECT
388public:
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
429private:
430 virtual void timerEvent(QTimerEvent *)
431 {
432 incubateFor(msecs: 1000);
433 }
434};
435
436void 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
453void 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
507void 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
523void 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
540void 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
548void 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
581void 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
648void 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
682void 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
735void 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
755class CustomSelector : public QQmlAbstractUrlInterceptor
756{
757public:
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
790Q_DECLARE_METATYPE(QList<QQmlAbstractUrlInterceptor::DataType>);
791
792void 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
848void 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
880void 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
892void 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
905void 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
916void 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
927void 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
949class ObjectCaller : public QObject
950{
951 Q_OBJECT
952signals:
953 void doubleReply(const double a);
954};
955
956void 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
981class CppSingleton : public QObject {
982 Q_OBJECT
983public:
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
994class JsSingleton : public QObject {
995 Q_OBJECT
996public:
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
1007class SomeQObjectClass : public QObject {
1008 Q_OBJECT
1009public:
1010 SomeQObjectClass() : QObject(nullptr){}
1011};
1012
1013class Dayfly : public QObject
1014{
1015 Q_OBJECT
1016};
1017
1018void 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
1145void 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
1157void 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
1167class EvilSingleton : public QObject
1168{
1169 Q_OBJECT
1170public:
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
1183void 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
1197void 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
1229QTEST_MAIN(tst_qqmlengine)
1230
1231#include "tst_qqmlengine.moc"
1232

source code of qtdeclarative/tests/auto/qml/qqmlengine/tst_qqmlengine.cpp