aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarco Bubke <marco.bubke@qt.io>2023-08-23 23:45:52 +0200
committerMarco Bubke <marco.bubke@qt.io>2023-10-02 20:22:10 +0000
commit1177bb4b1f3f913006d5ff0f2f05dfef2dc3a1de (patch)
tree64eea90c2a96037a9c13d3094d653397ebb1173f
parent3a0eff9e7959c53a0d068497ce645df387dab93b (diff)
QmlDesigner: Add PropertyComponentGenerator
Task-number: QDS-10578 Change-Id: I2c20f5aaa6ebfe7736012a02d4c4596589007866 Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io> Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
-rw-r--r--share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml25
-rw-r--r--src/libs/qmljs/qmljssimplereader.cpp5
-rw-r--r--src/libs/qmljs/qmljssimplereader.h3
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt1
-rw-r--r--src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp272
-rw-r--r--src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h53
-rw-r--r--src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h46
-rw-r--r--src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp177
-rw-r--r--src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h29
-rw-r--r--src/plugins/qmldesigner/designercore/include/model.h6
-rw-r--r--src/plugins/qmldesigner/designercore/include/module.h35
-rw-r--r--src/plugins/qmldesigner/designercore/model/model.cpp19
-rw-r--r--tests/unit/tests/matchers/CMakeLists.txt1
-rw-r--r--tests/unit/tests/matchers/strippedstring-matcher.h81
-rw-r--r--tests/unit/tests/mocks/CMakeLists.txt2
-rw-r--r--tests/unit/tests/mocks/projectstoragemock.cpp25
-rw-r--r--tests/unit/tests/mocks/projectstoragemock.h24
-rw-r--r--tests/unit/tests/mocks/propertycomponentgeneratormock.h22
-rw-r--r--tests/unit/tests/printers/gtest-qt-printing.cpp5
-rw-r--r--tests/unit/tests/printers/gtest-qt-printing.h2
-rw-r--r--tests/unit/tests/testdesignercore/CMakeLists.txt1
-rw-r--r--tests/unit/tests/unittests/CMakeLists.txt1
-rw-r--r--tests/unit/tests/unittests/componentcore/CMakeLists.txt15
-rw-r--r--tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp322
-rw-r--r--tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp487
-rw-r--r--tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp2
-rw-r--r--tests/unit/tests/unittests/model/model-test.cpp43
27 files changed, 1689 insertions, 15 deletions
diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml
index a6ee7eb476..03e42a974f 100644
--- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml
+++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/PropertyTemplates/TemplateTypes.qml
@@ -9,72 +9,93 @@ AutoTypes {
Type {
typeNames: ["int"]
+ module: "QML"
sourceFile: "IntEditorTemplate.template"
}
Type {
typeNames: ["real", "double", "qreal"]
+ module: "QML"
sourceFile: "RealEditorTemplate.template"
}
Type {
typeNames: ["string", "QString"]
+ module: "QML"
sourceFile: "StringEditorTemplate.template"
}
Type {
- typeNames: ["QUrl", "url"]
+ typeNames: ["url", "QUrl"]
+ module: "QML"
sourceFile: "UrlEditorTemplate.template"
}
Type {
typeNames: ["bool", "boolean"]
+ module: "QML"
sourceFile: "BooleanEditorTemplate.template"
}
Type {
typeNames: ["color", "QColor"]
+ module: "QtQuick"
sourceFile: "ColorEditorTemplate.template"
}
Type {
typeNames: ["Text"]
+ module: "QtQuick"
sourceFile: "TextEditorTemplate.template"
separateSection: true
}
Type {
typeNames: ["font", "QFont"]
+ module: "QtQuick"
sourceFile: "FontEditorTemplate.template"
separateSection: true
}
Type {
typeNames: ["Rectangle"]
+ module: "QtQuick"
sourceFile: "RectangleEditorTemplate.template"
separateSection: true
}
Type {
typeNames: ["Image"]
+ module: "QtQuick"
sourceFile: "ImageEditorTemplate.template"
separateSection: true
}
Type {
- typeNames: ["TextureInput", "Texture"]
+ typeNames: ["TextureInput"]
+ module: "QtQuick3D"
+ sourceFile: "3DItemFilterComboBoxEditorTemplate.template"
+ needsTypeArg: true
+ }
+
+ Type {
+ typeNames: ["Texture"]
+ module: "QtQuick3D"
sourceFile: "3DItemFilterComboBoxEditorTemplate.template"
needsTypeArg: true
}
Type {
typeNames: ["vector2d"]
+ module: "QtQuick3D"
sourceFile: "Vector2dEditorTemplate.template"
}
Type {
typeNames: ["vector3d"]
+ module: "QtQuick3D"
sourceFile: "Vector3dEditorTemplate.template"
}
Type {
typeNames: ["vector4d"]
+ module: "QtQuick3D"
sourceFile: "Vector4dEditorTemplate.template"
}
}
diff --git a/src/libs/qmljs/qmljssimplereader.cpp b/src/libs/qmljs/qmljssimplereader.cpp
index 761131b3a2..d4b8a21bf7 100644
--- a/src/libs/qmljs/qmljssimplereader.cpp
+++ b/src/libs/qmljs/qmljssimplereader.cpp
@@ -89,7 +89,10 @@ static Q_LOGGING_CATEGORY(simpleReaderLog, "qtc.qmljs.simpleReader", QtWarningMs
return newNode;
}
- const SimpleReaderNode::List SimpleReaderNode::children() const { return m_children; }
+ const SimpleReaderNode::List &SimpleReaderNode::children() const
+ {
+ return m_children;
+ }
void SimpleReaderNode::setProperty(const QString &name,
const SourceLocation &nameLocation,
diff --git a/src/libs/qmljs/qmljssimplereader.h b/src/libs/qmljs/qmljssimplereader.h
index de57bb62da..e14f49d65d 100644
--- a/src/libs/qmljs/qmljssimplereader.h
+++ b/src/libs/qmljs/qmljssimplereader.h
@@ -34,6 +34,7 @@ public:
SourceLocation valueLocation;
bool isValid() const { return !value.isNull() && value.isValid(); }
+ explicit operator bool() const { return isValid(); }
bool isDefaultValue() const
{
return !value.isNull() && !nameLocation.isValid() && !valueLocation.isValid();
@@ -53,7 +54,7 @@ public:
WeakPtr parent() const;
QString name() const;
SourceLocation nameLocation() const;
- const List children() const;
+ const List &children() const;
protected:
SimpleReaderNode();
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 8ea79bcdcf..6df5a63247 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -238,6 +238,7 @@ extend_qtc_library(QmlDesignerCore
modelmerger.h
modelnode.h
modelnodepositionstorage.h
+ module.h
nodeabstractproperty.h
nodeinstance.h
nodelistproperty.h
diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp
new file mode 100644
index 0000000000..7e5487909a
--- /dev/null
+++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.cpp
@@ -0,0 +1,272 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "propertycomponentgenerator.h"
+
+#include <utils/algorithm.h>
+#include <utils/environment.h>
+
+#include <model.h>
+
+#include <QFile>
+#include <QString>
+
+#include <memory>
+
+using namespace Qt::StringLiterals;
+
+namespace QmlDesigner {
+
+namespace {
+
+QmlJS::SimpleReaderNode::Ptr createTemplateConfiguration(const QString &propertyEditorResourcesPath)
+{
+ QmlJS::SimpleReader reader;
+ const QString fileName = propertyEditorResourcesPath + u"/PropertyTemplates/TemplateTypes.qml";
+ auto templateConfiguration = reader.readFile(fileName);
+
+ if (!templateConfiguration)
+ qWarning().nospace() << "template definitions:" << reader.errors();
+
+ return templateConfiguration;
+}
+
+template<typename Type>
+Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name)
+{
+ if (auto property = node->property(name)) {
+ const auto &value = property.value;
+ if (value.type() == QVariant::List) {
+ auto list = value.toList();
+ if (list.size())
+ return list.front().value<Type>();
+ } else {
+ return property.value.value<Type>();
+ }
+ }
+
+ return {};
+}
+
+QString getContent(const QString &path)
+{
+ QFile file{path};
+
+ if (file.open(QIODevice::ReadOnly))
+ return QString::fromUtf8(file.readAll());
+
+ return {};
+}
+
+QString generateComponentText(Utils::SmallStringView propertyName,
+ QStringView source,
+ Utils::SmallStringView typeName,
+ bool needsTypeArg)
+{
+ QString underscoreName{propertyName};
+ underscoreName.replace('.', '_');
+ if (needsTypeArg)
+ return source.arg(QString{propertyName}, underscoreName, QString{typeName});
+
+ return source.arg(QString{propertyName}, underscoreName);
+}
+
+PropertyComponentGenerator::Property generate(const PropertyMetaInfo &property,
+ const PropertyComponentGenerator::Entry &entry)
+{
+ auto propertyName = property.name();
+ auto component = generateComponentText(propertyName,
+ entry.propertyTemplate,
+ entry.typeName,
+ entry.needsTypeArg);
+
+ if (entry.separateSection)
+ return PropertyComponentGenerator::ComplexProperty{propertyName, component};
+
+ return PropertyComponentGenerator::BasicProperty{propertyName, component};
+}
+
+auto getRandomExportedName(const NodeMetaInfo &metaInfo)
+{
+ const auto &names = metaInfo.allExportedTypeNames();
+
+ using Result = decltype(names.front().name);
+
+ if (!names.empty())
+ return names.front().name;
+
+ return Result{};
+}
+
+} // namespace
+
+QString PropertyComponentGenerator::generateSubComponentText(Utils::SmallStringView propertyBaseName,
+ const PropertyMetaInfo &subProperty) const
+{
+ auto propertyType = subProperty.propertyType();
+ if (auto entry = findEntry(propertyType)) {
+ auto propertyName = Utils::SmallString::join({propertyBaseName, subProperty.name()});
+ return generateComponentText(propertyName,
+ entry->propertyTemplate,
+ entry->typeName,
+ entry->needsTypeArg);
+ }
+
+ return {};
+}
+
+QString PropertyComponentGenerator::generateComplexComponentText(Utils::SmallStringView propertyName,
+ const NodeMetaInfo &propertyType) const
+{
+ auto subProperties = propertyType.properties();
+
+ if (std::empty(subProperties))
+ return {};
+
+ auto propertyTypeName = getRandomExportedName(propertyType);
+ static QString templateText = QStringLiteral(
+ R"xy(
+ Section {
+ caption: %1 - %2
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 8
+ rightPadding: 0
+ expanded: false
+ level: 1
+ SectionLayout {
+ )xy");
+
+ auto component = templateText.arg(QString{propertyName}, QString{propertyTypeName});
+
+ auto propertyBaseName = Utils::SmallString::join({propertyName, "."});
+
+ bool subPropertiesHaveContent = false;
+ for (const auto &subProperty : propertyType.properties()) {
+ auto subPropertyContent = generateSubComponentText(propertyBaseName, subProperty);
+ subPropertiesHaveContent = subPropertiesHaveContent || subPropertyContent.size();
+ component += subPropertyContent;
+ }
+
+ component += "}\n}\n"_L1;
+
+ if (subPropertiesHaveContent)
+ return component;
+
+ return {};
+}
+
+PropertyComponentGenerator::Property PropertyComponentGenerator::generateComplexComponent(
+ const PropertyMetaInfo &property, const NodeMetaInfo &propertyType) const
+{
+ auto propertyName = property.name();
+ auto component = generateComplexComponentText(propertyName, propertyType);
+
+ if (component.isEmpty())
+ return {};
+
+ return ComplexProperty{propertyName, component};
+}
+
+namespace {
+
+std::optional<PropertyComponentGenerator::Entry> createEntry(QmlJS::SimpleReaderNode *node,
+ Model *model,
+ const QString &templatesPath)
+{
+ auto moduleName = getProperty<QByteArray>(node, "module");
+ if (moduleName.isEmpty())
+ return {};
+
+ auto module = model->module(moduleName);
+
+ auto typeName = getProperty<QByteArray>(node, "typeNames");
+
+ auto type = model->metaInfo(module, typeName);
+ if (!type)
+ return {};
+
+ auto path = getProperty<QString>(node, "sourceFile");
+ if (path.isEmpty())
+ return {};
+
+ auto content = getContent(templatesPath + path);
+ if (content.isEmpty())
+ return {};
+
+ bool needsTypeArg = getProperty<bool>(node, "needsTypeArg");
+
+ bool separateSection = getProperty<bool>(node, "separateSection");
+
+ return PropertyComponentGenerator::Entry{std::move(type),
+ std::move(typeName),
+ std::move(content),
+ separateSection,
+ needsTypeArg};
+}
+
+PropertyComponentGenerator::Entries createEntries(QmlJS::SimpleReaderNode::Ptr templateConfiguration,
+ Model *model,
+ const QString &templatesPath)
+{
+ PropertyComponentGenerator::Entries entries;
+ entries.reserve(32);
+
+ const auto &nodes = templateConfiguration->children();
+ for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) {
+ if (auto entry = createEntry(node.get(), model, templatesPath))
+ entries.push_back(*entry);
+ }
+
+ return entries;
+}
+
+QStringList createImports(QmlJS::SimpleReaderNode::Ptr templateConfiguration)
+{
+ auto property = templateConfiguration->property("imports");
+ return Utils::transform<QStringList>(property.value.toList(),
+ [](const auto &entry) { return entry.toString(); });
+}
+
+} // namespace
+
+PropertyComponentGenerator::PropertyComponentGenerator(const QString &propertyEditorResourcesPath,
+ Model *model)
+ : m_entries(createEntries(createTemplateConfiguration(propertyEditorResourcesPath),
+ model,
+ propertyEditorResourcesPath + "/PropertyTemplates/"))
+{
+ auto templateConfiguration = createTemplateConfiguration(propertyEditorResourcesPath);
+
+ m_entries = createEntries(templateConfiguration,
+ model,
+ propertyEditorResourcesPath + "/PropertyTemplates/");
+
+ m_imports = createImports(templateConfiguration);
+}
+
+PropertyComponentGenerator::Property PropertyComponentGenerator::create(const PropertyMetaInfo &property) const
+{
+ auto propertyType = property.propertyType();
+ if (auto entry = findEntry(propertyType))
+ return generate(property, *entry);
+
+ if (property.isWritable() && property.isPointer())
+ return {};
+
+ return generateComplexComponent(property, propertyType);
+}
+
+const PropertyComponentGenerator::Entry *PropertyComponentGenerator::findEntry(const NodeMetaInfo &type) const
+{
+ auto found = std::find_if(m_entries.begin(), m_entries.end(), [&](const auto &entry) {
+ return entry.type == type;
+ });
+
+ if (found != m_entries.end())
+ return std::addressof(*found);
+
+ return nullptr;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h
new file mode 100644
index 0000000000..67239c16a7
--- /dev/null
+++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgenerator.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "propertycomponentgeneratorinterface.h"
+
+#include <modelfwd.h>
+#include <nodemetainfo.h>
+
+#include <qmljs/qmljssimplereader.h>
+
+#include <optional>
+#include <variant>
+#include <vector>
+
+namespace QmlDesigner {
+
+class PropertyComponentGenerator final : public PropertyComponentGeneratorInterface
+{
+public:
+ PropertyComponentGenerator(const QString &propertyEditorResourcesPath, Model *model);
+
+ Property create(const PropertyMetaInfo &property) const override;
+
+ struct Entry
+ {
+ NodeMetaInfo type;
+ Utils::SmallString typeName;
+ QString propertyTemplate;
+ bool separateSection = false;
+ bool needsTypeArg = false;
+ };
+
+ using Entries = std::vector<Entry>;
+
+ QStringList imports() const override { return m_imports; }
+
+private:
+ const Entry *findEntry(const NodeMetaInfo &type) const;
+ QString generateSubComponentText(Utils::SmallStringView propertyBaseName,
+ const PropertyMetaInfo &subProperty) const;
+ QString generateComplexComponentText(Utils::SmallStringView propertyName,
+ const NodeMetaInfo &propertyType) const;
+ Property generateComplexComponent(const PropertyMetaInfo &property,
+ const NodeMetaInfo &propertyType) const;
+
+private:
+ Entries m_entries;
+ QStringList m_imports;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h b/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h
new file mode 100644
index 0000000000..ab4047c957
--- /dev/null
+++ b/src/plugins/qmldesigner/components/componentcore/propertycomponentgeneratorinterface.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <modelfwd.h>
+#include <nodemetainfo.h>
+
+#include <qmljs/qmljssimplereader.h>
+
+#include <optional>
+#include <variant>
+#include <vector>
+
+namespace QmlDesigner {
+
+class PropertyComponentGeneratorInterface
+{
+public:
+ PropertyComponentGeneratorInterface() = default;
+ PropertyComponentGeneratorInterface(const PropertyComponentGeneratorInterface &) = delete;
+ PropertyComponentGeneratorInterface &operator=(const PropertyComponentGeneratorInterface &) = delete;
+
+ struct BasicProperty
+ {
+ Utils::SmallString propertyName;
+ QString component;
+ };
+
+ struct ComplexProperty
+ {
+ Utils::SmallString propertyName;
+ QString component;
+ };
+
+ using Property = std::variant<std::monostate, BasicProperty, ComplexProperty>;
+
+ virtual Property create(const PropertyMetaInfo &property) const = 0;
+
+ virtual QStringList imports() const = 0;
+
+protected:
+ ~PropertyComponentGeneratorInterface() = default;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp
new file mode 100644
index 0000000000..272130b8f3
--- /dev/null
+++ b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.cpp
@@ -0,0 +1,177 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "propertyeditorcomponentgenerator.h"
+
+#include <utils/environment.h>
+#include <utils/span.h>
+
+#include <modelnode.h>
+
+namespace QmlDesigner {
+
+namespace {
+
+using GeneratorProperty = PropertyComponentGeneratorInterface::Property;
+using BasicProperty = PropertyComponentGeneratorInterface::BasicProperty;
+using ComplexProperty = PropertyComponentGeneratorInterface::ComplexProperty;
+using GeneratorProperties = std::vector<GeneratorProperty>;
+
+QString createImports(const QStringList &imports)
+{
+ QString importsContent;
+ importsContent.reserve(512);
+
+ for (const QString &import : imports) {
+ importsContent += import;
+ importsContent += '\n';
+ }
+
+ return importsContent;
+}
+
+QString componentButton(bool isComponent)
+{
+ if (isComponent)
+ return "ComponentButton {}";
+
+ return {};
+}
+
+void createBasicPropertySections(QString &components,
+ Utils::span<GeneratorProperty> generatorProperties)
+{
+ components += R"xy(
+ Column {
+ width: parent.width
+ leftPadding: 8
+ bottomPadding: 10
+ SectionLayout {)xy";
+
+ for (const auto &generatorProperty : generatorProperties)
+ components += std::get_if<BasicProperty>(&generatorProperty)->component;
+
+ components += R"xy(
+ }
+ })xy";
+}
+
+void createComplexPropertySections(QString &components,
+ Utils::span<GeneratorProperty> generatorProperties)
+{
+ for (const auto &generatorProperty : generatorProperties)
+ components += std::get_if<ComplexProperty>(&generatorProperty)->component;
+}
+
+Utils::SmallStringView propertyName(const GeneratorProperty &property)
+{
+ return std::visit(
+ [](const auto &p) -> Utils::SmallStringView {
+ if constexpr (!std::is_same_v<std::decay_t<decltype(p)>, std::monostate>)
+ return p.propertyName;
+ else
+ return {};
+ },
+ property);
+}
+
+PropertyMetaInfos getUnmangedProperties(const NodeMetaInfo &nodeInfo)
+{
+ PropertyMetaInfos properties;
+ properties.reserve(128);
+
+ auto prototypes = nodeInfo.selfAndPrototypes();
+
+ for (const auto &prototype : prototypes) {
+ if (prototype.propertyEditorPathId())
+ break;
+
+ auto localProperties = prototype.localProperties();
+
+ properties.insert(properties.end(), localProperties.begin(), localProperties.end());
+ }
+
+ return properties;
+}
+
+GeneratorProperties createSortedGeneratorProperties(
+ const PropertyMetaInfos &properties, const PropertyComponentGeneratorType &propertyGenerator)
+{
+ GeneratorProperties generatorProperties;
+ generatorProperties.reserve(properties.size());
+
+ for (const auto &property : properties) {
+ auto generatedProperty = propertyGenerator.create(property);
+ if (!std::holds_alternative<std::monostate>(generatedProperty))
+ generatorProperties.push_back(std::move(generatedProperty));
+ }
+ std::sort(generatorProperties.begin(),
+ generatorProperties.end(),
+ [](const auto &first, const auto &second) {
+ // this is sensitive to the order of the variant types but the test should catch any error
+ return std::forward_as_tuple(first.index(), propertyName(first))
+ < std::forward_as_tuple(second.index(), propertyName(second));
+ });
+
+ return generatorProperties;
+}
+
+QString createPropertySections(const PropertyComponentGeneratorType &propertyGenerator,
+ const NodeMetaInfo &nodeInfo)
+{
+ QString propertyComponents;
+ propertyComponents.reserve(100000);
+
+ auto generatorProperties = createSortedGeneratorProperties(getUnmangedProperties(nodeInfo),
+ propertyGenerator);
+
+ const auto begin = generatorProperties.begin();
+ const auto end = generatorProperties.end();
+
+ auto point = std::partition_point(begin, end, [](const auto &p) {
+ return !std::holds_alternative<ComplexProperty>(p);
+ });
+
+ if (begin != point)
+ createBasicPropertySections(propertyComponents, Utils::span<GeneratorProperty>{begin, point});
+
+ if (point != end)
+ createComplexPropertySections(propertyComponents, Utils::span<GeneratorProperty>{point, end});
+
+ return propertyComponents;
+}
+
+} // namespace
+
+PropertyEditorTemplateGenerator::PropertyEditorTemplateGenerator(
+ const PropertyComponentGeneratorType &propertyGenerator)
+ : m_propertyGenerator{propertyGenerator}
+{}
+
+QString PropertyEditorTemplateGenerator::create(const NodeMetaInfo &nodeInfo, bool isComponent)
+{
+ return QString{R"xy(
+ %1
+ Column {
+ width: parent.width
+ %2
+ Section {
+ caption: "%3"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ %4
+ }
+ }
+ })xy"}
+ .arg(createImports(m_propertyGenerator.imports()),
+ componentButton(isComponent),
+ QObject::tr("Exposed Custom Properties"),
+ createPropertySections(m_propertyGenerator, nodeInfo));
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h
new file mode 100644
index 0000000000..37dd824d43
--- /dev/null
+++ b/src/plugins/qmldesigner/components/componentcore/propertyeditorcomponentgenerator.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "propertycomponentgenerator.h"
+
+#include <nodemetainfo.h>
+
+namespace QmlDesigner {
+
+#ifdef UNIT_TESTS
+using PropertyComponentGeneratorType = PropertyComponentGeneratorInterface;
+#else
+using PropertyComponentGeneratorType = PropertyComponentGenerator;
+#endif
+class PropertyEditorTemplateGenerator
+{
+
+public:
+ PropertyEditorTemplateGenerator(const PropertyComponentGeneratorType &propertyGenerator);
+
+ QString create(const NodeMetaInfo &nodeInfo, bool isComponent);
+
+private:
+ const PropertyComponentGeneratorType &m_propertyGenerator;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h
index c404525a9c..ac8182c076 100644
--- a/src/plugins/qmldesigner/designercore/include/model.h
+++ b/src/plugins/qmldesigner/designercore/include/model.h
@@ -7,7 +7,9 @@
#include <documentmessage.h>
#include <model/modelresourcemanagementinterface.h>
+#include <module.h>
#include <projectstorage/projectstoragefwd.h>
+#include <projectstorage/projectstorageinfotypes.h>
#include <projectstorageids.h>
#include <QMimeData>
@@ -125,7 +127,11 @@ public:
const MetaInfo metaInfo() const;
MetaInfo metaInfo();
+ Module module(Utils::SmallStringView moduleName);
NodeMetaInfo metaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
+ NodeMetaInfo metaInfo(Module module,
+ Utils::SmallStringView typeName,
+ Storage::Version version = Storage::Version{}) const;
bool hasNodeMetaInfo(const TypeName &typeName, int majorVersion = -1, int minorVersion = -1) const;
void setMetaInfo(const MetaInfo &metaInfo);
diff --git a/src/plugins/qmldesigner/designercore/include/module.h b/src/plugins/qmldesigner/designercore/include/module.h
new file mode 100644
index 0000000000..5078f3ff8f
--- /dev/null
+++ b/src/plugins/qmldesigner/designercore/include/module.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <modelfwd.h>
+#include <projectstorageids.h>
+
+namespace QmlDesigner {
+
+class Module
+{
+public:
+ Module() = default;
+
+ Module(ModuleId moduleId, NotNullPointer<const ProjectStorageType> projectStorage)
+ : m_projectStorage{projectStorage}
+ , m_id{moduleId}
+ {}
+
+ ModuleId id() const { return m_id; }
+
+ bool isValid() const { return bool(m_id); }
+
+ explicit operator bool() const { return isValid(); }
+
+ const ProjectStorageType &projectStorage() const { return *m_projectStorage; }
+
+ friend bool operator==(Module first, Module second) { return first.m_id == second.m_id; }
+
+private:
+ NotNullPointer<const ProjectStorageType> m_projectStorage = {};
+ ModuleId m_id;
+};
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp
index 74afaaad86..c0393d524c 100644
--- a/src/plugins/qmldesigner/designercore/model/model.cpp
+++ b/src/plugins/qmldesigner/designercore/model/model.cpp
@@ -2436,6 +2436,16 @@ NodeMetaInfo Model::metaInfo(const TypeName &typeName, int majorVersion, int min
}
}
+NodeMetaInfo Model::metaInfo(Module module, Utils::SmallStringView typeName, Storage::Version version) const
+{
+ if constexpr (useProjectStorage()) {
+ return NodeMetaInfo(d->projectStorage->typeId(module.id(), typeName, version),
+ d->projectStorage);
+ } else {
+ return {};
+ }
+}
+
/*!
\brief Returns list of QML types available within the model.
*/
@@ -2444,6 +2454,15 @@ MetaInfo Model::metaInfo()
return d->metaInfo();
}
+Module Model::module(Utils::SmallStringView moduleName)
+{
+ if constexpr (useProjectStorage()) {
+ return Module(d->projectStorage->moduleId(moduleName), d->projectStorage);
+ }
+
+ return {};
+}
+
/*! \name View related functions
*/
//\{
diff --git a/tests/unit/tests/matchers/CMakeLists.txt b/tests/unit/tests/matchers/CMakeLists.txt
index f050cb83c8..32a7dad99c 100644
--- a/tests/unit/tests/matchers/CMakeLists.txt
+++ b/tests/unit/tests/matchers/CMakeLists.txt
@@ -7,6 +7,7 @@ add_qtc_library(TestMatchers OBJECT
SOURCES
info_exportedtypenames-matcher.h
import-matcher.h
+ strippedstring-matcher.h
unittest-matchers.h
version-matcher.h
qvariant-matcher.h
diff --git a/tests/unit/tests/matchers/strippedstring-matcher.h b/tests/unit/tests/matchers/strippedstring-matcher.h
new file mode 100644
index 0000000000..3c491cba8f
--- /dev/null
+++ b/tests/unit/tests/matchers/strippedstring-matcher.h
@@ -0,0 +1,81 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "version-matcher.h"
+
+#include <projectstorage/projectstoragetypes.h>
+
+#include <QRegularExpression>
+
+#include <algorithm>
+
+namespace Internal {
+class StippedStringEqMatcher
+{
+public:
+ explicit StippedStringEqMatcher(QString content)
+ : m_content(strip(std::move(content)))
+ {}
+
+ bool MatchAndExplain(const QString &s, testing::MatchResultListener *listener) const
+ {
+ auto strippedContent = strip(s);
+ bool isEqual = m_content == strippedContent;
+
+ if (!isEqual) {
+ auto [found1, found2] = std::mismatch(strippedContent.begin(),
+ strippedContent.end(),
+ m_content.begin(),
+ m_content.end());
+ if (found1 != strippedContent.end() && found2 != m_content.end()) {
+ *listener << "\ncurrent value mismatch start: \n"
+ << QStringView{found1, strippedContent.end()};
+ *listener << "\nexpected value mismatch start: \n"
+ << QStringView{found2, m_content.end()};
+ }
+ }
+
+ return isEqual;
+ }
+
+ static QString strip(QString s)
+ {
+ s.replace('\n', ' ');
+
+ auto newEnd = std::unique(s.begin(), s.end(), [](QChar first, QChar second) {
+ return first.isSpace() && second.isSpace();
+ });
+
+ s.erase(newEnd, s.end());
+
+ static QRegularExpression regex1{R"xx((\W)\s)xx"};
+ s.replace(regex1, R"xx(\1)xx");
+
+ static QRegularExpression regex2{R"xx(\s(\W))xx"};
+ s.replace(regex2, R"xx(\1)xx");
+
+ return s.trimmed();
+ }
+
+ void DescribeTo(::std::ostream *os) const
+ {
+ *os << "has stripped content " << testing::PrintToString(m_content);
+ }
+
+ void DescribeNegationTo(::std::ostream *os) const
+ {
+ *os << "doesn't have stripped content " << testing::PrintToString(m_content);
+ }
+
+private:
+ const QString m_content;
+};
+
+} // namespace Internal
+
+inline auto StrippedStringEq(const QStringView &content)
+{
+ return ::testing::PolymorphicMatcher(Internal::StippedStringEqMatcher(content.toString()));
+}
diff --git a/tests/unit/tests/mocks/CMakeLists.txt b/tests/unit/tests/mocks/CMakeLists.txt
index 5323f09e40..bd34e15c68 100644
--- a/tests/unit/tests/mocks/CMakeLists.txt
+++ b/tests/unit/tests/mocks/CMakeLists.txt
@@ -2,6 +2,7 @@ add_qtc_library(TestMocks OBJECT
EXCLUDE_FROM_INSTALL
PROPERTIES SKIP_AUTOGEN ON
PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR}
+ INCLUDES ../../../../src/plugins/qmldesigner/components/componentcore
DEPENDS
Qt::Core Qt::Widgets Googletest Sqlite TestDesignerCore
SOURCES
@@ -20,6 +21,7 @@ add_qtc_library(TestMocks OBJECT
mocktimer.h
mocktimestampprovider.h
modelresourcemanagementmock.h
+ propertycomponentgeneratormock.h
projectstoragemock.cpp
projectstoragemock.h
projectstoragepathwatchermock.h
diff --git a/tests/unit/tests/mocks/projectstoragemock.cpp b/tests/unit/tests/mocks/projectstoragemock.cpp
index 105b8c7346..b2445cd82a 100644
--- a/tests/unit/tests/mocks/projectstoragemock.cpp
+++ b/tests/unit/tests/mocks/projectstoragemock.cpp
@@ -112,6 +112,16 @@ QmlDesigner::ImportId ProjectStorageMock::createImportId(QmlDesigner::ModuleId m
return importId;
}
+void ProjectStorageMock::addExportedTypeName(QmlDesigner::TypeId typeId,
+ QmlDesigner::ModuleId moduleId,
+ Utils::SmallStringView typeName)
+{
+ ON_CALL(*this, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId));
+ ON_CALL(*this, fetchTypeIdByModuleIdAndExportedName(Eq(moduleId), Eq(typeName)))
+ .WillByDefault(Return(typeId));
+ exportedTypeName[typeId].emplace_back(moduleId, typeName);
+}
+
PropertyDeclarationId ProjectStorageMock::createProperty(TypeId typeId,
Utils::SmallString name,
PropertyDeclarationTraits traits,
@@ -159,6 +169,12 @@ void ProjectStorageMock::createFunction(QmlDesigner::TypeId typeId, Utils::Small
ON_CALL(*this, functionDeclarationNames(Eq(typeId))).WillByDefault(Return(functionNames));
}
+void ProjectStorageMock::setPropertyEditorPathId(QmlDesigner::TypeId typeId,
+ QmlDesigner::SourceId sourceId)
+{
+ ON_CALL(*this, propertyEditorPathId(Eq(typeId))).WillByDefault(Return(sourceId));
+}
+
namespace {
void addBaseProperties(TypeId typeId, TypeIds baseTypeIds, ProjectStorageMock &storage)
{
@@ -192,6 +208,10 @@ TypeId ProjectStorageMock::createType(ModuleId moduleId,
ON_CALL(*this, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId));
ON_CALL(*this, fetchTypeIdByModuleIdAndExportedName(Eq(moduleId), Eq(typeName)))
.WillByDefault(Return(typeId));
+ addExportedTypeName(typeId, moduleId, typeName);
+ ON_CALL(*this, exportedTypeNames(Eq(typeId))).WillByDefault([&](TypeId id) {
+ return exportedTypeName[id];
+ });
PropertyDeclarationId defaultPropertyDeclarationId;
if (defaultPropertyName.size()) {
if (!defaultPropertyTypeId) {
@@ -280,6 +300,7 @@ void ProjectStorageMock::setupQtQuick()
auto intId = createValue(qmlModuleId, "int");
createValue(qmlNativeModuleId, "uint");
auto doubleId = createValue(qmlModuleId, "double");
+ addExportedTypeName(doubleId, qmlModuleId, "real");
createValue(qmlNativeModuleId, "float");
createValue(qmlModuleId, "date");
auto stringId = createValue(qmlModuleId, "string");
@@ -307,7 +328,7 @@ void ProjectStorageMock::setupQtQuick()
createProperty(fontValueTypeId, "overline", boolId);
createProperty(fontValueTypeId, "pointSize", doubleId);
createProperty(fontValueTypeId, "pixelSize", intId);
- createValue(qtQuickModuleId, "font", {fontValueTypeId});
+ auto fontId = createValue(qtQuickModuleId, "font", {fontValueTypeId});
auto itemId = createObject(qtQuickModuleId,
"Item",
@@ -315,6 +336,8 @@ void ProjectStorageMock::setupQtQuick()
PropertyDeclarationTraits::IsList,
qtObjectId,
{qtObjectId});
+ createProperty(itemId, "x", doubleId);
+ createProperty(itemId, "font", fontId);
auto inputDeviceId = createObject(qtQuickModuleId, "InputDevice", {qtObjectId});
createProperty(inputDeviceId, "seatName", stringId);
diff --git a/tests/unit/tests/mocks/projectstoragemock.h b/tests/unit/tests/mocks/projectstoragemock.h
index 22e316ede3..5c189af519 100644
--- a/tests/unit/tests/mocks/projectstoragemock.h
+++ b/tests/unit/tests/mocks/projectstoragemock.h
@@ -9,6 +9,7 @@
#include <projectstorage/commontypecache.h>
#include <projectstorage/filestatus.h>
+#include <projectstorage/projectstorageinfotypes.h>
#include <projectstorage/projectstorageinterface.h>
#include <projectstorage/sourcepathcache.h>
@@ -40,15 +41,18 @@ public:
QmlDesigner::SourceId sourceId,
QmlDesigner::Storage::Version version = QmlDesigner::Storage::Version{});
- QmlDesigner::TypeId createType(
- QmlDesigner::ModuleId moduleId,
- Utils::SmallStringView typeName,
- Utils::SmallStringView defaultPropertyName,
- QmlDesigner::Storage::PropertyDeclarationTraits defaultPropertyTraits,
- QmlDesigner::TypeId defaultPropertyTypeId,
- QmlDesigner::Storage::TypeTraits typeTraits,
- QmlDesigner::TypeIds baseTypeIds = {},
- QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{});
+ void addExportedTypeName(QmlDesigner::TypeId typeId,
+ QmlDesigner::ModuleId moduleId,
+ Utils::SmallStringView typeName);
+
+ QmlDesigner::TypeId createType(QmlDesigner::ModuleId moduleId,
+ Utils::SmallStringView typeName,
+ Utils::SmallStringView defaultPropertyName,
+ QmlDesigner::Storage::PropertyDeclarationTraits defaultPropertyTraits,
+ QmlDesigner::TypeId defaultPropertyTypeId,
+ QmlDesigner::Storage::TypeTraits typeTraits,
+ QmlDesigner::TypeIds baseTypeIds = {},
+ QmlDesigner::SourceId sourceId = QmlDesigner::SourceId{});
QmlDesigner::TypeId createType(QmlDesigner::ModuleId moduleId,
Utils::SmallStringView typeName,
@@ -85,6 +89,7 @@ public:
void createSignal(QmlDesigner::TypeId typeId, Utils::SmallString name);
void createFunction(QmlDesigner::TypeId typeId, Utils::SmallString name);
+ void setPropertyEditorPathId(QmlDesigner::TypeId typeId, QmlDesigner::SourceId sourceId);
MOCK_METHOD(void,
synchronize,
@@ -280,6 +285,7 @@ public:
(const, override));
QmlDesigner::Storage::Info::CommonTypeCache<QmlDesigner::ProjectStorageInterface> typeCache{*this};
+ std::map<QmlDesigner::TypeId, QmlDesigner::Storage::Info::ExportedTypeNames> exportedTypeName;
};
class ProjectStorageMockWithQtQtuick : public ProjectStorageMock
diff --git a/tests/unit/tests/mocks/propertycomponentgeneratormock.h b/tests/unit/tests/mocks/propertycomponentgeneratormock.h
new file mode 100644
index 0000000000..043b64b464
--- /dev/null
+++ b/tests/unit/tests/mocks/propertycomponentgeneratormock.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "../utils/googletest.h"
+
+#include <propertycomponentgeneratorinterface.h>
+
+class PropertyComponentGeneratorMock : public QmlDesigner::PropertyComponentGeneratorInterface
+{
+ using Property = QmlDesigner::PropertyComponentGeneratorInterface::Property;
+
+public:
+ virtual ~PropertyComponentGeneratorMock() = default;
+ MOCK_METHOD(Property,
+ create,
+ (const QmlDesigner::PropertyMetaInfo &),
+
+ (const, override));
+ MOCK_METHOD(QStringList, imports, (), (const, override));
+};
diff --git a/tests/unit/tests/printers/gtest-qt-printing.cpp b/tests/unit/tests/printers/gtest-qt-printing.cpp
index 532aef525f..f432d87398 100644
--- a/tests/unit/tests/printers/gtest-qt-printing.cpp
+++ b/tests/unit/tests/printers/gtest-qt-printing.cpp
@@ -33,6 +33,11 @@ std::ostream &operator<<(std::ostream &out, const QString &text)
return out << text.toUtf8();
}
+std::ostream &operator<<(std::ostream &out, QStringView text)
+{
+ return out << text.toString();
+}
+
std::ostream &operator<<(std::ostream &out, const QVariant &variant)
{
QString output;
diff --git a/tests/unit/tests/printers/gtest-qt-printing.h b/tests/unit/tests/printers/gtest-qt-printing.h
index c84697c17b..ceec63817c 100644
--- a/tests/unit/tests/printers/gtest-qt-printing.h
+++ b/tests/unit/tests/printers/gtest-qt-printing.h
@@ -11,12 +11,14 @@ QT_BEGIN_NAMESPACE
class QVariant;
class QString;
+class QStringView;
class QTextCharFormat;
class QImage;
class QIcon;
std::ostream &operator<<(std::ostream &out, const QVariant &QVariant);
std::ostream &operator<<(std::ostream &out, const QString &text);
+std::ostream &operator<<(std::ostream &out, QStringView text);
std::ostream &operator<<(std::ostream &out, const QByteArray &byteArray);
std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format);
std::ostream &operator<<(std::ostream &out, const QImage &image);
diff --git a/tests/unit/tests/testdesignercore/CMakeLists.txt b/tests/unit/tests/testdesignercore/CMakeLists.txt
index 9a10b6d744..0847caa323 100644
--- a/tests/unit/tests/testdesignercore/CMakeLists.txt
+++ b/tests/unit/tests/testdesignercore/CMakeLists.txt
@@ -65,6 +65,7 @@ add_qtc_library(TestDesignerCore OBJECT
include/metainfo.h
include/metainforeader.h
include/modelnode.h
+ include/module.h
include/nodeabstractproperty.h
include/nodelistproperty.h
include/nodemetainfo.h
diff --git a/tests/unit/tests/unittests/CMakeLists.txt b/tests/unit/tests/unittests/CMakeLists.txt
index 5de9ef141c..7088bedcc0 100644
--- a/tests/unit/tests/unittests/CMakeLists.txt
+++ b/tests/unit/tests/unittests/CMakeLists.txt
@@ -38,6 +38,7 @@ function(unittest_copy_data_folder)
)
endfunction(unittest_copy_data_folder)
+add_subdirectory(componentcore)
add_subdirectory(listmodeleditor)
add_subdirectory(imagecache)
add_subdirectory(metainfo)
diff --git a/tests/unit/tests/unittests/componentcore/CMakeLists.txt b/tests/unit/tests/unittests/componentcore/CMakeLists.txt
new file mode 100644
index 0000000000..913fe21bea
--- /dev/null
+++ b/tests/unit/tests/unittests/componentcore/CMakeLists.txt
@@ -0,0 +1,15 @@
+extend_qtc_test(unittest
+ SOURCES
+ propertycomponentgenerator-test.cpp
+ propertyeditorcomponentgenerator-test.cpp
+)
+
+extend_qtc_test(unittest
+ SOURCES_PREFIX ../../../../../src/plugins/qmldesigner/components/componentcore
+ SOURCES
+ propertyeditorcomponentgenerator.cpp propertyeditorcomponentgenerator.h
+ propertycomponentgenerator.cpp propertycomponentgenerator.h
+ propertycomponentgeneratorinterface.h
+)
+
+
diff --git a/tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp b/tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp
new file mode 100644
index 0000000000..353efb91f6
--- /dev/null
+++ b/tests/unit/tests/unittests/componentcore/propertycomponentgenerator-test.cpp
@@ -0,0 +1,322 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <googletest.h>
+#include <mocks/abstractviewmock.h>
+#include <mocks/modelresourcemanagementmock.h>
+#include <mocks/projectstoragemock.h>
+#include <mocks/sourcepathcachemock.h>
+#include <strippedstring-matcher.h>
+
+#include <propertycomponentgenerator.h>
+
+using namespace Qt::StringLiterals;
+
+namespace QmlDesigner {
+
+std::ostream &operator<<(std::ostream &out, const PropertyComponentGenerator::BasicProperty &property)
+{
+ return out << "BasicProperty(" << property.propertyName << ", " << property.component << ")";
+}
+
+std::ostream &operator<<(std::ostream &out, const PropertyComponentGenerator::ComplexProperty &property)
+{
+ return out << "ComplexPropertiesFromTemplates(" << property.propertyName << ", "
+ << property.component << ")";
+}
+
+} // namespace QmlDesigner
+
+namespace {
+
+using BasicProperty = QmlDesigner::PropertyComponentGenerator::BasicProperty;
+using ComplexProperty = QmlDesigner::PropertyComponentGenerator::ComplexProperty;
+
+template<typename Matcher>
+auto IsBasicProperty(const Matcher &matcher)
+{
+ return VariantWith<BasicProperty>(Field(&BasicProperty::component, matcher));
+}
+
+template<typename Matcher>
+auto IsComplexProperty(const Matcher &matcher)
+{
+ return VariantWith<ComplexProperty>(Field(&ComplexProperty::component, matcher));
+}
+
+constexpr Utils::SmallStringView sourcesPath = UNITTEST_DIR
+ "/../../../../share/qtcreator/qmldesigner/propertyEditorQmlSources";
+
+class PropertyComponentGenerator : public ::testing::Test
+{
+protected:
+ static void SetUpTestSuite()
+ {
+ simpleReaderNode = createTemplateConfiguration(QString{sourcesPath});
+ }
+
+ static void TearDownTestSuite() { simpleReaderNode.reset(); }
+
+ static QmlJS::SimpleReaderNode::Ptr createTemplateConfiguration(const QString &propertyEditorResourcesPath)
+ {
+ QmlJS::SimpleReader reader;
+ const QString fileName = propertyEditorResourcesPath + u"/PropertyTemplates/TemplateTypes.qml";
+ auto templateConfiguration = reader.readFile(fileName);
+
+ if (!templateConfiguration)
+ qWarning().nospace() << "template definitions:" << reader.errors();
+
+ return templateConfiguration;
+ }
+
+ template<typename Type>
+ static Type getProperty(const QmlJS::SimpleReaderNode *node, const QString &name)
+ {
+ if (auto property = node->property(name)) {
+ const auto &value = property.value;
+ if (value.type() == QVariant::List) {
+ auto list = value.toList();
+ if (list.size())
+ return list.front().value<Type>();
+ } else {
+ return property.value.value<Type>();
+ }
+ }
+
+ return {};
+ }
+
+ static QString getSource(const QString &name)
+ {
+ const auto &nodes = simpleReaderNode->children();
+ for (const QmlJS::SimpleReaderNode::Ptr &node : nodes) {
+ if (auto typeName = getProperty<QString>(node.get(), "typeNames"); typeName != name)
+ continue;
+
+ auto sourcePath = getProperty<QString>(node.get(), "sourceFile");
+
+ return getContent(QString{sourcesPath} + "/PropertyTemplates/" + sourcePath);
+ }
+
+ return {};
+ }
+
+ static QString getContent(const QString &path)
+ {
+ QFile file{path};
+
+ if (file.open(QIODevice::ReadOnly))
+ return QString::fromUtf8(file.readAll());
+
+ return {};
+ }
+
+ static QString getExpectedContent(const QString &typeName,
+ const QString &propertyName,
+ const QString &binding)
+ {
+ auto source = getSource(typeName);
+
+ return source.arg(propertyName, binding);
+ }
+
+ QmlDesigner::NodeMetaInfo createTypeWithProperties(Utils::SmallStringView name,
+ QmlDesigner::TypeId propertyTypeId)
+ {
+ auto typeId = projectStorageMock.createValue(qmlModuleId, name);
+ projectStorageMock.createProperty(typeId, "sub1", propertyTypeId);
+ projectStorageMock.createProperty(typeId, "sub2", propertyTypeId);
+
+ return {typeId, &projectStorageMock};
+ }
+
+ QmlDesigner::NodeMetaInfo createType(Utils::SmallStringView name)
+ {
+ auto typeId = projectStorageMock.createValue(qmlModuleId, name);
+
+ return {typeId, &projectStorageMock};
+ }
+
+ QmlDesigner::PropertyMetaInfo createProperty(QmlDesigner::TypeId typeId,
+ Utils::SmallString name,
+ QmlDesigner::Storage::PropertyDeclarationTraits traits,
+ QmlDesigner::TypeId propertyTypeId)
+ {
+ auto propertyId = projectStorageMock.createProperty(typeId, name, traits, propertyTypeId);
+
+ return {propertyId, &projectStorageMock};
+ }
+
+ QString toString(const QmlDesigner::PropertyComponentGenerator::Property &propertyComponent,
+ const QString &baseName,
+ const QString &propertyName)
+ {
+ auto text = std::visit(
+ [](const auto &p) -> QString {
+ if constexpr (!std::is_same_v<std::decay_t<decltype(p)>, std::monostate>)
+ return p.component;
+ else
+ return {};
+ },
+ propertyComponent);
+
+ text.replace(propertyName, baseName + "."_L1 + propertyName);
+ text.replace("backendValues."_L1 + baseName + "."_L1 + propertyName,
+ "backendValues."_L1 + baseName + "_"_L1 + propertyName);
+
+ return text;
+ }
+
+protected:
+ inline static QSharedPointer<const QmlJS::SimpleReaderNode> simpleReaderNode;
+ NiceMock<AbstractViewMock> viewMock;
+ NiceMock<SourcePathCacheMockWithPaths> pathCacheMock{"/path/foo.qml"};
+ NiceMock<ProjectStorageMockWithQtQtuick> projectStorageMock{pathCacheMock.sourceId};
+ NiceMock<ModelResourceManagementMock> resourceManagementMock;
+ QmlDesigner::Model model{{projectStorageMock, pathCacheMock},
+ "Item",
+ -1,
+ -1,
+ nullptr,
+ std::make_unique<ModelResourceManagementMockWrapper>(
+ resourceManagementMock)};
+ QmlDesigner::PropertyComponentGenerator generator{QString{sourcesPath}, &model};
+ QmlDesigner::NodeMetaInfo itemMetaInfo = model.qtQuickItemMetaInfo();
+ QmlDesigner::ModuleId qmlModuleId = projectStorageMock.createModule("QML");
+};
+
+TEST_F(PropertyComponentGenerator,
+ basic_property_is_returned_for_property_with_a_template_with_no_separate_section)
+{
+ QString expected = getExpectedContent("real", "x", "x");
+ auto xProperty = itemMetaInfo.property("x");
+
+ auto propertyComponent = generator.create(xProperty);
+
+ ASSERT_THAT(propertyComponent, IsBasicProperty(StrippedStringEq(expected)));
+}
+
+TEST_F(PropertyComponentGenerator,
+ complex_property_is_returned_for_property_with_a_template_with_a_separate_section)
+{
+ QString expected = getExpectedContent("font", "font", "font");
+ auto fontProperty = itemMetaInfo.property("font");
+
+ auto propertyComponent = generator.create(fontProperty);
+
+ ASSERT_THAT(propertyComponent, IsComplexProperty(StrippedStringEq(expected)));
+}
+
+TEST_F(PropertyComponentGenerator,
+ complex_property_is_returned_for_property_without_a_template_and_subproperties)
+{
+ auto stringId = projectStorageMock.createValue(qmlModuleId, "string");
+ auto fooNodeInfo = createTypeWithProperties("Foo", stringId);
+ auto sub1Text = toString(generator.create(fooNodeInfo.property("sub1")), "foo", "sub1");
+ auto sub2Text = toString(generator.create(fooNodeInfo.property("sub2")), "foo", "sub2");
+ auto fooProperty = createProperty(itemMetaInfo.id(), "foo", {}, fooNodeInfo.id());
+ QString expectedText = QStringLiteral(
+ R"xy(
+ Section {
+ caption: foo - Foo
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 8
+ rightPadding: 0
+ expanded: false
+ level: 1
+ SectionLayout {
+ )xy");
+ expectedText += sub1Text;
+ expectedText += sub2Text;
+ expectedText += "}}";
+
+ auto propertyComponent = generator.create(fooProperty);
+
+ ASSERT_THAT(propertyComponent, IsComplexProperty(StrippedStringEq(expectedText)));
+}
+
+TEST_F(PropertyComponentGenerator,
+ pointer_readonly_complex_property_is_returned_for_property_without_a_template_and_subproperties)
+{
+ auto stringId = projectStorageMock.createValue(qmlModuleId, "string");
+ auto fooNodeInfo = createTypeWithProperties("Foo", stringId);
+ auto sub1Text = toString(generator.create(fooNodeInfo.property("sub1")), "foo", "sub1");
+ auto sub2Text = toString(generator.create(fooNodeInfo.property("sub2")), "foo", "sub2");
+ auto fooProperty = createProperty(itemMetaInfo.id(),
+ "foo",
+ QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer
+ | QmlDesigner::Storage::PropertyDeclarationTraits::IsReadOnly,
+ fooNodeInfo.id());
+ QString expectedText = QStringLiteral(
+ R"xy(
+ Section {
+ caption: foo - Foo
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 8
+ rightPadding: 0
+ expanded: false
+ level: 1
+ SectionLayout {
+ )xy");
+ expectedText += sub1Text;
+ expectedText += sub2Text;
+ expectedText += "}}";
+
+ auto propertyComponent = generator.create(fooProperty);
+
+ ASSERT_THAT(propertyComponent, IsComplexProperty(StrippedStringEq(expectedText)));
+}
+
+TEST_F(PropertyComponentGenerator, basic_property_without_template_is_returning_monostate)
+{
+ auto fooNodeInfo = createType("Foo");
+ auto fooProperty = createProperty(itemMetaInfo.id(), "foo", {}, fooNodeInfo.id());
+
+ auto propertyComponent = generator.create(fooProperty);
+
+ ASSERT_THAT(propertyComponent, VariantWith<std::monostate>(std::monostate{}));
+}
+
+TEST_F(PropertyComponentGenerator,
+ complex_property_without_templates_and_writeable_is_returning_monostate)
+{
+ auto barTypeId = projectStorageMock.createValue(qmlModuleId, "bar");
+ auto fooNodeInfo = createTypeWithProperties("Foo", barTypeId);
+ auto fooProperty = createProperty(itemMetaInfo.id(), "foo", {}, fooNodeInfo.id());
+
+ auto propertyComponent = generator.create(fooProperty);
+
+ ASSERT_THAT(propertyComponent, VariantWith<std::monostate>(std::monostate{}));
+}
+
+TEST_F(PropertyComponentGenerator,
+ pointer_writeable_complex_property_without_templates_and_with_only_subproperties_without_templates_is_returning_monostate)
+{
+ auto stringId = projectStorageMock.createValue(qmlModuleId, "string");
+ auto fooNodeInfo = createTypeWithProperties("Foo", stringId);
+ auto sub1Text = toString(generator.create(fooNodeInfo.property("sub1")), "foo", "sub1");
+ auto sub2Text = toString(generator.create(fooNodeInfo.property("sub2")), "foo", "sub2");
+ auto fooProperty = createProperty(itemMetaInfo.id(),
+ "foo",
+ QmlDesigner::Storage::PropertyDeclarationTraits::IsPointer,
+ fooNodeInfo.id());
+
+ auto propertyComponent = generator.create(fooProperty);
+
+ ASSERT_THAT(propertyComponent, VariantWith<std::monostate>(std::monostate{}));
+}
+
+TEST_F(PropertyComponentGenerator, get_imports)
+{
+ auto imports = generator.imports();
+
+ ASSERT_THAT(imports,
+ ElementsAre(Eq("import HelperWidgets 2.0"),
+ Eq("import QtQuick 2.15"),
+ Eq("import QtQuick.Layouts 1.15"),
+ Eq("import StudioTheme 1.0 as StudioTheme")));
+}
+
+} // namespace
diff --git a/tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp b/tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp
new file mode 100644
index 0000000000..d02b2b12cb
--- /dev/null
+++ b/tests/unit/tests/unittests/componentcore/propertyeditorcomponentgenerator-test.cpp
@@ -0,0 +1,487 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <googletest.h>
+#include <mocks/projectstoragemock.h>
+#include <mocks/propertycomponentgeneratormock.h>
+#include <strippedstring-matcher.h>
+
+#include <propertyeditorcomponentgenerator.h>
+
+namespace {
+
+using BasicProperty = QmlDesigner::PropertyComponentGenerator::BasicProperty;
+using ComplexProperty = QmlDesigner::PropertyComponentGenerator::ComplexProperty;
+using QmlDesigner::PropertyMetaInfo;
+
+class PropertyEditorTemplateGenerator : public ::testing::Test
+{
+protected:
+ QmlDesigner::NodeMetaInfo createType(Utils::SmallStringView name,
+ QmlDesigner::TypeIds baseTypeIds = {})
+ {
+ auto typeId = projectStorageMock.createValue(qtQuickModuleId, name, baseTypeIds);
+
+ return {typeId, &projectStorageMock};
+ }
+
+ QmlDesigner::PropertyMetaInfo createProperty(QmlDesigner::TypeId typeId,
+ Utils::SmallString name,
+ QmlDesigner::Storage::PropertyDeclarationTraits traits,
+ QmlDesigner::TypeId propertyTypeId)
+ {
+ auto propertyId = projectStorageMock.createProperty(typeId, name, traits, propertyTypeId);
+
+ return {propertyId, &projectStorageMock};
+ }
+
+ void setImports(const QStringList &imports)
+ {
+ ON_CALL(propertyGeneratorMock, imports()).WillByDefault(Return(imports));
+ }
+
+ void addBasicProperty(const PropertyMetaInfo &property,
+ Utils::SmallStringView name,
+ const QString &componentContent)
+ {
+ ON_CALL(propertyGeneratorMock, create(property))
+ .WillByDefault(Return(BasicProperty{name, componentContent}));
+ }
+
+ void addComplexProperty(const PropertyMetaInfo &property,
+ Utils::SmallStringView name,
+ const QString &componentContent)
+ {
+ ON_CALL(propertyGeneratorMock, create(property))
+ .WillByDefault(Return(ComplexProperty{name, componentContent}));
+ }
+
+ QmlDesigner::PropertyMetaInfo createBasicProperty(QmlDesigner::TypeId typeId,
+ Utils::SmallString name,
+ QmlDesigner::Storage::PropertyDeclarationTraits traits,
+ QmlDesigner::TypeId propertyTypeId,
+ const QString &componentContent)
+ {
+ auto propertyInfo = createProperty(typeId, name, traits, propertyTypeId);
+ addBasicProperty(propertyInfo, name, componentContent);
+
+ return propertyInfo;
+ }
+
+ QmlDesigner::PropertyMetaInfo createComplexProperty(
+ QmlDesigner::TypeId typeId,
+ Utils::SmallString name,
+ QmlDesigner::Storage::PropertyDeclarationTraits traits,
+ QmlDesigner::TypeId propertyTypeId,
+ const QString &componentContent)
+ {
+ auto propertyInfo = createProperty(typeId, name, traits, propertyTypeId);
+ addComplexProperty(propertyInfo, name, componentContent);
+
+ return propertyInfo;
+ }
+
+protected:
+ QmlDesigner::SourceId sourceId = QmlDesigner::SourceId::create(10);
+ NiceMock<ProjectStorageMockWithQtQtuick> projectStorageMock{sourceId};
+ NiceMock<PropertyComponentGeneratorMock> propertyGeneratorMock;
+ QmlDesigner::PropertyEditorTemplateGenerator generator{propertyGeneratorMock};
+ QmlDesigner::ModuleId qtQuickModuleId = projectStorageMock.createModule("QtQuick");
+ QmlDesigner::NodeMetaInfo fooTypeInfo = createType("Foo");
+ QmlDesigner::TypeId dummyTypeId = projectStorageMock.commonTypeCache().builtinTypeId<double>();
+};
+
+TEST_F(PropertyEditorTemplateGenerator, no_properties_and_no_imports)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ }
+ }
+ })xy"};
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, properties_without_component_are_not_shows)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ }
+ }
+ })xy"};
+ createProperty(fooTypeInfo.id(), "x", {}, dummyTypeId);
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, show_component_button_for_a_component_node)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ ComponentButton {}
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ }
+ }
+ })xy"};
+
+ auto text = generator.create(fooTypeInfo, true);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, imports)
+{
+ QString expectedText{
+ R"xy(
+ import QtQtuick
+ import Studio 2.1
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ }
+ }
+ })xy"};
+ setImports({"import QtQtuick", "import Studio 2.1"});
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, basic_property)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Column {
+ width: parent.width
+ leftPadding: 8
+ bottomPadding: 10
+ SectionLayout {
+ Double{}
+ }
+ }
+ }
+ }
+ })xy"};
+ createBasicProperty(fooTypeInfo.id(), "value", {}, dummyTypeId, "Double{}");
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, basic_properties_with_base_type)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Column {
+ width: parent.width
+ leftPadding: 8
+ bottomPadding: 10
+ SectionLayout {
+ SuperDouble{}
+ Double{}
+ }
+ }
+ }
+ }
+ })xy"};
+ createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "Double{}");
+ auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()});
+ createBasicProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperDouble{}");
+
+ auto text = generator.create(superFooInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator,
+ only_handle_basic_properties_for_types_without_specifics_or_panes)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Column {
+ width: parent.width
+ leftPadding: 8
+ bottomPadding: 10
+ SectionLayout {
+ SuperDouble{}
+ }
+ }
+ }
+ }
+ })xy"};
+ createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "Double{}");
+ auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()});
+ createBasicProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperDouble{}");
+ projectStorageMock.setPropertyEditorPathId(fooTypeInfo.id(), sourceId);
+
+ auto text = generator.create(superFooInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, order_basic_property)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Column {
+ width: parent.width
+ leftPadding: 8
+ bottomPadding: 10
+ SectionLayout {
+ AnotherX{}
+ AndY{}
+ SomeZ{}
+ }
+ }
+ }
+ }
+ })xy"};
+ createBasicProperty(fooTypeInfo.id(), "z", {}, dummyTypeId, "SomeZ{}");
+ createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "AnotherX{}");
+ createBasicProperty(fooTypeInfo.id(), "y", {}, dummyTypeId, "AndY{}");
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, complex_property)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Complex{}
+ }
+ }
+ })xy"};
+ createComplexProperty(fooTypeInfo.id(), "value", {}, dummyTypeId, "Complex{}");
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, complex_properties_with_base_type)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Complex{}
+ SuperComplex{}
+ }
+ }
+ })xy"};
+ createComplexProperty(fooTypeInfo.id(), "delegate", {}, dummyTypeId, "Complex{}");
+ auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()});
+ createComplexProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperComplex{}");
+
+ auto text = generator.create(superFooInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator,
+ only_handle_complex_properties_for_types_without_specifics_or_panes)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ SuperComplex{}
+ }
+ }
+ })xy"};
+ createComplexProperty(fooTypeInfo.id(), "delegate", {}, dummyTypeId, "Complex{}");
+ auto superFooInfo = createType("SuperFoo", {fooTypeInfo.id()});
+ createComplexProperty(superFooInfo.id(), "value", {}, dummyTypeId, "SuperComplex{}");
+ projectStorageMock.setPropertyEditorPathId(fooTypeInfo.id(), sourceId);
+
+ auto text = generator.create(superFooInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, ordered_complex_property)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Anchors{}
+ Delegate{}
+ ComplexFont{}
+ }
+ }
+ })xy"};
+ createComplexProperty(fooTypeInfo.id(), "delegate", {}, dummyTypeId, "Delegate{}");
+ createComplexProperty(fooTypeInfo.id(), "font", {}, dummyTypeId, "ComplexFont{}");
+ createComplexProperty(fooTypeInfo.id(), "anchors", {}, dummyTypeId, "Anchors{}");
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+
+TEST_F(PropertyEditorTemplateGenerator, basic_is_placed_before_complex_components)
+{
+ QString expectedText{
+ R"xy(
+ Column {
+ width: parent.width
+ Section {
+ caption: "Exposed Custom Properties"
+ anchors.left: parent.left
+ anchors.right: parent.right
+ leftPadding: 0
+ rightPadding: 0
+ bottomPadding: 0
+ Column {
+ width: parent.width
+ Column {
+ width: parent.width
+ leftPadding: 8
+ bottomPadding: 10
+ SectionLayout {
+ Double{}
+ }
+ }
+ Font{}
+ }
+ }
+ })xy"};
+ createBasicProperty(fooTypeInfo.id(), "x", {}, dummyTypeId, "Double{}");
+ createComplexProperty(fooTypeInfo.id(), "font", {}, dummyTypeId, "Font{}");
+
+ auto text = generator.create(fooTypeInfo, false);
+
+ ASSERT_THAT(text, StrippedStringEq(expectedText));
+}
+} // namespace
diff --git a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp
index c1f3b4f8d7..c21d325936 100644
--- a/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp
+++ b/tests/unit/tests/unittests/metainfo/nodemetainfo-test.cpp
@@ -409,7 +409,7 @@ TEST_F(NodeMetaInfo, get_invalid_property_if_not_exists)
{
auto metaInfo = model.qtQuickItemMetaInfo();
- auto property = metaInfo.property("x");
+ auto property = metaInfo.property("foo");
ASSERT_THAT(property, PropertyId(IsFalse()));
}
diff --git a/tests/unit/tests/unittests/model/model-test.cpp b/tests/unit/tests/unittests/model/model-test.cpp
index 17374d4314..b705c15ffa 100644
--- a/tests/unit/tests/unittests/model/model-test.cpp
+++ b/tests/unit/tests/unittests/model/model-test.cpp
@@ -869,6 +869,49 @@ TEST_F(Model, meta_info_of_not_existing_type_is_invalid)
ASSERT_THAT(meta_info, IsFalse());
}
+TEST_F(Model, module_is_valid)
+{
+ auto module = model.module("QML");
+
+ ASSERT_THAT(module, IsTrue());
+}
+
+TEST_F(Model, module_returns_always_the_same)
+{
+ auto oldModule = model.module("QML");
+
+ auto module = model.module("QML");
+
+ ASSERT_THAT(module, oldModule);
+}
+
+TEST_F(Model, get_meta_info_by_module)
+{
+ auto module = model.module("QML");
+
+ auto metaInfo = model.metaInfo(module, "QtObject");
+
+ ASSERT_THAT(metaInfo, model.qmlQtObjectMetaInfo());
+}
+
+TEST_F(Model, get_invalid_meta_info_by_module_for_wrong_name)
+{
+ auto module = model.module("QML");
+
+ auto metaInfo = model.metaInfo(module, "Object");
+
+ ASSERT_THAT(metaInfo, IsFalse());
+}
+
+TEST_F(Model, get_invalid_meta_info_by_module_for_wrong_module)
+{
+ auto module = model.module("Qml");
+
+ auto metaInfo = model.metaInfo(module, "Object");
+
+ ASSERT_THAT(metaInfo, IsFalse());
+}
+
TEST_F(Model, add_refresh_callback_to_project_storage)
{
EXPECT_CALL(projectStorageMock, addRefreshCallback(_));