Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <private/qmlutils_p.h>
#include <private/qqmlengine_p.h>
#include <private/qqmlimport_p.h>
#include <private/qqmlpluginimporter_p.h>
#include <QtQuick/qquickview.h>
#include <QtQuick/qquickitem.h>
#include <QtTest/qsignalspy.h>
#include <QtQml/qqmlabstracturlinterceptor.h>
#include <QtQml/qqmlapplicationengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlmoduleregistration.h>
#include <QtCore/qscopeguard.h>
#include <QtCore/qlibraryinfo.h>
#include <QtCore/private/qlibraryinfo_p.h>
#include <QtCore/qpluginloader.h>
#include "MyStaticModule/mystaticmoduleplugin.h"
using namespace Qt::StringLiterals;
class TheThing : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int width MEMBER m_width FINAL)
public:
int m_width = 12;
};
void qml_register_types_noimport()
{
qmlRegisterTypesAndRevisions<TheThing>("noimport", 1);
qmlRegisterModule("noimport", 1, 0);
}
class tst_QQmlImport : public QQmlDataTest
{
Q_OBJECT
public:
tst_QQmlImport()
: QQmlDataTest(QT_QMLTEST_DATADIR)
, noimportRegistration("noimport", qml_register_types_noimport)
{
}
private slots:
void importPathOrder();
void testDesignerSupported();
void uiFormatLoading();
void completeQmldirPaths_data();
void completeQmldirPaths();
void interceptQmldir();
void singletonVersionResolution();
void removeDynamicPlugin();
void removeStaticPlugins();
void partialImportVersions_data();
void partialImportVersions();
void registerModuleImport();
void importDependenciesPrecedence();
void importFromNamespacedDirctoryResolvesCorrectly();
void cleanup();
void envResourceImportPath();
void preferResourcePath_data();
void preferResourcePath();
void preferResourcePathScript();
void invalidFileImport_data();
void invalidFileImport();
void implicitWithDependencies();
void qualifiedScriptImport();
void invalidImportUrl();
void registerTypesFromImplicitImport_data();
void registerTypesFromImplicitImport();
void containsAllQtConfEntries();
void sanitizeUNCPath();
void implicitImportOfModuleDependingOnPlugin();
private:
QQmlModuleRegistration noimportRegistration;
};
void tst_QQmlImport::cleanup()
{
QQmlImports::setDesignerSupportRequired(false);
}
void tst_QQmlImport::envResourceImportPath()
{
const bool hadEnv = qEnvironmentVariableIsSet("QML_IMPORT_PATH");
const QByteArray oldEnv = hadEnv ? qgetenv("QML_IMPORT_PATH") : QByteArray();
auto guard = qScopeGuard([&] {
if (hadEnv)
qputenv("QML_IMPORT_PATH", oldEnv);
else
qunsetenv("QML_IMPORT_PATH");
});
const QStringList envPaths({
QLatin1String(":/some/resource"),
dataDirectory(),
QLatin1String(":/some/other/resource"),
directory()
});
qputenv("QML_IMPORT_PATH", envPaths.join(QDir::listSeparator()).toUtf8());
QQmlEngine engine;
const QStringList importPaths = QQmlTypeLoader::get(&engine)->importPathList();
for (const QString &path : envPaths)
QVERIFY((importPaths.contains(path.startsWith(u':') ? QLatin1String("qrc") + path : path)));
}
void tst_QQmlImport::preferResourcePath_data()
{
QTest::addColumn<QUrl>("file");
QTest::addRow("without qmldir") << testFileUrl("prefer.qml");
QTest::addRow("with qmldir") << testFileUrl("prefer2.qml");
}
void tst_QQmlImport::preferResourcePath()
{
QFETCH(QUrl, file);
QQmlEngine engine;
engine.addImportPath(dataDirectory());
QQmlComponent component(&engine, file);
QVERIFY2(component.isReady(), component.errorString().toUtf8());
QScopedPointer<QObject> o(component.create());
QCOMPARE(o->objectName(), "right");
}
void tst_QQmlImport::preferResourcePathScript()
{
// A qualified import of a module with a "prefer" directive must resolve
// its scripts from the preferred (resource) location, just like its
// components. Otherwise the script next to the qmldir on disk would be
// used, which does not exist when the module is deployed with its files
// compiled into resources. QTBUG-143877
QQmlEngine engine;
engine.addImportPath(dataDirectory());
QQmlComponent component(&engine, testFileUrl("preferScriptQualified.qml"));
QVERIFY2(component.isReady(), component.errorString().toUtf8());
QScopedPointer<QObject> o(component.create());
QVERIFY(!o.isNull());
QCOMPARE(o->objectName(), "right");
}
void tst_QQmlImport::invalidFileImport_data()
{
QTest::addColumn<QString>("file");
QTest::addColumn<QString>("import");
QTest::addRow("file absolute")
<< QStringLiteral("absoluteImport.qml")
<< QStringLiteral("/foo/bar/baz");
QTest::addRow("resource absolute")
<< QStringLiteral("absoluteResourceImport.qml")
<< QStringLiteral(":/absolute/resource/path");
QTest::addRow("resource relative")
<< QStringLiteral("relativeResourceImport.qml")
<< QStringLiteral(":relative/resource/path");
}
void tst_QQmlImport::invalidFileImport()
{
QFETCH(QString, file);
QFETCH(QString, import);
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl(file));
QVERIFY(component.isError());
QVERIFY(component.errorString().contains(
QStringLiteral("\"%1\" is not a valid import URL. "
"You can pass relative paths or URLs with schema, "
"but not absolute paths or resource paths.").arg(import)));
}
void tst_QQmlImport::implicitWithDependencies()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("implicitWithDependencies/A.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> o(component.create());
QVERIFY(!o.isNull());
QCOMPARE(o->objectName(), QStringLiteral("notARectangle"));
}
void tst_QQmlImport::implicitImportOfModuleDependingOnPlugin()
{
QQmlApplicationEngine engine;
engine.load(QUrl(u"qrc:/ModuleImportingShapes/Main.qml"_s));
QCOMPARE(engine.rootObjects().size(), 1);
QCOMPARE(engine.rootObjects().constFirst()->objectName(), u"main"_s);
}
void tst_QQmlImport::qualifiedScriptImport()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("qualifiedScriptImport.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> o(component.create());
QVERIFY(!o.isNull());
QCOMPARE(o->property("a"), QVariant::fromValue<double>(12));
QCOMPARE(o->property("b"), QVariant::fromValue<int>(3));
QCOMPARE(o->property("c"), QVariant());
}
void tst_QQmlImport::invalidImportUrl()
{
QQmlEngine engine;
const QUrl url = testFileUrl("fileDotSlashImport.qml");
QQmlComponent component(&engine, url);
QVERIFY(component.isError());
QCOMPARE(component.errorString(),
url.toString()
+ QLatin1String(
}
void tst_QQmlImport::registerTypesFromImplicitImport_data()
{
QTest::addColumn<QUrl>("testfile");
QTest::addRow("immediate") << testFileUrl("noimport/Main.qml");
QTest::addRow("redirected") << testFileUrl("noimport/redirected/Redirected.qml");
QTest::addRow("dirimported") << testFileUrl("noimport/dirimported/Dirimported.qml");
}
void tst_QQmlImport::registerTypesFromImplicitImport()
{
QFETCH(QUrl, testfile);
QQmlEngine engine;
QQmlComponent c(&engine, testfile);
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QVERIFY(!o.isNull());
TheThing *t = qobject_cast<TheThing *>(o.data());
QVERIFY(t);
QCOMPARE(t->m_width, 640);
}
void tst_QQmlImport::containsAllQtConfEntries()
{
QString qtConfPath(u":/qmlimports.qt.conf");
QLibraryInfoPrivate::setQtconfManualPath(&qtConfPath);
QLibraryInfoPrivate::reload();
auto cleanup = qScopeGuard([](){
QLibraryInfoPrivate::setQtconfManualPath(nullptr);
QLibraryInfoPrivate::reload();
});
QQmlEngine engine;
auto importPaths = engine.importPathList();
QVERIFY(importPaths.contains(u"qrc:/a/path"));
QVERIFY(importPaths.contains(u"qrc:/another/path"));
QVERIFY(importPaths.contains(u"qrc:/even/more/path"));
}
void tst_QQmlImport::sanitizeUNCPath()
{
QString wildUNCPath = QStringLiteral("//Server2/Sh%re/foO/qmldir");
QQmlTypeLoader::sanitizeUNCPath(&wildUNCPath);
// It lowercases the "server" component of the path. The rest is left as-is
QCOMPARE(wildUNCPath, QStringLiteral("//server2/Sh%re/foO/qmldir"));
}
void tst_QQmlImport::testDesignerSupported()
{
std::unique_ptr<QQuickView> window = std::make_unique<QQuickView>();
window->engine()->addImportPath(directory());
window->setSource(testFileUrl("testfile_supported.qml"));
QVERIFY(window->errors().isEmpty());
window->setSource(testFileUrl("testfile_unsupported.qml"));
QVERIFY(window->errors().isEmpty());
QQmlImports::setDesignerSupportRequired(true);
//imports are cached so we create a new window
window = std::make_unique<QQuickView>();
window->engine()->addImportPath(directory());
window->engine()->clearComponentCache();
window->setSource(testFileUrl("testfile_supported.qml"));
QVERIFY(window->errors().isEmpty());
QString warningString("%1:5:1: module does not support the designer \"MyPluginUnsupported\" \n import MyPluginUnsupported 1.0\r \n ^ ");
#if !defined(Q_OS_WIN)
warningString.remove('\r');
#endif
warningString = warningString.arg(testFileUrl("testfile_unsupported.qml").toString());
QTest::ignoreMessage(QtWarningMsg, warningString.toLocal8Bit());
window->setSource(testFileUrl("testfile_unsupported.qml"));
QVERIFY(!window->errors().isEmpty());
}
void tst_QQmlImport::uiFormatLoading()
{
int size = 0;
std::unique_ptr<QQmlApplicationEngine> test = std::make_unique<QQmlApplicationEngine>(testFileUrl("TestForm.ui.qml"));
test->addImportPath(directory());
QCOMPARE(test->rootObjects().size(), ++size);
QVERIFY(test->rootObjects()[size -1]);
QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
QSignalSpy objectCreated(test.get(), SIGNAL(objectCreated(QObject*,QUrl)));
test->load(testFileUrl("TestForm.ui.qml"));
QCOMPARE(objectCreated.size(), size);//one less than rootObjects().size() because we missed the first one
QCOMPARE(test->rootObjects().size(), ++size);
QVERIFY(test->rootObjects()[size -1]);
QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
QByteArray testQml("import QtQml 2.0; QtObject{property bool success: true; property TestForm t: TestForm{}}");
test->loadData(testQml, testFileUrl("dynamicTestForm.ui.qml"));
QCOMPARE(objectCreated.size(), size);
QCOMPARE(test->rootObjects().size(), ++size);
QVERIFY(test->rootObjects()[size -1]);
QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
test->load(testFileUrl("openTestFormFromDir.qml"));
QCOMPARE(objectCreated.size(), size);
QCOMPARE(test->rootObjects().size(), ++size);
QVERIFY(test->rootObjects()[size -1]);
QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
test->load(testFileUrl("openTestFormFromQmlDir.qml"));
QCOMPARE(objectCreated.size(), size);
QCOMPARE(test->rootObjects().size(), ++size);
QVERIFY(test->rootObjects()[size -1]);
QVERIFY(test->rootObjects()[size -1]->property("success").toBool());
}
void tst_QQmlImport::importPathOrder()
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_HARMONY)
QSKIP("QLibraryInfo::path(QLibraryInfo::QmlImportsPath) returns bogus path on this platform, but it's nevertheless unusable.");
#endif
QStringList expectedImportPaths;
QString appDirPath = QCoreApplication::applicationDirPath();
QString qml2Imports = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
#ifdef Q_OS_WIN
// The drive letter has a different case as QQmlImport will
// cause it to be converted after passing through QUrl
appDirPath[0] = appDirPath[0].toUpper();
qml2Imports[0] = qml2Imports[0].toUpper();
#endif
expectedImportPaths << appDirPath
<< QLatin1String("qrc:/qt-project.org/imports")
<< QLatin1String("qrc:/qt/qml")
<< qml2Imports;
QQmlEngine engine;
QCOMPARE(engine.importPathList(), expectedImportPaths);
// Add an import path
engine.addImportPath(QT_QMLTEST_DATADIR);
QFileInfo fi(QT_QMLTEST_DATADIR);
expectedImportPaths.prepend(fi.absoluteFilePath());
QCOMPARE(engine.importPathList(), expectedImportPaths);
// Add qml2Imports again to make it the first of the list
engine.addImportPath(qml2Imports);
expectedImportPaths.move(expectedImportPaths.indexOf(qml2Imports), 0);
QCOMPARE(engine.importPathList(), expectedImportPaths);
// Verify if the type in the module comes first in the import path list
// takes the precedence. In the case below, the width of both items
// should be the same to that of the type defined in "path2".
engine.addImportPath(testFile("importPathOrder/path1"));
engine.addImportPath(testFile("importPathOrder/path2"));
QQmlComponent component(&engine, testFile("importPathOrder/MyModuleTest.qml"));
QScopedPointer<QObject> rootItem(component.create());
QVERIFY(component.errorString().isEmpty());
QVERIFY(!rootItem.isNull());
QQuickItem *item1 = rootItem->findChild<QQuickItem*>("myItem1");
QQuickItem *item2 = rootItem->findChild<QQuickItem*>("myItem2");
QVERIFY(item1 != nullptr);
QVERIFY(item2 != nullptr);
QCOMPARE(item1->width(), 200);
QCOMPARE(item2->width(), 200);
}
Q_DECLARE_METATYPE(QQmlImports::ImportVersion)
void tst_QQmlImport::completeQmldirPaths_data()
{
QTest::addColumn<QString>("uri");
QTest::addColumn<QStringList>("basePaths");
QTest::addColumn<QTypeRevision>("version");
QTest::addColumn<QStringList>("expectedPaths");
QTest::newRow("QtQml") << "QtQml" << (QStringList() << "qtbase/qml/" << "path/to/qml")
<< QTypeRevision::fromVersion(2, 7)
<< (QStringList() << "qtbase/qml/QtQml.2.7/qmldir" << "path/to/qml/QtQml.2.7/qmldir"
<< "qtbase/qml/QtQml.2/qmldir" << "path/to/qml/QtQml.2/qmldir"
<< "qtbase/qml/QtQml/qmldir" << "path/to/qml/QtQml/qmldir");
QTest::newRow("QtQml.Models") << "QtQml.Models" << QStringList("qtbase/qml/")
<< QTypeRevision::fromVersion(2, 2)
<< (QStringList() << "qtbase/qml/QtQml/Models.2.2/qmldir" << "qtbase/qml/QtQml.2.2/Models/qmldir"
<< "qtbase/qml/QtQml/Models.2/qmldir" << "qtbase/qml/QtQml.2/Models/qmldir"
<< "qtbase/qml/QtQml/Models/qmldir");
QTest::newRow("org.qt-project.foo.bar 0.1") << "org.qt-project.foo.bar" << QStringList("qtbase/qml/")
<< QTypeRevision::fromVersion(0, 1)
<< (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"
<< "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"
<< "qtbase/qml/org/qt-project/foo/bar/qmldir");
QTest::newRow("org.qt-project.foo.bar 4") << "org.qt-project.foo.bar" << QStringList("qtbase/qml/")
<< QTypeRevision::fromMajorVersion(4)
<< (QStringList()
<< "qtbase/qml/org/qt-project/foo/bar.4/qmldir"
<< "qtbase/qml/org/qt-project/foo.4/bar/qmldir"
<< "qtbase/qml/org/qt-project.4/foo/bar/qmldir"
<< "qtbase/qml/org.4/qt-project/foo/bar/qmldir"
<< "qtbase/qml/org/qt-project/foo/bar/qmldir");
QTest::newRow("org.qt-project.foo.bar") << "org.qt-project.foo.bar" << QStringList("qtbase/qml/")
<< QTypeRevision()
<< (QStringList()
<< "qtbase/qml/org/qt-project/foo/bar/qmldir");
}
void tst_QQmlImport::completeQmldirPaths()
{
QFETCH(QString, uri);
QFETCH(QStringList, basePaths);
QFETCH(QTypeRevision, version);
QFETCH(QStringList, expectedPaths);
QCOMPARE(QQmlImports::completeQmldirPaths(uri, basePaths, version), expectedPaths);
}
class QmldirUrlInterceptor : public QQmlAbstractUrlInterceptor {
public:
QUrl intercept(const QUrl &url, DataType type) override
{
if (type != UrlString && !url.isEmpty() && url.isValid()) {
QString str = url.toString(QUrl::None);
return QUrl{str.replace("$(INTERCEPT)"_L1, "intercepted"_L1)};
}
return url;
}
};
void tst_QQmlImport::interceptQmldir()
{
QQmlEngine engine;
QmldirUrlInterceptor interceptor;
engine.addUrlInterceptor(&interceptor);
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("interceptQmldir.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
// QTBUG-77102
void tst_QQmlImport::singletonVersionResolution()
{
QQmlEngine engine;
engine.addImportPath(testFile("QTBUG-77102/imports"));
{
// Singleton with higher version is simply ignored when importing lower version of plugin
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("QTBUG-77102/main.0.9.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
{
// but the singleton is not accessible
QQmlComponent component(&engine);
QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression {".*ReferenceError: MySettings is not defined$"} );
component.loadUrl(testFileUrl("QTBUG-77102/main.0.9.fail.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
{
// unless a version which is high enough is imported
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("QTBUG-77102/main.1.0.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
auto item = qobject_cast<QQuickItem*>(obj.get());
QCOMPARE(item->width(), 50);
}
{
// or when there is no number because we are importing from a path
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("QTBUG-77102/main.nonumber.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
auto item = qobject_cast<QQuickItem*>(obj.get());
QCOMPARE(item->width(), 50);
}
}
void tst_QQmlImport::removeDynamicPlugin()
{
qmlClearTypeRegistrations();
QQmlEngine engine;
QTest::ignoreMessage(QtWarningMsg, "Registered dynamic plugin!");
{
// Load something that adds a dynamic plugin
QQmlComponent component(&engine, testFileUrl("importDynamicModule.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
}
const QStringList &plugins = QQmlPluginImporter::plugins();
QTest::ignoreMessage(QtWarningMsg, "Unregistered dynamic plugin!");
QVERIFY(!plugins.isEmpty());
for (const QString &plugin : plugins)
QVERIFY(QQmlPluginImporter::removePlugin(plugin));
QVERIFY(QQmlPluginImporter::plugins().isEmpty());
qmlClearTypeRegistrations();
}
void tst_QQmlImport::removeStaticPlugins()
{
qmlClearTypeRegistrations();
QVERIFY(!MyStaticModulePlugin::s_myStaticModulePluginRegistered);
QQmlEngine engine;
{
// Load something that adds a static plugin
QQmlComponent component(&engine, testFileUrl("importStaticModule.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
}
QVERIFY(MyStaticModulePlugin::s_myStaticModulePluginRegistered);
QVERIFY(QQmlMetaType::qmlType("MyStaticModule/MyCppComponent", QTypeRevision::fromVersion(1, 0))
.isValid());
qmlClearTypeRegistrations();
QVERIFY(!MyStaticModulePlugin::s_myStaticModulePluginRegistered);
QVERIFY(!QQmlMetaType::qmlType("MyStaticModule/MyCppComponent",
QTypeRevision::fromVersion(1, 0))
.isValid());
}
void tst_QQmlImport::partialImportVersions_data()
{
QTest::addColumn<QString>("version");
QTest::addColumn<bool>("valid");
QTest::addRow("empty") << "" << true;
QTest::addRow("2") << "2" << true;
QTest::addRow("6") << "6" << true;
QTest::addRow("2.0") << "2.0" << false;
QTest::addRow("2.3") << "2.3" << true;
QTest::addRow("2.15") << "2.15" << true;
QTest::addRow("6.0") << "6.0" << true;
}
void tst_QQmlImport::partialImportVersions()
{
QFETCH(QString, version);
QFETCH(bool, valid);
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData("import QtQml " + version.toUtf8() + "; Connections { enabled: false }",
QUrl());
QCOMPARE(component.isReady(), valid);
if (valid) {
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
}
class NotItem : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(Item)
};
void tst_QQmlImport::registerModuleImport()
{
enum Result { IsItem, IsNotItem, IsNull, IsInvalid };
const auto check = [&]() -> Result {
QQmlEngine engine;
engine.addImportPath(directory());
QQmlComponent component(&engine);
component.setData("import MyPluginSupported; Item {}", QUrl());
if (!component.isReady())
return IsInvalid;
QScopedPointer<QObject> obj(component.create());
if (obj.isNull())
return IsNull;
if (qobject_cast<NotItem *>(obj.data()))
return IsNotItem;
else if (qobject_cast<QQuickItem *>(obj.data()))
return IsItem;
return IsInvalid;
};
const auto isValid = [&]() {
return check() == IsItem;
};
qmlRegisterTypesAndRevisions<NotItem>("ShadowQuick", 1);
qmlRegisterModuleImport("MyPluginSupported", 2, "QtQuick");
QVERIFY(isValid());
qmlUnregisterModuleImport("MyPluginSupported", 2, "QtQuick");
QVERIFY(!isValid());
qmlRegisterModuleImport("MyPluginSupported", 3, "QtQuick"); // won't match, 3 doesn't exist
QVERIFY(!isValid());
qmlRegisterModuleImport("MyPluginSupported", 1, "QtQuick"); // won't match, as we import latest
QVERIFY(!isValid());
qmlRegisterModuleImport("MyPluginSupported", QQmlModuleImportModuleAny, "QtQuick");
QVERIFY(isValid());
qmlUnregisterModuleImport("MyPluginSupported", QQmlModuleImportModuleAny, "QtQuick");
QVERIFY(!isValid());
qmlRegisterModuleImport("MyPluginSupported", QQmlModuleImportModuleAny, "QtQuick",
QQmlModuleImportAuto); // matches, because both 2.0
QVERIFY(isValid());
qmlUnregisterModuleImport("MyPluginSupported", QQmlModuleImportModuleAny, "QtQuick",
QQmlModuleImportAuto);
QVERIFY(!isValid());
qmlRegisterModuleImport("MyPluginSupported", 2, "QtQuick", 2, 15);
QVERIFY(isValid());
qmlUnregisterModuleImport("MyPluginSupported", 2, "QtQuick", 2, 15);
QVERIFY(!isValid());
qmlUnregisterModuleImport("MyPluginSupported", 3, "QtQuick");
QVERIFY(!isValid());
qmlUnregisterModuleImport("MyPluginSupported", 1, "QtQuick");
QVERIFY(!isValid());
qmlRegisterModuleImport("MyPluginSupported", 2, "ShadowQuick", 1);
qmlRegisterModuleImport("MyPluginSupported", 2, "QtQuick", 2);
QCOMPARE(check(), IsItem);
qmlUnregisterModuleImport("MyPluginSupported", 2, "ShadowQuick", 1);
qmlUnregisterModuleImport("MyPluginSupported", 2, "QtQuick", 2);
qmlRegisterModuleImport("MyPluginSupported", 2, "QtQuick", 2);
qmlRegisterModuleImport("MyPluginSupported", 2, "ShadowQuick", 1);
QCOMPARE(check(), IsNotItem);
qmlUnregisterModuleImport("MyPluginSupported", 2, "QtQuick", 2);
qmlUnregisterModuleImport("MyPluginSupported", 2, "ShadowQuick", 1);
qmlRegisterTypesAndRevisions<NotItem>("NoQmldir", 2);
qmlRegisterModuleImport("NoQmldir", QQmlModuleImportModuleAny, "QtQml", QQmlModuleImportAuto);
{
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData(R"(
import NoQmldir 2.0
QtObject {
property Item item: Item {}
}
)", QUrl::fromLocalFile(""));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
std::unique_ptr<QObject> object { component.create() };
QVERIFY(object);
NotItem *item = object->property("item").value<NotItem *>();
QVERIFY(item);
}
}
void tst_QQmlImport::importDependenciesPrecedence()
{
QQmlEngine engine;
engine.addImportPath(dataDirectory());
QQmlComponent component(&engine, testFile("dependencies.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> instance(component.create());
QVERIFY(!instance.isNull());
QCOMPARE(instance->property("a").toString(), QString::fromLatin1("a"));
QCOMPARE(instance->property("b").toString(), QString::fromLatin1("b"));
}
void tst_QQmlImport::importFromNamespacedDirctoryResolvesCorrectly()
{
QQmlEngine engine;
engine.addImportPath(dataDirectory());
QQmlComponent component(&engine);
component.loadFromModule("QtQml", "Timer"); // ensure Timer is resolved once to QtQml already
QHashedString module {};
QHashedString typeName { QLatin1String("Timer") };
QVERIFY(QQmlMetaType::qmlType(typeName, module, {}).isValid());
component.loadUrl(testFileUrl("UsingCustomTimer.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> root(component.create());
QVERIFY(root);
QVERIFY(root->property("success").toBool());
}
QTEST_MAIN(tst_QQmlImport)
#include "tst_qqmlimport.moc"