From b32bd7f236a75ce060dfbb7a5ee99ebb5510585f Mon Sep 17 00:00:00 2001 From: Rafal Stawarski Date: Fri, 18 Oct 2024 12:04:45 +0200 Subject: [PATCH] QmlProjectManager: Generate .qmlproject file based on qmldir (McuModuleProjectItem) Task-number: QDS-13811 Change-Id: I187f04fbc4ba7752e9e1d7eb634c0904abae3a2f Reviewed-by: Marco Bubke Reviewed-by: Thomas Hartmann --- src/plugins/qmlprojectmanager/CMakeLists.txt | 8 + .../mcumoduleprojectitem.cpp | 261 +++++++++++++ .../qmldirtoqmlproject/mcumoduleprojectitem.h | 50 +++ .../qmlprojectmanager/CMakeLists.txt | 1 + .../qmlprojectmanager/data/README.md | 10 + .../existing_qmlproject/File1.qml | 5 + .../existing_qmlproject/File2.qml | 5 + .../existing_qmlproject/Internal/File3.qml | 5 + .../existing_qmlproject/TestSingleton.qml | 6 + .../existing_qmlproject/qmldir | 3 + .../test_module.qmlproject | 16 + .../TestSingleton.qml | 6 + .../incorrect_module_name_qmldir/qmldir | 3 + .../invalid_qmlproject/test_module.qmlproject | 5 + .../TestSingleton.qml | 6 + .../missing_module_name_qmldir/qmldir | 2 + .../missing_qml_files_qmldir/qmldir | 2 + .../missing_qmlproject/File1.qml | 5 + .../missing_qmlproject/File2.qml | 5 + .../missing_qmlproject/Internal/File3.qml | 5 + .../missing_qmlproject/TestSingleton.qml | 6 + .../missing_qmlproject/qmldir | 3 + .../mcumoduleprojectitem-test.cpp | 368 ++++++++++++++++++ 23 files changed, 786 insertions(+) create mode 100644 src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.cpp create mode 100644 src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.h create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File1.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File2.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/Internal/File3.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/TestSingleton.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/qmldir create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/TestSingleton.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/qmldir create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/invalid_qmlproject/test_module.qmlproject create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/TestSingleton.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/qmldir create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qml_files_qmldir/qmldir create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File1.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File2.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/Internal/File3.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/TestSingleton.qml create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/qmldir create mode 100644 tests/unit/tests/unittests/qmlprojectmanager/mcumoduleprojectitem-test.cpp diff --git a/src/plugins/qmlprojectmanager/CMakeLists.txt b/src/plugins/qmlprojectmanager/CMakeLists.txt index 5e237cccc89..08d46d35a50 100644 --- a/src/plugins/qmlprojectmanager/CMakeLists.txt +++ b/src/plugins/qmlprojectmanager/CMakeLists.txt @@ -53,6 +53,13 @@ extend_qtc_plugin(QmlProjectManager boilerplate.qrc ) +extend_qtc_plugin(QmlProjectManager + PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}/qmldirtoqmlproject + SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/qmldirtoqmlproject + SOURCES + mcumoduleprojectitem.cpp mcumoduleprojectitem.h +) + add_qtc_library(QmlProjectManagerLib OBJECT CONDITION WITH_TESTS AND Qt6_VERSION VERSION_GREATER_EQUAL 6.4.3 EXCLUDE_FROM_INSTALL @@ -66,4 +73,5 @@ add_qtc_library(QmlProjectManagerLib OBJECT buildsystem/projectitem/qmlprojectitem.cpp buildsystem/projectitem/qmlprojectitem.h buildsystem/projectitem/converters.cpp buildsystem/projectitem/converters.h qmlprojectexporter/filetypes.cpp qmlprojectexporter/filetypes.h + qmldirtoqmlproject/mcumoduleprojectitem.cpp qmldirtoqmlproject/mcumoduleprojectitem.h ) diff --git a/src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.cpp b/src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.cpp new file mode 100644 index 00000000000..867f12ee180 --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.cpp @@ -0,0 +1,261 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "mcumoduleprojectitem.h" + +#include +#include + +#include +#include + +using namespace Qt::Literals::StringLiterals; + +namespace Constants { +namespace QmlDir { +constexpr auto QMLDIR = "qmldir"_L1; +constexpr auto MODULE = "module"_L1; +constexpr auto QML_FILE_FILTER = "*.qml"_L1; +} // namespace QmlDir + +namespace Json { +constexpr auto MODULE_URI = "moduleUri"_L1; +constexpr auto QML_FILES = "qmlFiles"_L1; +constexpr auto QMLPROJECT_PATH = "qmlProjectPath"_L1; +} // namespace Json + +namespace QmlProject { +constexpr auto QMLPROJECT_EXTENSION = ".qmlproject"_L1; +constexpr auto MCU_MODULE = "MCU.Module"_L1; +constexpr auto URI = "uri"_L1; +constexpr auto QML_FILES = "QmlFiles"_L1; +constexpr auto FILES = "files"_L1; + +const auto QMLPROJECT_TEMPLATE = QString(R"(/* File generated by Qt Design Studio */ + +import QmlProject 1.3 +Project { + MCU.Module { + uri: %1 + } + QmlFiles { + files: [ + %2 + ] + } +} +)"); +} // namespace QmlProject +} // namespace Constants + +namespace { +Q_LOGGING_CATEGORY(log, "QmlProjectManager.McuModuleProjectItem") + +bool isValidQmlProjectPath(const Utils::FilePath &path) +{ + return path.endsWith(Constants::QmlProject::QMLPROJECT_EXTENSION) + && (path.ensureExistingFile() || path.parentDir().isWritableDir()); +} + +QJsonObject parseQmlProjectFile(const Utils::FilePath &qmlproject) +{ + auto qmlprojectPathStr = qmlproject.toFSPathString(); + + if (!qmlproject.exists()) { + qCCritical(log) << "qmlproject file not found:" << qmlprojectPathStr; + return {}; + } + + QmlJS::SimpleReader reader; + QmlJS::SimpleReaderNode::Ptr rootNode = reader.readFile(qmlprojectPathStr); + if (!reader.errors().isEmpty() || !rootNode->isValid()) { + qCCritical(log) << "Unable to parse:" << qmlprojectPathStr; + qCCritical(log) << reader.errors(); + return {}; + } + + QJsonObject result; + result.insert(Constants::Json::QMLPROJECT_PATH, qmlprojectPathStr); + + auto checkNodeName = [](const QString &node, const QString &expecedName) { + return node.compare(expecedName, Qt::CaseInsensitive) == 0; + }; + + //expected just two nodes: MCU.Module and QmlFiles + for (const auto &childNode : rootNode->children()) { + auto nodeName = childNode->name(); + if (checkNodeName(nodeName, Constants::QmlProject::MCU_MODULE)) { + result.insert(Constants::Json::MODULE_URI, + childNode->property(Constants::QmlProject::URI).value.toString()); + } else if (checkNodeName(nodeName, Constants::QmlProject::QML_FILES)) { + result.insert(Constants::Json::QML_FILES, + childNode->property(Constants::QmlProject::FILES).value.toJsonArray()); + } else { + qCWarning(log) << "Unsupported node:" << nodeName; + } + } + + return result; +} +} // namespace + +namespace QmlProjectManager { +McuModuleProjectItem::McuModuleProjectItem(const QJsonObject &project) + : m_project(project) +{} + +McuModuleProjectItem::McuModuleProjectItem(const Utils::FilePath &qmlprojectFile) + : m_qmlProjectFile(qmlprojectFile) + , m_project(parseQmlProjectFile(m_qmlProjectFile)) +{ +} + +std::optional McuModuleProjectItem::fromQmldirModule(const Utils::FilePath &qmldirFile) +{ + auto qmldirFileStr = qmldirFile.toFSPathString(); + + // check qmldirFile + if (!qmldirFile.exists()) { + qCWarning(log) << "File not found:" << qmldirFileStr; + return {}; + } + if (qmldirFile.fileName() != Constants::QmlDir::QMLDIR) { + qCWarning(log) << "It's not qmldir file:" << qmldirFileStr; + return {}; + } + + auto qmldirContents = qmldirFile.fileContents(); + if (!qmldirContents) { + qCWarning(log) << "Unable to read the file:" << qmldirFileStr + << ", error:" << qmldirContents.error(); + return {}; + } + + // find module name + QByteArray fileContents = qmldirContents.value(); + QTextStream ts(fileContents); + QString moduleName; + + while (!ts.atEnd()) { + QString line = ts.readLine().trimmed(); + if (line.startsWith(Constants::QmlDir::MODULE, Qt::CaseInsensitive)) { + auto list = line.split(' '); + if (list.size() != 2) { + qCWarning(log) << "Invalid module identifier:" << line; + return {}; + } + moduleName = list.last(); + break; + } + } + + if (moduleName.isEmpty()) { + qCWarning(log) << "Module name not found in the qmldir"; + return {}; + } + + // list qml files + const auto qmldirParent = qmldirFile.parentDir(); + auto qmlDirEntries = qmldirParent.dirEntries(Utils::FileFilter{{Constants::QmlDir::QML_FILE_FILTER}, + QDir::NoFilter, + QDirIterator::Subdirectories}); + if (qmlDirEntries.empty()) { + qCWarning(log) << "No qml files found in:" << qmldirParent; + return {}; + } + auto qmlFiles = Utils::transform(qmlDirEntries, [qmldirParent](const Utils::FilePath &path) { + return path.relativePathFrom(qmldirParent).toFSPathString(); + }); + + // build mcu module project + QJsonObject result; + result.insert(Constants::Json::MODULE_URI, moduleName); + result.insert(Constants::Json::QML_FILES, QJsonArray::fromStringList(qmlFiles)); + + auto filename = moduleName.replace('.', '_'); + auto qmlprojectPath = qmldirParent.resolvePath( + Utils::FilePath::fromString(filename + Constants::QmlProject::QMLPROJECT_EXTENSION)); + result.insert(Constants::Json::QMLPROJECT_PATH, qmlprojectPath.toFSPathString()); + + return McuModuleProjectItem(result); +} + +bool McuModuleProjectItem::isValid() const noexcept +{ + return !uri().isEmpty() && !qmlFiles().isEmpty() && isValidQmlProjectPath(qmlProjectPath()); +} + +QString McuModuleProjectItem::uri() const noexcept +{ + return m_project[Constants::Json::MODULE_URI].toString(); +} + +void McuModuleProjectItem::setUri(const QString &moduleUri) +{ + m_project[Constants::Json::MODULE_URI] = moduleUri; +} + +QStringList McuModuleProjectItem::qmlFiles() const noexcept +{ + return m_project[Constants::Json::QML_FILES].toVariant().toStringList(); +} + +void McuModuleProjectItem::setQmlFiles(const QStringList &files) +{ + m_project[Constants::Json::QML_FILES] = QJsonArray::fromStringList(files); +} + +Utils::FilePath McuModuleProjectItem::qmlProjectPath() const noexcept +{ + return Utils::FilePath::fromString(m_project[Constants::Json::QMLPROJECT_PATH].toString()); +} + +void McuModuleProjectItem::setQmlProjectPath(const Utils::FilePath &path) +{ + m_project[Constants::Json::QMLPROJECT_PATH] = path.toFSPathString(); +} + +QJsonObject McuModuleProjectItem::project() const noexcept +{ + return m_project; +} + +bool McuModuleProjectItem::saveQmlProjectFile() const +{ + if (!isValid()) { + return false; + } + + auto path = qmlProjectPath(); + if (path.exists()) { + if (McuModuleProjectItem old(path); old == *this) { + return false; + } + } + + QTC_ASSERT_EXPECTED(path.writeFileContents(jsonToQmlproject()), return false); + return true; +} + +bool McuModuleProjectItem::operator==(const McuModuleProjectItem &other) const noexcept +{ + return this->project() == other.project(); +} + +QByteArray McuModuleProjectItem::jsonToQmlproject() const +{ + auto quoted = [](const QString &s) { return QString("\"%1\"").arg(s); }; + auto indent = [](int tabs = 1) { return QString(" ").repeated(tabs * 4); }; + auto quotedQmlFiles = Utils::transform(qmlFiles(), [quoted](const QString &file) { + return quoted(file); + }); + + QString qmlFilesSeparator; + QTextStream ts(&qmlFilesSeparator); + ts << "," << Qt::endl << indent(3); + + return Constants::QmlProject::QMLPROJECT_TEMPLATE + .arg(quoted(uri()), quotedQmlFiles.join(qmlFilesSeparator)) + .toUtf8(); +} +} // namespace QmlProjectManager diff --git a/src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.h b/src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.h new file mode 100644 index 00000000000..fea924e29b7 --- /dev/null +++ b/src/plugins/qmlprojectmanager/qmldirtoqmlproject/mcumoduleprojectitem.h @@ -0,0 +1,50 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "../qmlprojectmanager_global.h" + +#include + +#include + +#ifndef UNIT_TESTS +# define MCUMODULEPROJEC_EXPORT QMLPROJECTMANAGER_EXPORT +#else +# define MCUMODULEPROJEC_EXPORT +#endif + +namespace QmlProjectManager { +class MCUMODULEPROJEC_EXPORT McuModuleProjectItem +{ +public: + explicit McuModuleProjectItem(const QJsonObject &project); + explicit McuModuleProjectItem(const Utils::FilePath &qmlprojectFile); + + static std::optional fromQmldirModule(const Utils::FilePath &qmldirFile); + + bool isValid() const noexcept; + + QString uri() const noexcept; + void setUri(const QString &moduleUri); + + QStringList qmlFiles() const noexcept; + void setQmlFiles(const QStringList &files); + + Utils::FilePath qmlProjectPath() const noexcept; + void setQmlProjectPath(const Utils::FilePath &path); + + QJsonObject project() const noexcept; + + bool saveQmlProjectFile() const; + + bool operator==(const McuModuleProjectItem &other) const noexcept; + +private: + QByteArray jsonToQmlproject() const; + + Utils::FilePath m_qmlProjectFile; + QJsonObject m_project; +}; +} // namespace QmlProjectManager diff --git a/tests/unit/tests/unittests/qmlprojectmanager/CMakeLists.txt b/tests/unit/tests/unittests/qmlprojectmanager/CMakeLists.txt index a3859a9ce7b..52c5dcac459 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/CMakeLists.txt +++ b/tests/unit/tests/unittests/qmlprojectmanager/CMakeLists.txt @@ -6,6 +6,7 @@ extend_qtc_test(unittest SOURCES converters-test.cpp projectitem-test.cpp + mcumoduleprojectitem-test.cpp ) unittest_copy_data_folder() diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/README.md b/tests/unit/tests/unittests/qmlprojectmanager/data/README.md index afb4db417f5..c5067a10695 100644 --- a/tests/unit/tests/unittests/qmlprojectmanager/data/README.md +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/README.md @@ -70,3 +70,13 @@ Some main points for qmlproject files for MCU projects: Test data contains an example project folders that file filters will be initialized and tested. * **filelist.txt**: List of the files need to be found by the file filters. + +## Qml to qmlproject test data +Input data for the McuModuleProjectItem tests. McuModuleProjectItem represents the MCU module and can be generated from a regular QML module (based on qmldir). + +* **existing_qmlproject**: read and process valid .qmlproject module +* **incorrect_module_name_qmldir**: generate .qmlproject based on qmldir (failure - module name is wrong) +* **invalid_qmlproject**: read and process invalid .qmlproject module +* **missing_module_name_qmldir**: generate .qmlproject based on qmldir (failure - missing module name) +* **missing_qml_files_qmldir**: generate .qmlproject based on qmldir (failure - missing qml files) +* **missing_qmlproject**: generate .qmlproject based on qmldir (success) diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File1.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File1.qml new file mode 100644 index 00000000000..e136cb34d19 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File1.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File2.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File2.qml new file mode 100644 index 00000000000..e136cb34d19 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/File2.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/Internal/File3.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/Internal/File3.qml new file mode 100644 index 00000000000..e136cb34d19 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/Internal/File3.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/TestSingleton.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/TestSingleton.qml new file mode 100644 index 00000000000..d27a193422d --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/TestSingleton.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/qmldir b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/qmldir new file mode 100644 index 00000000000..bc85f5eb760 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/qmldir @@ -0,0 +1,3 @@ +# test comment +module test.module +singleton TestSingleton 1.0 TestSingleton.qml diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject new file mode 100644 index 00000000000..8dea55b7dc2 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject @@ -0,0 +1,16 @@ +import QmlProject 1.3 + +Project { + MCU.Module { + uri: "test.module" + } + + QmlFiles { + files: [ + "TestSingleton.qml", + "File1.qml", + "File2.qml", + "Internal/File3.qml" + ] + } +} \ No newline at end of file diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/TestSingleton.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/TestSingleton.qml new file mode 100644 index 00000000000..d27a193422d --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/TestSingleton.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/qmldir b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/qmldir new file mode 100644 index 00000000000..870207b6326 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/incorrect_module_name_qmldir/qmldir @@ -0,0 +1,3 @@ +# test comment +module test module +singleton TestSingleton 1.0 TestSingleton.qml diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/invalid_qmlproject/test_module.qmlproject b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/invalid_qmlproject/test_module.qmlproject new file mode 100644 index 00000000000..05ee3902ec0 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/invalid_qmlproject/test_module.qmlproject @@ -0,0 +1,5 @@ +import QmlProject 1.3 + +Project { + +} \ No newline at end of file diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/TestSingleton.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/TestSingleton.qml new file mode 100644 index 00000000000..d27a193422d --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/TestSingleton.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/qmldir b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/qmldir new file mode 100644 index 00000000000..89a03cef545 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_module_name_qmldir/qmldir @@ -0,0 +1,2 @@ +# test comment +singleton TestSingleton 1.0 TestSingleton.qml diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qml_files_qmldir/qmldir b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qml_files_qmldir/qmldir new file mode 100644 index 00000000000..0c44ccb6166 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qml_files_qmldir/qmldir @@ -0,0 +1,2 @@ +# test comment +module test.module \ No newline at end of file diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File1.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File1.qml new file mode 100644 index 00000000000..e136cb34d19 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File1.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File2.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File2.qml new file mode 100644 index 00000000000..e136cb34d19 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/File2.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/Internal/File3.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/Internal/File3.qml new file mode 100644 index 00000000000..e136cb34d19 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/Internal/File3.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/TestSingleton.qml b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/TestSingleton.qml new file mode 100644 index 00000000000..d27a193422d --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/TestSingleton.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQuick + +QtObject { + +} diff --git a/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/qmldir b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/qmldir new file mode 100644 index 00000000000..bc85f5eb760 --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/data/qmldirtoqmlproject/missing_qmlproject/qmldir @@ -0,0 +1,3 @@ +# test comment +module test.module +singleton TestSingleton 1.0 TestSingleton.qml diff --git a/tests/unit/tests/unittests/qmlprojectmanager/mcumoduleprojectitem-test.cpp b/tests/unit/tests/unittests/qmlprojectmanager/mcumoduleprojectitem-test.cpp new file mode 100644 index 00000000000..885e6ca238c --- /dev/null +++ b/tests/unit/tests/unittests/qmlprojectmanager/mcumoduleprojectitem-test.cpp @@ -0,0 +1,368 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "../utils/googletest.h" // IWYU pragma: keep + +#include + +#include + +namespace { + +constexpr QLatin1String localTestDataDir{UNITTEST_DIR "/qmlprojectmanager/data"}; + +constexpr QLatin1String jsonProject{R"( +{ + "moduleUri": "test.module", + "qmlFiles": [ + "TestSingleton.qml", + "File1.qml", + "File2.qml", + "Internal/File3.qml" + ], + "qmlProjectPath": "%1" +} +)"}; + +class McuModuleProjectItem : public testing::Test +{ +protected: + static void SetUpTestSuite() + { + existingQmlProject = std::make_unique( + Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject")); + + auto fromQmldir = QmlProjectManager::McuModuleProjectItem::fromQmldirModule( + Utils::FilePath::fromString(localTestDataDir + + "/qmldirtoqmlproject/missing_qmlproject/qmldir")); + missingQmlProject = std::make_unique( + fromQmldir ? *fromQmldir : QmlProjectManager::McuModuleProjectItem{QJsonObject{}}); + + invalidQmlProject = std::make_unique( + Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/invalid_qmlproject/test_module.qmlproject")); + + fromJsonObject = std::make_unique( + QJsonDocument::fromJson( + jsonProject.arg(localTestDataDir + "/qmldirtoqmlproject/test_module.qmlproject").toUtf8()) + .object()); + + fromIncompleteJsonObject = std::make_unique( + QJsonDocument::fromJson(jsonProject.toString().toUtf8()).object()); + + createFromEmpty = std::make_unique(QJsonObject{}); + } + + static void TearDownTestSuite() + { + existingQmlProject.reset(); + missingQmlProject->qmlProjectPath().removeFile(); + missingQmlProject.reset(); + invalidQmlProject.reset(); + fromJsonObject.reset(); + fromIncompleteJsonObject.reset(); + createFromEmpty->qmlProjectPath().removeFile(); + createFromEmpty.reset(); + } + +protected: + inline static std::unique_ptr existingQmlProject; + inline static std::unique_ptr missingQmlProject; + inline static std::unique_ptr invalidQmlProject; + inline static std::unique_ptr fromJsonObject; + inline static std::unique_ptr fromIncompleteJsonObject; + inline static std::unique_ptr createFromEmpty; +}; +} // namespace + +TEST_F(McuModuleProjectItem, is_valid_existing_qmlproject) +{ + auto isValid = existingQmlProject->isValid(); + + ASSERT_TRUE(isValid); +} + +TEST_F(McuModuleProjectItem, get_uri_existing_qmlproject) +{ + auto uri = existingQmlProject->uri(); + + ASSERT_THAT(uri, Eq("test.module")); +} + +TEST_F(McuModuleProjectItem, get_qml_files_existing_qmlproject) +{ + auto files = existingQmlProject->qmlFiles(); + + ASSERT_THAT(files, + UnorderedElementsAre("Internal/File3.qml", "File2.qml", "File1.qml", "TestSingleton.qml")); +} + +TEST_F(McuModuleProjectItem, get_qmlproject_path_existing_qmlproject) +{ + auto path = existingQmlProject->qmlProjectPath(); + auto expectedPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject"); + + ASSERT_THAT(path, Eq(expectedPath)); +} + +TEST_F(McuModuleProjectItem, get_project_existing_qmlproject) +{ + auto project = existingQmlProject->project(); + auto expectedJsonProject = QJsonDocument::fromJson( + jsonProject + .arg(localTestDataDir + "/qmldirtoqmlproject/existing_qmlproject/test_module.qmlproject") + .toUtf8()) + .object(); + + ASSERT_THAT(project, Eq(expectedJsonProject)); +} + +TEST_F(McuModuleProjectItem, save_qmlproject_file_existing_qmlproject) +{ + bool saved = existingQmlProject->saveQmlProjectFile(); + + ASSERT_FALSE(saved); +} + +TEST_F(McuModuleProjectItem, is_valid_missing_qmlproject) +{ + auto isValid = missingQmlProject->isValid(); + + ASSERT_TRUE(isValid); +} + +TEST_F(McuModuleProjectItem, get_uri_missing_qmlproject) +{ + auto uri = missingQmlProject->uri(); + + ASSERT_THAT(uri, Eq("test.module")); +} + +TEST_F(McuModuleProjectItem, get_qml_files_missing_qmlproject) +{ + auto files = missingQmlProject->qmlFiles(); + + ASSERT_THAT(files, + UnorderedElementsAre("Internal/File3.qml", "File2.qml", "File1.qml", "TestSingleton.qml")); +} + +TEST_F(McuModuleProjectItem, get_qmlproject_path_missing_qmlproject) +{ + auto path = missingQmlProject->qmlProjectPath(); + auto expectedPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/missing_qmlproject/test_module.qmlproject"); + + ASSERT_THAT(path, Eq(expectedPath)); +} + +TEST_F(McuModuleProjectItem, check_saved_qmlproject_file_missing_qmlproject) +{ + auto projectPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/missing_qmlproject/test_module.qmlproject"); + missingQmlProject->saveQmlProjectFile(); + + QmlProjectManager::McuModuleProjectItem savedQmlProject(projectPath); + + ASSERT_THAT(*missingQmlProject, Eq(savedQmlProject)); +} + +TEST_F(McuModuleProjectItem, is_valid_invalid_qmlproject) +{ + auto isValid = invalidQmlProject->isValid(); + + ASSERT_FALSE(isValid); +} + +TEST_F(McuModuleProjectItem, get_uri_invalid_qmlproject) +{ + auto uri = invalidQmlProject->uri(); + + ASSERT_THAT(uri, IsEmpty()); +} + +TEST_F(McuModuleProjectItem, get_qml_files_invalid_qmlproject) +{ + auto files = invalidQmlProject->qmlFiles(); + + ASSERT_THAT(files, IsEmpty()); +} + +TEST_F(McuModuleProjectItem, get_qmlproject_path_invalid_qmlproject) +{ + auto path = invalidQmlProject->qmlProjectPath(); + auto expectedPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/invalid_qmlproject/test_module.qmlproject"); + + ASSERT_THAT(path, Eq(expectedPath)); +} + +TEST_F(McuModuleProjectItem, save_qmlproject_file_invalid_qmlproject) +{ + bool saved = invalidQmlProject->saveQmlProjectFile(); + + ASSERT_FALSE(saved); +} + +TEST_F(McuModuleProjectItem, is_valid_from_json_object) +{ + auto isValid = fromJsonObject->isValid(); + + ASSERT_TRUE(isValid); +} + +TEST_F(McuModuleProjectItem, get_uri_from_json_object) +{ + auto uri = fromJsonObject->uri(); + + ASSERT_THAT(uri, Eq("test.module")); +} + +TEST_F(McuModuleProjectItem, get_qml_files_from_json_object) +{ + auto files = fromJsonObject->qmlFiles(); + + ASSERT_THAT(files, + UnorderedElementsAre("Internal/File3.qml", "File2.qml", "File1.qml", "TestSingleton.qml")); +} + +TEST_F(McuModuleProjectItem, get_qmlproject_path_from_json_object) +{ + auto path = fromJsonObject->qmlProjectPath(); + auto expectedPath = Utils::FilePath::fromString(localTestDataDir + + "/qmldirtoqmlproject/test_module.qmlproject"); + + ASSERT_THAT(path, Eq(expectedPath)); +} + +TEST_F(McuModuleProjectItem, get_project_from_json_object) +{ + auto project = fromJsonObject->project(); + auto expectedJsonProject = QJsonDocument::fromJson( + jsonProject + .arg(localTestDataDir + + "/qmldirtoqmlproject/test_module.qmlproject") + .toUtf8()) + .object(); + + ASSERT_THAT(project, Eq(expectedJsonProject)); +} + +TEST_F(McuModuleProjectItem, is_valid_from_incomplete_json_object) +{ + auto isValid = fromIncompleteJsonObject->isValid(); + + ASSERT_FALSE(isValid); +} + +TEST_F(McuModuleProjectItem, get_uri_from_incomplete_json_object) +{ + auto uri = fromIncompleteJsonObject->uri(); + + ASSERT_THAT(uri, Eq("test.module")); +} + +TEST_F(McuModuleProjectItem, get_qml_files_from_incomplete_json_object) +{ + auto files = fromIncompleteJsonObject->qmlFiles(); + + ASSERT_THAT(files, + UnorderedElementsAre("Internal/File3.qml", "File2.qml", "File1.qml", "TestSingleton.qml")); +} + +TEST_F(McuModuleProjectItem, get_qmlproject_path_from_incomplete_json_object) +{ + auto path = fromIncompleteJsonObject->qmlProjectPath(); + + ASSERT_FALSE(path.endsWith(".qmlproject")); +} + +TEST_F(McuModuleProjectItem, save_qmlproject_from_incomplete_json_object) +{ + bool saved = fromIncompleteJsonObject->saveQmlProjectFile(); + + ASSERT_FALSE(saved); +} + +TEST_F(McuModuleProjectItem, set_uri_create_from_empty) +{ + createFromEmpty->setUri("test.module"); + + ASSERT_THAT(createFromEmpty->uri(), Eq("test.module")); +} + +TEST_F(McuModuleProjectItem, set_qml_files_create_from_empty) +{ + createFromEmpty->setQmlFiles({"File1.qml", "File2.qml"}); + + ASSERT_THAT(createFromEmpty->qmlFiles(), UnorderedElementsAre("File1.qml", "File2.qml")); +} + +TEST_F(McuModuleProjectItem, set_qmlproject_path_create_from_empty) +{ + auto projectPath = Utils::FilePath::fromString(localTestDataDir + + "/qmldirtoqmlproject/test_module.qmlproject"); + + createFromEmpty->setQmlProjectPath(projectPath); + + ASSERT_THAT(createFromEmpty->qmlProjectPath(), Eq(projectPath)); +} + +TEST_F(McuModuleProjectItem, is_valid_create_from_empty) +{ + bool isValid = createFromEmpty->isValid(); + + ASSERT_TRUE(isValid); +} + +TEST_F(McuModuleProjectItem, check_saved_qmlproject_create_from_empty) +{ + auto projectPath = Utils::FilePath::fromString(localTestDataDir + + "/qmldirtoqmlproject/test_module.qmlproject"); + createFromEmpty->saveQmlProjectFile(); + + QmlProjectManager::McuModuleProjectItem savedQmlProject(projectPath); + + ASSERT_THAT(*createFromEmpty, Eq(savedQmlProject)); +} + +TEST_F(McuModuleProjectItem, create_from_nonexisting_file) +{ + auto projectPath = Utils::FilePath::fromString(localTestDataDir + + "/qmldirtoqmlproject/nonexisting"); + + auto projectItem = QmlProjectManager::McuModuleProjectItem::fromQmldirModule(projectPath); + + ASSERT_FALSE(projectItem); +} + +TEST_F(McuModuleProjectItem, create_from_missing_module_name_qmldir) +{ + auto projectPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/missing_module_name_qmldir/qmldir"); + + auto projectItem = QmlProjectManager::McuModuleProjectItem::fromQmldirModule(projectPath); + + ASSERT_FALSE(projectItem); +} + +TEST_F(McuModuleProjectItem, create_from_incorrect_module_name_qmldir) +{ + auto projectPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/incorrect_module_name_qmldir/qmldir"); + + auto projectItem = QmlProjectManager::McuModuleProjectItem::fromQmldirModule(projectPath); + + ASSERT_FALSE(projectItem); +} + +TEST_F(McuModuleProjectItem, create_from_missing_qml_files_qmldir) +{ + auto projectPath = Utils::FilePath::fromString( + localTestDataDir + "/qmldirtoqmlproject/missing_qml_files_qmldir/qmldir"); + + auto projectItem = QmlProjectManager::McuModuleProjectItem::fromQmldirModule(projectPath); + + ASSERT_FALSE(projectItem); +}