1/****************************************************************************
2**
3** Copyright (C) 2016 Research In Motion.
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 <QQmlApplicationEngine>
31#include <QScopedPointer>
32#include <QSignalSpy>
33#include <QRegularExpression>
34#if QT_CONFIG(process)
35#include <QProcess>
36#endif
37#include <QDebug>
38
39class tst_qqmlapplicationengine : public QQmlDataTest
40{
41 Q_OBJECT
42public:
43 tst_qqmlapplicationengine() {}
44
45
46private slots:
47 void initTestCase();
48 void basicLoading();
49 void testNonResolvedPath();
50 void application_data();
51 void application();
52 void applicationProperties();
53 void removeObjectsWhenDestroyed();
54 void loadTranslation_data();
55 void loadTranslation();
56 void translationChange();
57 void setInitialProperties();
58 void failureToLoadTriggersWarningSignal();
59 void errorWhileCreating();
60
61private:
62 QString buildDir;
63 QString srcDir;
64};
65
66void tst_qqmlapplicationengine::initTestCase()
67{
68 qputenv(varName: "QT_MESSAGE_PATTERN", value: ""); // don't let it modify the debug output from testapp
69 buildDir = QDir::currentPath();
70 QQmlDataTest::initTestCase(); //Changes current path to src dir
71 srcDir = QDir::currentPath();
72}
73
74void tst_qqmlapplicationengine::basicLoading()
75{
76 int size = 0;
77
78 QQmlApplicationEngine *test = new QQmlApplicationEngine(testFileUrl(fileName: "basicTest.qml"));
79 QCOMPARE(test->rootObjects().size(), ++size);
80 QVERIFY(test->rootObjects()[size -1]);
81 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
82
83 QSignalSpy objectCreated(test, SIGNAL(objectCreated(QObject*,QUrl)));
84 test->load(url: testFileUrl(fileName: "basicTest.qml"));
85 QCOMPARE(objectCreated.count(), size);//one less than rootObjects().size() because we missed the first one
86 QCOMPARE(test->rootObjects().size(), ++size);
87 QVERIFY(test->rootObjects()[size -1]);
88 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
89
90 QByteArray testQml("import QtQml 2.0; QtObject{property bool success: true; property TestItem t: TestItem{}}");
91 test->loadData(data: testQml, url: testFileUrl(fileName: "dynamicTest.qml"));
92 QCOMPARE(objectCreated.count(), size);
93 QCOMPARE(test->rootObjects().size(), ++size);
94 QVERIFY(test->rootObjects()[size -1]);
95 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
96
97 delete test;
98}
99
100// make sure we resolve a relative URL to an absolute one, otherwise things
101// will break.
102void tst_qqmlapplicationengine::testNonResolvedPath()
103{
104#ifdef Q_OS_ANDROID
105 QSKIP("Android stores QML files in resources, and the path to a resource cannot be relative in this case");
106#endif
107 {
108 // NOTE NOTE NOTE! Missing testFileUrl is *WANTED* here! We want a
109 // non-resolved URL.
110 QQmlApplicationEngine test("data/nonResolvedLocal.qml");
111 QCOMPARE(test.rootObjects().size(), 1);
112 QVERIFY(test.rootObjects()[0]);
113 QVERIFY(test.rootObjects()[0]->property("success").toBool());
114 }
115 {
116 QQmlApplicationEngine test;
117 // NOTE NOTE NOTE! Missing testFileUrl is *WANTED* here! We want a
118 // non-resolved URL.
119 test.load(filePath: "data/nonResolvedLocal.qml");
120 QCOMPARE(test.rootObjects().size(), 1);
121 QVERIFY(test.rootObjects()[0]);
122 QVERIFY(test.rootObjects()[0]->property("success").toBool());
123 }
124}
125
126void tst_qqmlapplicationengine::application_data()
127{
128#ifdef Q_OS_ANDROID
129 QSKIP("Cannot launch external process on Android");
130#endif
131 QTest::addColumn<QByteArray>(name: "qmlFile");
132 QTest::addColumn<QByteArray>(name: "expectedStdErr");
133
134 QTest::newRow(dataTag: "delayed quit") << QByteArray("delayedQuit.qml")
135 << QByteArray("qml: Start: delayedQuit.qml\nqml: End\n");
136 QTest::newRow(dataTag: "delayed exit") << QByteArray("delayedExit.qml")
137 << QByteArray("qml: Start: delayedExit.qml\nqml: End\n");
138 QTest::newRow(dataTag: "immediate quit") << QByteArray("immediateQuit.qml")
139 << QByteArray("qml: End: immediateQuit.qml\n");
140 QTest::newRow(dataTag: "immediate exit") << QByteArray("immediateExit.qml")
141 << QByteArray("qml: End: immediateExit.qml\n");
142}
143
144void tst_qqmlapplicationengine::application()
145{
146 /* This test batches together some tests about running an external application
147 written with QQmlApplicationEngine. The application tests the following functionality
148 which is easier to do by watching a separate process:
149 - Loads relative paths from the working directory
150 - Quits when quit is called
151 - Exits when exit is called
152 - Emits aboutToQuit after quit is called
153 - Has access to application command line arguments
154
155 Note that checking the output means that on builds with extra debugging, this might fail with a false positive.
156 Also the testapp is automatically built and installed in shadow builds, so it does NOT use testData
157 */
158
159 QFETCH(QByteArray, qmlFile);
160 QFETCH(QByteArray, expectedStdErr);
161
162#if QT_CONFIG(process)
163 QDir::setCurrent(buildDir);
164 QProcess *testProcess = new QProcess(this);
165 QStringList args;
166 args << qmlFile; // QML file passed as an argument is going to be run by testapp.
167 testProcess->start(program: QLatin1String("testapp/testapp"), arguments: args);
168 QVERIFY(testProcess->waitForFinished(5000));
169 QCOMPARE(testProcess->exitCode(), 0);
170 QByteArray testStdOut = testProcess->readAllStandardOutput();
171 QByteArray testStdErr = testProcess->readAllStandardError();
172#ifdef Q_OS_WIN
173 expectedStdErr.replace('\n', QByteArray("\r\n"));
174#endif
175 QCOMPARE(testStdOut, QByteArray(""));
176 QVERIFY2(QString(testStdErr).endsWith(QString(expectedStdErr)),
177 QByteArray("\nExpected ending:\n") + expectedStdErr
178 + QByteArray("\nActual output:\n") + testStdErr);
179 delete testProcess;
180 QDir::setCurrent(srcDir);
181#else // process
182 QSKIP("No process support");
183#endif // process
184}
185
186void tst_qqmlapplicationengine::applicationProperties()
187{
188 const QString originalName = QCoreApplication::applicationName();
189 const QString originalVersion = QCoreApplication::applicationVersion();
190 const QString originalOrganization = QCoreApplication::organizationName();
191 const QString originalDomain = QCoreApplication::organizationDomain();
192 QString firstName = QLatin1String("Test A");
193 QString firstVersion = QLatin1String("0.0A");
194 QString firstOrganization = QLatin1String("Org A");
195 QString firstDomain = QLatin1String("a.org");
196 QString secondName = QLatin1String("Test B");
197 QString secondVersion = QLatin1String("0.0B");
198 QString secondOrganization = QLatin1String("Org B");
199 QString secondDomain = QLatin1String("b.org");
200
201 QCoreApplication::setApplicationName(firstName);
202 QCoreApplication::setApplicationVersion(firstVersion);
203 QCoreApplication::setOrganizationName(firstOrganization);
204 QCoreApplication::setOrganizationDomain(firstDomain);
205
206 QQmlApplicationEngine *test = new QQmlApplicationEngine(testFileUrl(fileName: "applicationTest.qml"));
207 QObject* root = test->rootObjects().at(i: 0);
208 QVERIFY(root);
209 QCOMPARE(root->property("originalName").toString(), firstName);
210 QCOMPARE(root->property("originalVersion").toString(), firstVersion);
211 QCOMPARE(root->property("originalOrganization").toString(), firstOrganization);
212 QCOMPARE(root->property("originalDomain").toString(), firstDomain);
213 QCOMPARE(root->property("currentName").toString(), secondName);
214 QCOMPARE(root->property("currentVersion").toString(), secondVersion);
215 QCOMPARE(root->property("currentOrganization").toString(), secondOrganization);
216 QCOMPARE(root->property("currentDomain").toString(), secondDomain);
217 QCOMPARE(QCoreApplication::applicationName(), secondName);
218 QCOMPARE(QCoreApplication::applicationVersion(), secondVersion);
219 QCOMPARE(QCoreApplication::organizationName(), secondOrganization);
220 QCOMPARE(QCoreApplication::organizationDomain(), secondDomain);
221
222 QObject* application = root->property(name: "applicationInstance").value<QObject*>();
223 QVERIFY(application);
224 QSignalSpy nameChanged(application, SIGNAL(nameChanged()));
225 QSignalSpy versionChanged(application, SIGNAL(versionChanged()));
226 QSignalSpy organizationChanged(application, SIGNAL(organizationChanged()));
227 QSignalSpy domainChanged(application, SIGNAL(domainChanged()));
228
229 QCoreApplication::setApplicationName(originalName);
230 QCoreApplication::setApplicationVersion(originalVersion);
231 QCoreApplication::setOrganizationName(originalOrganization);
232 QCoreApplication::setOrganizationDomain(originalDomain);
233
234 QCOMPARE(nameChanged.count(), 1);
235 QCOMPARE(versionChanged.count(), 1);
236 QCOMPARE(organizationChanged.count(), 1);
237 QCOMPARE(domainChanged.count(), 1);
238
239 delete test;
240}
241
242void tst_qqmlapplicationengine::removeObjectsWhenDestroyed()
243{
244 QScopedPointer<QQmlApplicationEngine> test(new QQmlApplicationEngine);
245 QVERIFY(test->rootObjects().isEmpty());
246
247 QSignalSpy objectCreated(test.data(), SIGNAL(objectCreated(QObject*,QUrl)));
248 test->load(url: testFileUrl(fileName: "basicTest.qml"));
249 QCOMPARE(objectCreated.count(), 1);
250
251 QSignalSpy objectDestroyed(test->rootObjects().first(), SIGNAL(destroyed()));
252 test->rootObjects().first()->deleteLater();
253 objectDestroyed.wait();
254 QCOMPARE(objectDestroyed.count(), 1);
255 QCOMPARE(test->rootObjects().size(), 0);
256}
257
258void tst_qqmlapplicationengine::loadTranslation_data()
259{
260 QTest::addColumn<QUrl>(name: "qmlUrl");
261 QTest::addColumn<QString>(name: "translation");
262
263 QTest::newRow(dataTag: "local file") << testFileUrl(fileName: "loadTranslation.qml")
264 << QStringLiteral("translated");
265 QTest::newRow(dataTag: "qrc") << QUrl(QLatin1String("qrc:///data/loadTranslation.qml"))
266 << QStringLiteral("translated");
267}
268
269void tst_qqmlapplicationengine::loadTranslation()
270{
271 QFETCH(QUrl, qmlUrl);
272 QFETCH(QString, translation);
273
274 QQmlApplicationEngine test(qmlUrl);
275 QVERIFY(!test.rootObjects().isEmpty());
276
277 QObject *rootObject = test.rootObjects().first();
278 QVERIFY(rootObject);
279
280 QCOMPARE(rootObject->property("translation").toString(), translation);
281}
282
283void tst_qqmlapplicationengine::translationChange()
284{
285 if (QLocale().language() == QLocale::SwissGerman) {
286 QSKIP("Skipping this when running under the Swiss locale as we would always load translation.");
287 }
288
289 QQmlApplicationEngine engine(testFileUrl(fileName: "loadTranslation.qml"));
290
291 QCOMPARE(engine.uiLanguage(), QLocale().bcp47Name());
292
293 QObject *rootObject = engine.rootObjects().first();
294 QVERIFY(rootObject);
295
296 QCOMPARE(rootObject->property("translation").toString(), "translated");
297
298 engine.setUiLanguage("de_CH");
299 QCOMPARE(rootObject->property("translation").toString(), QString::fromUtf8("Gr\u00FCezi"));
300
301 engine.setUiLanguage(QString());
302 QCOMPARE(rootObject->property("translation").toString(), "translate it");
303}
304
305void tst_qqmlapplicationengine::setInitialProperties()
306{
307 QQmlApplicationEngine test {};
308 {
309 test.setInitialProperties(QVariantMap{{"success", false}});
310 test.load(url: testFileUrl(fileName: "basicTest.qml"));
311 QVERIFY(!test.rootObjects().empty());
312 QCOMPARE(test.rootObjects().first()->property("success").toBool(), false);
313 }
314 {
315 test.setInitialProperties({{"success", true}});
316 test.load(url: testFileUrl(fileName: "basicTest.qml"));
317 QCOMPARE(test.rootObjects().size(), 2);
318 QCOMPARE(test.rootObjects().at(1)->property("success").toBool(), true);
319 }
320}
321
322Q_DECLARE_METATYPE(QList<QQmlError>) // for signalspy below
323
324void tst_qqmlapplicationengine::failureToLoadTriggersWarningSignal()
325{
326 auto url = testFileUrl(fileName: "invalid.qml");
327 qRegisterMetaType<QList<QQmlError>>();
328 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: "QQmlApplicationEngine failed to load component");
329 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg,
330 messagePattern: QRegularExpression(QRegularExpression::escape(str: url.toString()) + QLatin1Char('*')));
331 QQmlApplicationEngine test;
332 QSignalSpy warningObserver(&test, &QQmlApplicationEngine::warnings);
333 test.load(url);
334 QTRY_COMPARE(warningObserver.count(), 1);
335}
336
337void tst_qqmlapplicationengine::errorWhileCreating()
338{
339 auto url = testFileUrl(fileName: "requiredViolation.qml");
340 QQmlApplicationEngine test;
341 QSignalSpy observer(&test, &QQmlApplicationEngine::objectCreated);
342
343 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, message: "QQmlApplicationEngine failed to create component");
344 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, qPrintable(QStringLiteral("%1:5:5: Required property foo was not initialized").arg(testFileUrl("Required.qml").toString())));
345
346 test.load(url);
347
348 QTRY_COMPARE(observer.count(), 1);
349 QList<QVariant> args = observer.takeFirst();
350 QVERIFY(args.at(0).isNull());
351 QCOMPARE(args.at(1).toUrl(), url);
352}
353
354QTEST_MAIN(tst_qqmlapplicationengine)
355
356#include "tst_qqmlapplicationengine.moc"
357

source code of qtdeclarative/tests/auto/qml/qqmlapplicationengine/tst_qqmlapplicationengine.cpp