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 <QtTest/QtTest>
30#include <QQmlApplicationEngine>
31#include <QQmlAbstractUrlInterceptor>
32#include <QtQuick/qquickview.h>
33#include <QtQuick/qquickitem.h>
34#include <private/qqmlimport_p.h>
35#include <private/qqmlengine_p.h>
36#include "../../shared/util.h"
37
38class tst_QQmlImport : public QQmlDataTest
39{
40 Q_OBJECT
41
42private slots:
43 void importPathOrder();
44 void testDesignerSupported();
45 void uiFormatLoading();
46 void completeQmldirPaths_data();
47 void completeQmldirPaths();
48 void interceptQmldir();
49 void singletonVersionResolution();
50 void removeDynamicPlugin();
51 void cleanup();
52};
53
54void tst_QQmlImport::cleanup()
55{
56 QQmlImports::setDesignerSupportRequired(false);
57}
58
59void tst_QQmlImport::testDesignerSupported()
60{
61 QQuickView *window = new QQuickView();
62 window->engine()->addImportPath(dir: directory());
63
64 window->setSource(testFileUrl(fileName: "testfile_supported.qml"));
65 QVERIFY(window->errors().isEmpty());
66
67 window->setSource(testFileUrl(fileName: "testfile_unsupported.qml"));
68 QVERIFY(window->errors().isEmpty());
69
70 QQmlImports::setDesignerSupportRequired(true);
71
72 //imports are cached so we create a new window
73 delete window;
74 window = new QQuickView();
75
76 window->engine()->addImportPath(dir: directory());
77 window->engine()->clearComponentCache();
78
79 window->setSource(testFileUrl(fileName: "testfile_supported.qml"));
80 QVERIFY(window->errors().isEmpty());
81
82 QString warningString("%1:30:1: module does not support the designer \"MyPluginUnsupported\" \n import MyPluginUnsupported 1.0\r \n ^ ");
83#if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID)
84 warningString.remove(c: '\r');
85#endif
86 warningString = warningString.arg(a: testFileUrl(fileName: "testfile_unsupported.qml").toString());
87 QTest::ignoreMessage(type: QtWarningMsg, message: warningString.toLocal8Bit());
88 window->setSource(testFileUrl(fileName: "testfile_unsupported.qml"));
89 QVERIFY(!window->errors().isEmpty());
90
91 delete window;
92}
93
94void tst_QQmlImport::uiFormatLoading()
95{
96 int size = 0;
97
98 QQmlApplicationEngine *test = new QQmlApplicationEngine(testFileUrl(fileName: "TestForm.ui.qml"));
99 test->addImportPath(dir: directory());
100 QCOMPARE(test->rootObjects().size(), ++size);
101 QVERIFY(test->rootObjects()[size -1]);
102 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
103
104 QSignalSpy objectCreated(test, SIGNAL(objectCreated(QObject*,QUrl)));
105 test->load(url: testFileUrl(fileName: "TestForm.ui.qml"));
106 QCOMPARE(objectCreated.count(), size);//one less than rootObjects().size() because we missed the first one
107 QCOMPARE(test->rootObjects().size(), ++size);
108 QVERIFY(test->rootObjects()[size -1]);
109 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
110
111 QByteArray testQml("import QtQml 2.0; QtObject{property bool success: true; property TestForm t: TestForm{}}");
112 test->loadData(data: testQml, url: testFileUrl(fileName: "dynamicTestForm.ui.qml"));
113 QCOMPARE(objectCreated.count(), size);
114 QCOMPARE(test->rootObjects().size(), ++size);
115 QVERIFY(test->rootObjects()[size -1]);
116 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
117
118 test->load(url: testFileUrl(fileName: "openTestFormFromDir.qml"));
119 QCOMPARE(objectCreated.count(), size);
120 QCOMPARE(test->rootObjects().size(), ++size);
121 QVERIFY(test->rootObjects()[size -1]);
122 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
123
124 test->load(url: testFileUrl(fileName: "openTestFormFromQmlDir.qml"));
125 QCOMPARE(objectCreated.count(), size);
126 QCOMPARE(test->rootObjects().size(), ++size);
127 QVERIFY(test->rootObjects()[size -1]);
128 QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
129
130 delete test;
131}
132
133void tst_QQmlImport::importPathOrder()
134{
135#ifdef Q_OS_ANDROID
136 QSKIP("QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath) returns bogus path on Android, but its nevertheless unusable.");
137#endif
138 QStringList expectedImportPaths;
139 QString appDirPath = QCoreApplication::applicationDirPath();
140 QString qml2Imports = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
141#ifdef Q_OS_WIN
142 // The drive letter has a different case as QQmlImport will
143 // cause it to be converted after passing through QUrl
144 appDirPath[0] = appDirPath[0].toUpper();
145 qml2Imports[0] = qml2Imports[0].toUpper();
146#endif
147 expectedImportPaths << appDirPath
148 << QLatin1String("qrc:/qt-project.org/imports")
149 << qml2Imports;
150 QQmlEngine engine;
151 QCOMPARE(expectedImportPaths, engine.importPathList());
152
153 // Add an import path
154 engine.addImportPath(QT_QMLTEST_DATADIR);
155 expectedImportPaths.prepend(QT_QMLTEST_DATADIR);
156 QCOMPARE(expectedImportPaths, engine.importPathList());
157}
158
159Q_DECLARE_METATYPE(QQmlImports::ImportVersion)
160
161void tst_QQmlImport::completeQmldirPaths_data()
162{
163 QTest::addColumn<QString>(name: "uri");
164 QTest::addColumn<QStringList>(name: "basePaths");
165 QTest::addColumn<int>(name: "majorVersion");
166 QTest::addColumn<int>(name: "minorVersion");
167 QTest::addColumn<QStringList>(name: "expectedPaths");
168
169 QTest::newRow(dataTag: "QtQml") << "QtQml" << (QStringList() << "qtbase/qml/" << "path/to/qml") << 2 << 7
170 << (QStringList() << "qtbase/qml/QtQml.2.7/qmldir" << "path/to/qml/QtQml.2.7/qmldir"
171 << "qtbase/qml/QtQml.2/qmldir" << "path/to/qml/QtQml.2/qmldir"
172 << "qtbase/qml/QtQml/qmldir" << "path/to/qml/QtQml/qmldir");
173
174 QTest::newRow(dataTag: "QtQml.Models") << "QtQml.Models" << QStringList("qtbase/qml/") << 2 << 2
175 << (QStringList() << "qtbase/qml/QtQml/Models.2.2/qmldir" << "qtbase/qml/QtQml.2.2/Models/qmldir"
176 << "qtbase/qml/QtQml/Models.2/qmldir" << "qtbase/qml/QtQml.2/Models/qmldir"
177 << "qtbase/qml/QtQml/Models/qmldir");
178
179 QTest::newRow(dataTag: "org.qt-project.foo.bar") << "org.qt-project.foo.bar" << QStringList("qtbase/qml/") << 0 << 1
180 << (QStringList() << "qtbase/qml/org/qt-project/foo/bar.0.1/qmldir" << "qtbase/qml/org/qt-project/foo.0.1/bar/qmldir" << "qtbase/qml/org/qt-project.0.1/foo/bar/qmldir" << "qtbase/qml/org.0.1/qt-project/foo/bar/qmldir"
181 << "qtbase/qml/org/qt-project/foo/bar.0/qmldir" << "qtbase/qml/org/qt-project/foo.0/bar/qmldir" << "qtbase/qml/org/qt-project.0/foo/bar/qmldir" << "qtbase/qml/org.0/qt-project/foo/bar/qmldir"
182 << "qtbase/qml/org/qt-project/foo/bar/qmldir");
183}
184
185void tst_QQmlImport::completeQmldirPaths()
186{
187 QFETCH(QString, uri);
188 QFETCH(QStringList, basePaths);
189 QFETCH(int, majorVersion);
190 QFETCH(int, minorVersion);
191 QFETCH(QStringList, expectedPaths);
192
193 QCOMPARE(QQmlImports::completeQmldirPaths(uri, basePaths, majorVersion, minorVersion), expectedPaths);
194}
195
196class QmldirUrlInterceptor : public QQmlAbstractUrlInterceptor {
197public:
198 QUrl intercept(const QUrl &url, DataType type) override
199 {
200 if (type != UrlString && !url.isEmpty() && url.isValid()) {
201 QString str = url.toString(options: QUrl::None);
202 return str.replace(QStringLiteral("$(INTERCEPT)"), QStringLiteral("intercepted"));
203 }
204 return url;
205 }
206};
207
208void tst_QQmlImport::interceptQmldir()
209{
210 QQmlEngine engine;
211 QmldirUrlInterceptor interceptor;
212 engine.setUrlInterceptor(&interceptor);
213
214 QQmlComponent component(&engine);
215 component.loadUrl(url: testFileUrl(fileName: "interceptQmldir.qml"));
216 QVERIFY(component.isReady());
217 QScopedPointer<QObject> obj(component.create());
218 QVERIFY(!obj.isNull());
219}
220
221// QTBUG-77102
222void tst_QQmlImport::singletonVersionResolution()
223{
224 QQmlEngine engine;
225 engine.addImportPath(dir: testFile(fileName: "QTBUG-77102/imports"));
226 {
227 // Singleton with higher version is simply ignored when importing lower version of plugin
228 QQmlComponent component(&engine);
229 component.loadUrl(url: testFileUrl(fileName: "QTBUG-77102/main.0.9.qml"));
230 QVERIFY(component.isReady());
231 QScopedPointer<QObject> obj(component.create());
232 QVERIFY(!obj.isNull());
233 }
234 {
235 // but the singleton is not accessible
236 QQmlComponent component(&engine);
237 QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, messagePattern: QRegularExpression {".*ReferenceError: MySettings is not defined$"} );
238 component.loadUrl(url: testFileUrl(fileName: "QTBUG-77102/main.0.9.fail.qml"));
239 QVERIFY(component.isReady());
240 QScopedPointer<QObject> obj(component.create());
241 QVERIFY(!obj.isNull());
242 }
243 {
244 // unless a version which is high enough is imported
245 QQmlComponent component(&engine);
246 component.loadUrl(url: testFileUrl(fileName: "QTBUG-77102/main.1.0.qml"));
247 QVERIFY(component.isReady());
248 QScopedPointer<QObject> obj(component.create());
249 QVERIFY(!obj.isNull());
250 auto item = qobject_cast<QQuickItem*>(object: obj.get());
251 QCOMPARE(item->width(), 50);
252 }
253 {
254 // or when there is no number because we are importing from a path
255 QQmlComponent component(&engine);
256 component.loadUrl(url: testFileUrl(fileName: "QTBUG-77102/main.nonumber.qml"));
257 QVERIFY(component.isReady());
258 QScopedPointer<QObject> obj(component.create());
259 QVERIFY(!obj.isNull());
260 auto item = qobject_cast<QQuickItem*>(object: obj.get());
261 QCOMPARE(item->width(), 50);
262 }
263}
264
265void tst_QQmlImport::removeDynamicPlugin()
266{
267 qmlClearTypeRegistrations();
268 QQmlEngine engine;
269 {
270 // Load something that adds a dynamic plugin
271 QQmlComponent component(&engine);
272 component.setData(QByteArray("import QtTest 1.0; TestResult{}"), baseUrl: QUrl());
273 QVERIFY(component.isReady());
274 }
275 QQmlImportDatabase *imports = &QQmlEnginePrivate::get(e: &engine)->importDatabase;
276 const QStringList &plugins = imports->dynamicPlugins();
277 QVERIFY(!plugins.isEmpty());
278 for (const QString &plugin : plugins)
279 QVERIFY(imports->removeDynamicPlugin(plugin));
280 QVERIFY(imports->dynamicPlugins().isEmpty());
281 qmlClearTypeRegistrations();
282}
283
284
285QTEST_MAIN(tst_QQmlImport)
286
287#include "tst_qqmlimport.moc"
288

source code of qtdeclarative/tests/auto/qml/qqmlimport/tst_qqmlimport.cpp