diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index c7989fda36d..340cbd77a1a 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -341,6 +341,7 @@ extend_qtc_library(QmlDesignerCore modelnodepositionrecalculator.cpp modelnodepositionrecalculator.h modelnodepositionstorage.cpp + modelresourcemanagementinterface.h modeltotextmerger.cpp modeltotextmerger.h modelutils.cpp diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index 95295fed6a0..c2cd807a8a2 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -40,7 +41,7 @@ class RewriterView; class NodeInstanceView; class TextModifier; -using PropertyListType = QList >; +using PropertyListType = QList>; class QMLDESIGNERCORE_EXPORT Model : public QObject { @@ -61,25 +62,34 @@ public: const TypeName &type, int major = 1, int minor = 1, - Model *metaInfoProxyModel = nullptr); - Model(const TypeName &typeName, int major = 1, int minor = 1, Model *metaInfoProxyModel = nullptr); + Model *metaInfoProxyModel = nullptr, + std::unique_ptr resourceManagement = {}); + Model(const TypeName &typeName, + int major = 1, + int minor = 1, + Model *metaInfoProxyModel = nullptr, + std::unique_ptr resourceManagement = {}); ~Model(); static ModelPointer create(const TypeName &typeName, int major = 1, int minor = 1, - Model *metaInfoProxyModel = nullptr) + Model *metaInfoProxyModel = nullptr, + std::unique_ptr resourceManagement = {}) { - return ModelPointer(new Model(typeName, major, minor, metaInfoProxyModel)); + return ModelPointer( + new Model(typeName, major, minor, metaInfoProxyModel, std::move(resourceManagement))); } static ModelPointer create(ProjectStorageType &projectStorage, const TypeName &typeName, int major = 1, - int minor = 1) + int minor = 1, + std::unique_ptr resourceManagement = {}) { - return ModelPointer(new Model(projectStorage, typeName, major, minor)); + return ModelPointer( + new Model(projectStorage, typeName, major, minor, nullptr, std::move(resourceManagement))); } QUrl fileUrl() const; @@ -120,6 +130,8 @@ public: void attachView(AbstractView *view); void detachView(AbstractView *view, ViewNotification emitDetachNotify = NotifyView); + QList allModelNodes() const; + // Editing sub-components: // Imports: @@ -177,4 +189,4 @@ private: std::unique_ptr d; }; -} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 685b900cb0b..e9891da620c 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -270,6 +270,8 @@ QMLDESIGNERCORE_EXPORT bool operator !=(const ModelNode &firstNode, const ModelN QMLDESIGNERCORE_EXPORT bool operator <(const ModelNode &firstNode, const ModelNode &secondNode); QMLDESIGNERCORE_EXPORT QDebug operator<<(QDebug debug, const ModelNode &modelNode); QMLDESIGNERCORE_EXPORT QTextStream& operator<<(QTextStream &stream, const ModelNode &modelNode); + +using ModelNodes = QList; } Q_DECLARE_METATYPE(QmlDesigner::ModelNode) diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index 443abf3f07e..bdb7b63392e 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -1619,16 +1619,20 @@ TypeName NodeMetaInfo::simplifiedTypeName() const int NodeMetaInfo::majorVersion() const { - if (isValid()) - return m_privateData->majorVersion(); + if constexpr (!useProjectStorage()) { + if (isValid()) + return m_privateData->majorVersion(); + } return -1; } int NodeMetaInfo::minorVersion() const { - if (isValid()) - return m_privateData->minorVersion(); + if constexpr (!useProjectStorage()) { + if (isValid()) + return m_privateData->minorVersion(); + } return -1; } diff --git a/src/plugins/qmldesigner/designercore/model/abstractproperty.cpp b/src/plugins/qmldesigner/designercore/model/abstractproperty.cpp index 3bccf54660c..00541e12e2f 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractproperty.cpp @@ -39,7 +39,6 @@ AbstractProperty::AbstractProperty(const Internal::InternalPropertyPointer &prop m_model(model), m_view(view) { - Q_ASSERT(!m_model || m_view); } AbstractProperty::AbstractProperty(const AbstractProperty &property, AbstractView *view) diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index a09db03dacd..9967d49ee7b 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -64,8 +64,12 @@ RewriterTransaction AbstractView::beginRewriterTransaction(const QByteArray &ide ModelNode AbstractView::createModelNode(const TypeName &typeName) { - const NodeMetaInfo metaInfo = model()->metaInfo(typeName); - return createModelNode(typeName, metaInfo.majorVersion(), metaInfo.minorVersion()); + if constexpr (useProjectStorage()) { + return createModelNode(typeName, -1, -1); + } else { + const NodeMetaInfo metaInfo = model()->metaInfo(typeName); + return createModelNode(typeName, metaInfo.majorVersion(), metaInfo.minorVersion()); + } } ModelNode AbstractView::createModelNode(const TypeName &typeName, diff --git a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp index 77dbec5e25a..45d4e4cf20e 100644 --- a/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/bindingproperty.cpp @@ -56,7 +56,7 @@ void BindingProperty::setExpression(const QString &expression) } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isBindingProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->setBindingProperty(internalNode(), name(), expression); } @@ -341,7 +341,7 @@ void BindingProperty::setDynamicTypeNameAndExpression(const TypeName &typeName, } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isBindingProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->setDynamicBindingProperty(internalNode(), name(), typeName, expression); } diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index fcdb66b8029..844454ae55e 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -62,9 +62,11 @@ ModelPrivate::ModelPrivate(Model *model, const TypeName &typeName, int major, int minor, - Model *metaInfoProxyModel) + Model *metaInfoProxyModel, + std::unique_ptr resourceManagement) : projectStorage{&projectStorage} , m_model{model} + , m_resourceManagement{std::move(resourceManagement)} { m_metaInfoProxyModel = metaInfoProxyModel; @@ -75,9 +77,14 @@ ModelPrivate::ModelPrivate(Model *model, m_currentTimelineNode = m_rootInternalNode; } -ModelPrivate::ModelPrivate( - Model *model, const TypeName &typeName, int major, int minor, Model *metaInfoProxyModel) +ModelPrivate::ModelPrivate(Model *model, + const TypeName &typeName, + int major, + int minor, + Model *metaInfoProxyModel, + std::unique_ptr resourceManagement) : m_model(model) + , m_resourceManagement{std::move(resourceManagement)} { m_metaInfoProxyModel = metaInfoProxyModel; @@ -279,7 +286,9 @@ InternalNodePointer ModelPrivate::createNode(const TypeName &typeName, notifyNodeCreated(newNode); if (!newNode->propertyNameList().isEmpty()) - notifyVariantPropertiesChanged(newNode, newNode->propertyNameList(), AbstractView::PropertiesAdded); + notifyVariantPropertiesChanged(newNode, + newNode->propertyNameList(), + AbstractView::PropertiesAdded); return newNode; } @@ -303,16 +312,40 @@ EnabledViewRange ModelPrivate::enabledViews() const return EnabledViewRange{m_viewList}; } +void ModelPrivate::handleResourceSet(const ModelResourceSet &resourceSet) +{ + for (const ModelNode &node : resourceSet.removeModelNodes) { + if (node) + removeNode(node.m_internalNode); + } + + for (const AbstractProperty &property : resourceSet.removeProperties) { + if (property) + removeProperty(property.m_internalNode->property(property.m_propertyName)); + } + + for (const auto &[property, expression] : resourceSet.setExpressions) { + if (property) + setBindingProperty(property.m_internalNode, property.m_propertyName, expression); + } +} + void ModelPrivate::removeAllSubNodes(const InternalNodePointer &node) { for (const InternalNodePointer &subNode : node->allSubNodes()) removeNodeFromModel(subNode); } +void ModelPrivate::removeNodeAndRelatedResources(const InternalNodePointer &node) +{ + if (m_resourceManagement) + handleResourceSet(m_resourceManagement->removeNode(ModelNode{node, m_model, nullptr})); + else + removeNode(node); +} + void ModelPrivate::removeNode(const InternalNodePointer &node) { - Q_ASSERT(node); - AbstractView::PropertyChangeFlags propertyChangeFlags = AbstractView::NoAdditionalChanges; notifyNodeAboutToBeRemoved(node); @@ -1088,6 +1121,15 @@ static QList toPropertyPairList(const QListremoveProperty(AbstractProperty{property, m_model, nullptr})); + else + removeProperty(property); +} + void ModelPrivate::removeProperty(const InternalPropertyPointer &property) { notifyPropertiesAboutToBeRemoved({property}); @@ -1408,13 +1450,19 @@ Model::Model(ProjectStorageType &projectStorage, const TypeName &typeName, int major, int minor, - Model *metaInfoProxyModel) + Model *metaInfoProxyModel, + std::unique_ptr resourceManagement) : d(std::make_unique( - this, projectStorage, typeName, major, minor, metaInfoProxyModel)) + this, projectStorage, typeName, major, minor, metaInfoProxyModel, std::move(resourceManagement))) {} -Model::Model(const TypeName &typeName, int major, int minor, Model *metaInfoProxyModel) - : d(std::make_unique(this, typeName, major, minor, metaInfoProxyModel)) +Model::Model(const TypeName &typeName, + int major, + int minor, + Model *metaInfoProxyModel, + std::unique_ptr resourceManagement) + : d(std::make_unique( + this, typeName, major, minor, metaInfoProxyModel, std::move(resourceManagement))) {} Model::~Model() = default; @@ -2156,4 +2204,9 @@ void Model::detachView(AbstractView *view, ViewNotification emitDetachNotify) d->detachView(view, emitNotify); } +QList Model::allModelNodes() const +{ + return QmlDesigner::toModelNodeList(d->allNodes(), nullptr); +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index ce2a49fc42d..b802d703f25 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -101,8 +101,14 @@ public: const TypeName &type, int major, int minor, - Model *metaInfoProxyModel); - ModelPrivate(Model *model, const TypeName &type, int major, int minor, Model *metaInfoProxyModel); + Model *metaInfoProxyModel, + std::unique_ptr resourceManagement); + ModelPrivate(Model *model, + const TypeName &type, + int major, + int minor, + Model *metaInfoProxyModel, + std::unique_ptr resourceManagement); ~ModelPrivate() override; @@ -120,6 +126,7 @@ public: bool isRootNode = false); /*factory methods for internal use in model and rewriter*/ + void removeNodeAndRelatedResources(const InternalNodePointer &node); void removeNode(const InternalNodePointer &node); void changeNodeId(const InternalNodePointer &node, const QString &id); void changeNodeType(const InternalNodePointer &node, const TypeName &typeName, int majorVersion, int minorVersion); @@ -235,6 +242,7 @@ public: //node state property manipulation void addProperty(const InternalNodePointer &node, const PropertyName &name); void setPropertyValue(const InternalNodePointer &node,const PropertyName &name, const QVariant &value); + void removePropertyAndRelatedResources(const InternalPropertyPointer &property); void removeProperty(const InternalPropertyPointer &property); void setBindingProperty(const InternalNodePointer &node, const PropertyName &name, const QString &expression); @@ -282,6 +290,7 @@ private: QVector toModelNodeVector(const QVector &nodeVector, AbstractView *view) const; QVector toInternalNodeVector(const QVector &modelNodeVector) const; EnabledViewRange enabledViews() const; + void handleResourceSet(const ModelResourceSet &resourceSet); public: NotNullPointer projectStorage = nullptr; @@ -300,6 +309,7 @@ private: InternalNodePointer m_currentStateNode; InternalNodePointer m_rootInternalNode; InternalNodePointer m_currentTimelineNode; + std::unique_ptr m_resourceManagement; QUrl m_fileUrl; QPointer m_rewriterView; QPointer m_nodeInstanceView; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index fd3c59fa1f1..7e64f4c4bf4 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -290,7 +290,7 @@ A node might become invalid if e.g. it or one of its ancestors is deleted. */ bool ModelNode::isValid() const { - return !m_model.isNull() && !m_view.isNull() && m_internalNode && m_internalNode->isValid; + return !m_model.isNull() && m_internalNode && m_internalNode->isValid; } /*! @@ -652,7 +652,7 @@ void ModelNode::removeProperty(const PropertyName &name) const return; if (m_internalNode->hasProperty(name)) - model()->d->removeProperty(m_internalNode->property(name)); + model()->d->removePropertyAndRelatedResources(m_internalNode->property(name)); } /*! \brief removes this node from the node tree @@ -692,7 +692,7 @@ void ModelNode::destroy() return; removeModelNodeFromSelection(*this); - model()->d->removeNode(m_internalNode); + model()->d->removeNodeAndRelatedResources(m_internalNode); } //\} diff --git a/src/plugins/qmldesigner/designercore/model/modelresourcemanagementinterface.h b/src/plugins/qmldesigner/designercore/model/modelresourcemanagementinterface.h new file mode 100644 index 00000000000..b2188132d6a --- /dev/null +++ b/src/plugins/qmldesigner/designercore/model/modelresourcemanagementinterface.h @@ -0,0 +1,39 @@ +// 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 + +#include + +namespace QmlDesigner { + +struct ModelResourceSet +{ + struct SetExpression + { + BindingProperty property; + QString expression; + }; + + QList removeModelNodes; + QList removeProperties; + QList setExpressions; +}; + +class QMLDESIGNERCORE_EXPORT ModelResourceManagementInterface +{ +public: + ModelResourceManagementInterface() = default; + virtual ~ModelResourceManagementInterface() = default; + + ModelResourceManagementInterface(const ModelResourceManagementInterface &) = delete; + ModelResourceManagementInterface &operator=(const ModelResourceManagementInterface &) = delete; + ModelResourceManagementInterface(ModelResourceManagementInterface &&) = default; + ModelResourceManagementInterface &operator=(ModelResourceManagementInterface &&) = default; + + virtual ModelResourceSet removeNode(const ModelNode &node) const = 0; + virtual ModelResourceSet removeProperty(const AbstractProperty &property) const = 0; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp b/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp index e2ef9d76f06..60b4e77dc6d 100644 --- a/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/nodeabstractproperty.cpp @@ -71,7 +71,7 @@ void NodeAbstractProperty::reparentHere(const ModelNode &modelNode, bool isNode return; if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isNodeAbstractProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); if (modelNode.hasParentProperty()) { Internal::InternalNodeAbstractProperty::Pointer oldParentProperty = modelNode.internalNode()->parentProperty(); diff --git a/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp b/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp index a9cc7a34f54..4af8428e6be 100644 --- a/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/nodeproperty.cpp @@ -32,7 +32,7 @@ void NodeProperty::setModelNode(const ModelNode &modelNode) } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isNodeProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->reparentNode(internalNode(), name(), modelNode.internalNode(), false); //### we have to add a flag that this is not a list } diff --git a/src/plugins/qmldesigner/designercore/model/signalhandlerproperty.cpp b/src/plugins/qmldesigner/designercore/model/signalhandlerproperty.cpp index 205105187d1..4690044fa61 100644 --- a/src/plugins/qmldesigner/designercore/model/signalhandlerproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/signalhandlerproperty.cpp @@ -41,7 +41,7 @@ void SignalHandlerProperty::setSource(const QString &source) } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isSignalHandlerProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->setSignalHandlerProperty(internalNode(), name(), source); } @@ -118,7 +118,7 @@ void SignalDeclarationProperty::setSignature(const QString &signature) } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isSignalDeclarationProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->setSignalDeclarationProperty(internalNode(), name(), signature); } diff --git a/src/plugins/qmldesigner/designercore/model/variantproperty.cpp b/src/plugins/qmldesigner/designercore/model/variantproperty.cpp index 48d0fa86300..c879ec7a347 100644 --- a/src/plugins/qmldesigner/designercore/model/variantproperty.cpp +++ b/src/plugins/qmldesigner/designercore/model/variantproperty.cpp @@ -47,7 +47,7 @@ void VariantProperty::setValue(const QVariant &value) } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isVariantProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->setVariantProperty(internalNode(), name(), value); } @@ -96,7 +96,7 @@ void VariantProperty::setDynamicTypeNameAndValue(const TypeName &type, const QVa } if (internalNode()->hasProperty(name()) && !internalNode()->property(name())->isVariantProperty()) - privateModel()->removeProperty(internalNode()->property(name())); + privateModel()->removePropertyAndRelatedResources(internalNode()->property(name())); privateModel()->setDynamicVariantProperty(internalNode(), name(), type, value); } diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 4665159d154..974ebdf6aab 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -242,36 +242,37 @@ public: return false; } - bool isBasedOn(TypeId id0) const override { return isBasedOn_(id0); } + bool isBasedOn(TypeId typeId) const { return isBasedOn_(typeId); } - bool isBasedOn(TypeId id0, TypeId id1) const override { return isBasedOn_(id0, id1); } + bool isBasedOn(TypeId typeId, TypeId id1) const override { return isBasedOn_(typeId, id1); } - bool isBasedOn(TypeId id0, TypeId id1, TypeId id2) const override + bool isBasedOn(TypeId typeId, TypeId id1, TypeId id2) const override { - return isBasedOn_(id0, id1, id2); + return isBasedOn_(typeId, id1, id2); } - bool isBasedOn(TypeId id0, TypeId id1, TypeId id2, TypeId id3) const override + bool isBasedOn(TypeId typeId, TypeId id1, TypeId id2, TypeId id3) const override { - return isBasedOn_(id0, id1, id2, id3); + return isBasedOn_(typeId, id1, id2, id3); } - bool isBasedOn(TypeId id0, TypeId id1, TypeId id2, TypeId id3, TypeId id4) const override + bool isBasedOn(TypeId typeId, TypeId id1, TypeId id2, TypeId id3, TypeId id4) const override { - return isBasedOn_(id0, id1, id2, id3, id4); + return isBasedOn_(typeId, id1, id2, id3, id4); } - bool isBasedOn(TypeId id0, TypeId id1, TypeId id2, TypeId id3, TypeId id4, TypeId id5) const override + bool isBasedOn(TypeId typeId, TypeId id1, TypeId id2, TypeId id3, TypeId id4, TypeId id5) const override { - return isBasedOn_(id0, id1, id2, id3, id4, id5); + return isBasedOn_(typeId, id1, id2, id3, id4, id5); } - bool isBasedOn(TypeId id0, TypeId id1, TypeId id2, TypeId id3, TypeId id4, TypeId id5, TypeId id6) const override + bool isBasedOn( + TypeId typeId, TypeId id1, TypeId id2, TypeId id3, TypeId id4, TypeId id5, TypeId id6) const override { - return isBasedOn_(id0, id1, id2, id3, id4, id5, id6); + return isBasedOn_(typeId, id1, id2, id3, id4, id5, id6); } - bool isBasedOn(TypeId id0, + bool isBasedOn(TypeId typeId, TypeId id1, TypeId id2, TypeId id3, @@ -280,7 +281,7 @@ public: TypeId id6, TypeId id7) const override { - return isBasedOn_(id0, id1, id2, id3, id4, id5, id6, id7); + return isBasedOn_(typeId, id1, id2, id3, id4, id5, id6, id7); } TypeId fetchTypeIdByExportedName(Utils::SmallStringView name) const diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h index 9558bc2e184..bc74172ecd7 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageinterface.h @@ -36,7 +36,6 @@ public: propertyName(PropertyDeclarationId propertyDeclarationId) const = 0; virtual TypeIds prototypeAndSelfIds(TypeId type) const = 0; virtual TypeIds prototypeIds(TypeId type) const = 0; - virtual bool isBasedOn(TypeId) const = 0; virtual bool isBasedOn(TypeId, TypeId) const = 0; virtual bool isBasedOn(TypeId, TypeId, TypeId) const = 0; virtual bool isBasedOn(TypeId, TypeId, TypeId, TypeId) const = 0; diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index d0fde6d5d9a..834c43ae3fd 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -56,6 +56,8 @@ add_qtc_test(unittest GTEST gtest-std-printing.h lastchangedrowid-test.cpp import-test.cpp + model-test.cpp + modelresourcemanagementmock.h matchingtext-test.cpp mockfutureinterface.h mockmutex.h @@ -66,6 +68,7 @@ add_qtc_test(unittest GTEST mocktimer.cpp mocktimer.h nodelistproperty-test.cpp processevents-utilities.cpp processevents-utilities.h + projectstoragemock.cpp projectstoragemock.h sizedarray-test.cpp smallstring-test.cpp spydummy.cpp spydummy.h @@ -258,6 +261,7 @@ extend_qtc_test(unittest model/model.cpp model/model_p.h model/modelnode.cpp + model/modelresourcemanagementinterface.h model/propertycontainer.cpp model/propertyparser.cpp model/nodeabstractproperty.cpp diff --git a/tests/unit/unittest/listmodeleditor-test.cpp b/tests/unit/unittest/listmodeleditor-test.cpp index b3373f1e645..146ba65ad5d 100644 --- a/tests/unit/unittest/listmodeleditor-test.cpp +++ b/tests/unit/unittest/listmodeleditor-test.cpp @@ -73,19 +73,10 @@ class ListModelEditor : public testing::Test public: ListModelEditor() { - setModuleId("QtQuick", modelId_QtQuick); - setType(modelId_QtQuick, "Item", "data"); - designerModel = QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item", 1, 1); - setModuleId("QtQml.Models", modelId_QtQml_Models); - setType(modelId_QtQml_Models, "ListModel", "children"); - setType(modelId_QtQml_Models, "ListElement", "children"); - componentModel = QmlDesigner::Model::create(projectStorageMock, "QtQml.Models.ListModel", 1, 1); - designerModel->attachView(&mockView); emptyListModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); - setType(modelId_QtQuick, "ListView", "data"); listViewNode = mockView.createModelNode("QtQuick.ListView", 2, 15); listModelNode = mockView.createModelNode("QtQml.Models.ListModel", 2, 15); mockView.rootModelNode().defaultNodeListProperty().reparentHere(listModelNode); @@ -108,29 +99,6 @@ public: ON_CALL(goIntoComponentMock, Call(_)).WillByDefault([](ModelNode node) { return node; }); } - void setModuleId(Utils::SmallStringView moduleName, ModuleId moduleId) - { - ON_CALL(projectStorageMock, moduleId(Eq(moduleName))).WillByDefault(Return(moduleId)); - } - - void setType(ModuleId moduleId, - Utils::SmallStringView typeName, - Utils::SmallString defaultPeopertyName) - { - static int typeIdNumber = 0; - TypeId typeId = TypeId::create(++typeIdNumber); - - static int defaultPropertyIdNumber = 0; - PropertyDeclarationId defaultPropertyId = PropertyDeclarationId::create( - ++defaultPropertyIdNumber); - - ON_CALL(projectStorageMock, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId)); - ON_CALL(projectStorageMock, type(Eq(typeId))) - .WillByDefault(Return(Info::Type{defaultPropertyId, {}})); - ON_CALL(projectStorageMock, propertyName(Eq(defaultPropertyId))) - .WillByDefault(Return(defaultPeopertyName)); - } - using Entry = std::pair; ModelNode createElement(std::initializer_list entries, AbstractView &view, ModelNode listModel) @@ -211,9 +179,10 @@ public: } protected: - NiceMock projectStorageMock; + NiceMock projectStorageMock; NiceMock> goIntoComponentMock; - QmlDesigner::ModelPointer designerModel; + QmlDesigner::ModelPointer designerModel{ + QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item", 1, 1)}; NiceMock mockView; QmlDesigner::ListModelEditorModel model{ [&] { return mockView.createModelNode("QtQml.Models.ListModel", 2, 15); }, @@ -225,11 +194,10 @@ protected: ModelNode element1; ModelNode element2; ModelNode element3; - QmlDesigner::ModelPointer componentModel; + QmlDesigner::ModelPointer componentModel{ + QmlDesigner::Model::create(projectStorageMock, "QtQml.Models.ListModel", 1, 1)}; NiceMock mockComponentView; ModelNode componentElement; - ModuleId modelId_QtQuick = ModuleId::create(1); - ModuleId modelId_QtQml_Models = ModuleId::create(2); }; TEST_F(ListModelEditor, CreatePropertyNameSet) diff --git a/tests/unit/unittest/mocklistmodeleditorview.h b/tests/unit/unittest/mocklistmodeleditorview.h index 1de8e1edea0..0f6ff38742e 100644 --- a/tests/unit/unittest/mocklistmodeleditorview.h +++ b/tests/unit/unittest/mocklistmodeleditorview.h @@ -26,10 +26,25 @@ public: const QmlDesigner::NodeAbstractProperty &oldPropertyParent, AbstractView::PropertyChangeFlags propertyChange), (override)); + MOCK_METHOD(void, propertiesRemoved, (const QList &propertyList), (override)); + MOCK_METHOD(void, + propertiesAboutToBeRemoved, + (const QList &propertyList), + (override)); + + MOCK_METHOD(void, + bindingPropertiesChanged, + (const QList &propertyList, + PropertyChangeFlags propertyChange), + (override)); + MOCK_METHOD(void, + bindingPropertiesAboutToBeChanged, + (const QList &propertyList), + (override)); MOCK_METHOD(void, nodeRemoved, @@ -37,4 +52,5 @@ public: const QmlDesigner::NodeAbstractProperty &parentProperty, AbstractView::PropertyChangeFlags propertyChange), (override)); + MOCK_METHOD(void, nodeAboutToBeRemoved, (const QmlDesigner::ModelNode &removedNode), (override)); }; diff --git a/tests/unit/unittest/model-test.cpp b/tests/unit/unittest/model-test.cpp new file mode 100644 index 00000000000..7de2618aa93 --- /dev/null +++ b/tests/unit/unittest/model-test.cpp @@ -0,0 +1,491 @@ +// 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 "mocklistmodeleditorview.h" +#include "modelresourcemanagementmock.h" +#include "projectstoragemock.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +using QmlDesigner::AbstractProperty; +using QmlDesigner::ModelNode; +using QmlDesigner::ModelNodes; +using QmlDesigner::ModelResourceSet; + +template +auto HasPropertyName(const Matcher &matcher) +{ + return Property(&AbstractProperty::name, matcher); +} + +class Model : public ::testing::Test +{ +protected: + Model() + { + model.attachView(&viewMock); + rootNode = viewMock.rootModelNode(); + ON_CALL(resourceManagementMock, removeNode(_)).WillByDefault([](const auto &node) { + return ModelResourceSet{{node}, {}, {}}; + }); + ON_CALL(resourceManagementMock, removeProperty(_)).WillByDefault([](const auto &property) { + return ModelResourceSet{{}, {property}, {}}; + }); + } + + ~Model() { model.detachView(&viewMock); } + + auto createNodeWithParent(const ModelNode &parentNode) + { + auto node = viewMock.createModelNode("QtQuick.Item"); + parentNode.defaultNodeAbstractProperty().reparentHere(node); + + return node; + } + + auto createProperty(const ModelNode &parentNode, QmlDesigner::PropertyName name) + { + auto property = parentNode.variantProperty(name); + property.setValue(4); + return property; + } + +protected: + NiceMock viewMock; + NiceMock projectStorageMock; + NiceMock resourceManagementMock; + QmlDesigner::Model model{projectStorageMock, + "QtQuick.Item", + -1, + -1, + nullptr, + std::make_unique( + resourceManagementMock)}; + ModelNode rootNode; +}; + +TEST_F(Model, ModelNodeDestroyIsCallingModelResourceManagementRemoveNode) +{ + auto node = createNodeWithParent(rootNode); + + EXPECT_CALL(resourceManagementMock, removeNode(node)); + + node.destroy(); +} + +TEST_F(Model, ModelNodeRemoveProperyIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.variantProperty("foo"); + property.setValue(4); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.removeProperty("foo"); +} + +TEST_F(Model, NodeAbstractPropertyReparentHereIsCallingModelResourceManagementRemoveProperty) +{ + auto node = createNodeWithParent(rootNode); + auto property = rootNode.variantProperty("foo"); + property.setValue(4); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.nodeListProperty("foo").reparentHere(node); +} + +TEST_F(Model, NodePropertySetModelNodeIsCallingModelResourceManagementRemoveProperty) +{ + auto node = createNodeWithParent(rootNode); + auto property = rootNode.variantProperty("foo"); + property.setValue(4); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.nodeProperty("foo").setModelNode(node); +} + +TEST_F(Model, VariantPropertySetValueIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.bindingProperty("foo"); + property.setExpression("blah"); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.variantProperty("foo").setValue(7); +} + +TEST_F(Model, + VariantPropertySetDynamicTypeNameAndEnumerationIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.bindingProperty("foo"); + property.setExpression("blah"); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.variantProperty("foo").setDynamicTypeNameAndEnumeration("int", "Ha"); +} + +TEST_F(Model, VariantPropertySetDynamicTypeNameAndValueIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.bindingProperty("foo"); + property.setExpression("blah"); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.variantProperty("foo").setDynamicTypeNameAndValue("int", 7); +} + +TEST_F(Model, BindingPropertySetExpressionIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.variantProperty("foo"); + property.setValue(4); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.bindingProperty("foo").setExpression("blah"); +} + +TEST_F(Model, + BindingPropertySetDynamicTypeNameAndExpressionIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.variantProperty("foo"); + property.setValue(4); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.bindingProperty("foo").setDynamicTypeNameAndExpression("int", "blah"); +} + +TEST_F(Model, SignalHandlerPropertySetSourceIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.bindingProperty("foo"); + property.setExpression("blah"); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.signalHandlerProperty("foo").setSource("blah"); +} + +TEST_F(Model, SignalDeclarationPropertySetSignatureIsCallingModelResourceManagementRemoveProperty) +{ + auto property = rootNode.bindingProperty("foo"); + property.setExpression("blah"); + + EXPECT_CALL(resourceManagementMock, removeProperty(Eq(property))); + + rootNode.signalDeclarationProperty("foo").setSignature("blah"); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewNodeAboutToBeRemoved) +{ + auto node = createNodeWithParent(rootNode); + auto node2 = createNodeWithParent(rootNode); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node, node2}, {}, {}})); + + EXPECT_CALL(viewMock, nodeAboutToBeRemoved(Eq(node))); + EXPECT_CALL(viewMock, nodeAboutToBeRemoved(Eq(node2))); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewNodeRemoved) +{ + auto node = createNodeWithParent(rootNode); + auto node2 = createNodeWithParent(rootNode); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node, node2}, {}, {}})); + + EXPECT_CALL(viewMock, nodeRemoved(Eq(node), _, _)); + EXPECT_CALL(viewMock, nodeRemoved(Eq(node2), _, _)); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewNodeRemovedWithValidNodes) +{ + auto node = createNodeWithParent(rootNode); + auto node2 = createNodeWithParent(rootNode); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node, node2, ModelNode{}}, {}, {}})); + + EXPECT_CALL(viewMock, nodeRemoved(Eq(node), _, _)); + EXPECT_CALL(viewMock, nodeRemoved(Eq(node2), _, _)); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewPropertiesAboutToBeRemoved) +{ + auto node = createNodeWithParent(rootNode); + auto property = createProperty(rootNode, "foo"); + auto property2 = createProperty(rootNode, "bar"); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node}, {property, property2}, {}})); + + EXPECT_CALL(viewMock, propertiesAboutToBeRemoved(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, propertiesAboutToBeRemoved(ElementsAre(Eq(property2)))); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewPropertiesRemoved) +{ + auto node = createNodeWithParent(rootNode); + auto property = createProperty(rootNode, "foo"); + auto property2 = createProperty(rootNode, "bar"); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node}, {property, property2}, {}})); + + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property2)))); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewPropertiesRemovedOnlyWithValidProperties) +{ + auto node = createNodeWithParent(rootNode); + auto property = createProperty(rootNode, "foo"); + auto property2 = createProperty(rootNode, "bar"); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node}, {property, property2, {}}, {}})); + + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property2)))); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewBindingPropertiesAboutToBeChanged) +{ + auto node = createNodeWithParent(rootNode); + auto property = rootNode.bindingProperty("foo"); + auto property2 = rootNode.bindingProperty("bar"); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node}, {}, {{property, "yi"}, {property2, "er"}}})); + + EXPECT_CALL(viewMock, bindingPropertiesAboutToBeChanged(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, bindingPropertiesAboutToBeChanged(ElementsAre(Eq(property2)))); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewBindingPropertiesChanged) +{ + auto node = createNodeWithParent(rootNode); + auto property = rootNode.bindingProperty("foo"); + auto property2 = rootNode.bindingProperty("bar"); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return(ModelResourceSet{{node}, {}, {{property, "yi"}, {property2, "er"}}})); + + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property)), _)); + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property2)), _)); + + node.destroy(); +} + +TEST_F(Model, ModelNodeDestroyIsCallingAbstractViewBindingPropertiesChangedOnlyWithValidProperties) +{ + auto node = createNodeWithParent(rootNode); + auto property = rootNode.bindingProperty("foo"); + auto property2 = rootNode.bindingProperty("bar"); + ON_CALL(resourceManagementMock, removeNode(node)) + .WillByDefault(Return( + ModelResourceSet{{node}, {}, {{property, "yi"}, {property2, "er"}, {{}, "san"}}})); + + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property)), _)); + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property2)), _)); + + node.destroy(); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewNodeAboutToBeRemoved) +{ + auto property = createProperty(rootNode, "foo"); + auto node = createNodeWithParent(rootNode); + auto node2 = createNodeWithParent(rootNode); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault(Return(ModelResourceSet{{node, node2}, {property}, {}})); + + EXPECT_CALL(viewMock, nodeAboutToBeRemoved(Eq(node))); + EXPECT_CALL(viewMock, nodeAboutToBeRemoved(Eq(node2))); + + rootNode.removeProperty("foo"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewNodeRemoved) +{ + auto property = createProperty(rootNode, "foo"); + auto node = createNodeWithParent(rootNode); + auto node2 = createNodeWithParent(rootNode); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault(Return(ModelResourceSet{{node, node2}, {property}, {}})); + + EXPECT_CALL(viewMock, nodeRemoved(Eq(node), _, _)); + EXPECT_CALL(viewMock, nodeRemoved(Eq(node2), _, _)); + + rootNode.removeProperty("foo"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewNodeRemovedWithValidNodes) +{ + auto property = createProperty(rootNode, "foo"); + auto node = createNodeWithParent(rootNode); + auto node2 = createNodeWithParent(rootNode); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault(Return(ModelResourceSet{{node, node2, ModelNode{}}, {property}, {}})); + + EXPECT_CALL(viewMock, nodeRemoved(Eq(node), _, _)); + EXPECT_CALL(viewMock, nodeRemoved(Eq(node2), _, _)); + + rootNode.removeProperty("foo"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewPropertiesAboutToBeRemoved) +{ + auto property = createProperty(rootNode, "yi"); + auto property2 = createProperty(rootNode, "er"); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault(Return(ModelResourceSet{{}, {property, property2}, {}})); + + EXPECT_CALL(viewMock, propertiesAboutToBeRemoved(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, propertiesAboutToBeRemoved(ElementsAre(Eq(property2)))); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewPropertiesRemoved) +{ + auto property = createProperty(rootNode, "yi"); + auto property2 = createProperty(rootNode, "er"); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault(Return(ModelResourceSet{{}, {property, property2}, {}})); + + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property2)))); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewPropertiesRemovedOnlyWithValidProperties) +{ + auto property = createProperty(rootNode, "yi"); + auto property2 = createProperty(rootNode, "er"); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault(Return(ModelResourceSet{{}, {property, property2, {}}, {}})); + + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property)))); + EXPECT_CALL(viewMock, propertiesRemoved(ElementsAre(Eq(property2)))); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewBindingPropertiesAboutToBeChanged) +{ + auto property = createProperty(rootNode, "yi"); + auto property1 = rootNode.bindingProperty("foo"); + auto property2 = rootNode.bindingProperty("bar"); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault( + Return(ModelResourceSet{{}, {property}, {{property1, "yi"}, {property2, "er"}}})); + + EXPECT_CALL(viewMock, bindingPropertiesAboutToBeChanged(ElementsAre(Eq(property1)))); + EXPECT_CALL(viewMock, bindingPropertiesAboutToBeChanged(ElementsAre(Eq(property2)))); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, ModelNodeRemovePropertyIsCallingAbstractViewBindingPropertiesChanged) +{ + auto property = createProperty(rootNode, "yi"); + auto property1 = rootNode.bindingProperty("foo"); + auto property2 = rootNode.bindingProperty("bar"); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault( + Return(ModelResourceSet{{}, {property}, {{property1, "yi"}, {property2, "er"}}})); + + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property1)), _)); + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property2)), _)); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, + ModelNodeRemovePropertyIsCallingAbstractViewBindingPropertiesChangedOnlyWithValidProperties) +{ + auto property = createProperty(rootNode, "yi"); + auto property1 = rootNode.bindingProperty("foo"); + auto property2 = rootNode.bindingProperty("bar"); + ON_CALL(resourceManagementMock, removeProperty(property)) + .WillByDefault( + Return(ModelResourceSet{{}, {property}, {{property1, "yi"}, {property2, "er"}, {}}})); + + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property1)), _)); + EXPECT_CALL(viewMock, bindingPropertiesChanged(ElementsAre(Eq(property2)), _)); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, ByDefaultRemoveModelNodeRemovesNode) +{ + model.detachView(&viewMock); + QmlDesigner::Model newModel{projectStorageMock, "QtQuick.Item"}; + newModel.attachView(&viewMock); + auto node = createNodeWithParent(viewMock.rootModelNode()); + + EXPECT_CALL(viewMock, nodeAboutToBeRemoved(Eq(node))); + + node.destroy(); +} + +TEST_F(Model, ByDefaultRemovePropertiesRemovesProperty) +{ + model.detachView(&viewMock); + QmlDesigner::Model newModel{projectStorageMock, "QtQuick.Item"}; + newModel.attachView(&viewMock); + rootNode = viewMock.rootModelNode(); + auto property = createProperty(rootNode, "yi"); + + EXPECT_CALL(viewMock, propertiesAboutToBeRemoved(ElementsAre(Eq(property)))); + + rootNode.removeProperty("yi"); +} + +TEST_F(Model, ByDefaultRemoveModelNodeInFactoryMethodCallsRemovesNode) +{ + model.detachView(&viewMock); + auto newModel = QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item"); + newModel->attachView(&viewMock); + auto node = createNodeWithParent(viewMock.rootModelNode()); + + EXPECT_CALL(viewMock, nodeAboutToBeRemoved(Eq(node))); + + node.destroy(); +} + +TEST_F(Model, ByDefaultRemovePropertiesInFactoryMethodCallsRemoveProperty) +{ + model.detachView(&viewMock); + auto newModel = QmlDesigner::Model::create(projectStorageMock, "QtQuick.Item"); + newModel->attachView(&viewMock); + rootNode = viewMock.rootModelNode(); + auto property = createProperty(rootNode, "yi"); + + EXPECT_CALL(viewMock, propertiesAboutToBeRemoved(ElementsAre(Eq(property)))); + + rootNode.removeProperty("yi"); +} + +} // namespace diff --git a/tests/unit/unittest/modelresourcemanagementmock.h b/tests/unit/unittest/modelresourcemanagementmock.h new file mode 100644 index 00000000000..0b98adfcff0 --- /dev/null +++ b/tests/unit/unittest/modelresourcemanagementmock.h @@ -0,0 +1,42 @@ +// 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 "googletest.h" + +#include +#include + +class ModelResourceManagementMock : public QmlDesigner::ModelResourceManagementInterface +{ +public: + MOCK_METHOD(QmlDesigner::ModelResourceSet, + removeNode, + (const QmlDesigner::ModelNode &), + (const, override)); + MOCK_METHOD(QmlDesigner::ModelResourceSet, + removeProperty, + (const QmlDesigner::AbstractProperty &), + (const, override)); +}; + +class ModelResourceManagementMockWrapper : public QmlDesigner::ModelResourceManagementInterface +{ +public: + ModelResourceManagementMockWrapper(ModelResourceManagementMock &mock) + : mock{mock} + {} + + QmlDesigner::ModelResourceSet removeNode(const QmlDesigner::ModelNode &node) const override + { + return mock.removeNode(node); + } + + QmlDesigner::ModelResourceSet removeProperty(const QmlDesigner::AbstractProperty &property) const override + { + return mock.removeProperty(property); + } + + ModelResourceManagementMock &mock; +}; diff --git a/tests/unit/unittest/nodelistproperty-test.cpp b/tests/unit/unittest/nodelistproperty-test.cpp index 8abb350aa63..36a8dcb6df7 100644 --- a/tests/unit/unittest/nodelistproperty-test.cpp +++ b/tests/unit/unittest/nodelistproperty-test.cpp @@ -25,11 +25,6 @@ protected: using iterator = QmlDesigner::NodeListProperty::iterator; NodeListProperty() { - ModuleId modelId_QtQuick = ModuleId::create(1); - setModuleId("QtQuick", modelId_QtQuick); - setType(modelId_QtQuick, "Item", "data"); - model = std::make_unique(projectStorageMock, "QtQuick.Item"); - model->attachView(&abstractViewMock); nodeListProperty = abstractViewMock.rootModelNode().nodeListProperty("foo"); @@ -82,8 +77,9 @@ protected: } protected: - NiceMock projectStorageMock; - std::unique_ptr model; + NiceMock projectStorageMock; + std::unique_ptr model{ + std::make_unique(projectStorageMock, "QtQuick.Item")}; NiceMock abstractViewMock; QmlDesigner::NodeListProperty nodeListProperty; ModelNode node1; diff --git a/tests/unit/unittest/projectstoragemock.cpp b/tests/unit/unittest/projectstoragemock.cpp new file mode 100644 index 00000000000..8e4928377f4 --- /dev/null +++ b/tests/unit/unittest/projectstoragemock.cpp @@ -0,0 +1,89 @@ +// 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 "projectstoragemock.h" + +namespace QmlDesigner { +namespace { + +template +void incrementBasicId(BasicId &id) +{ + id = BasicId::create(id.internalId() + 1); +} + +ModuleId createModule(ProjectStorageMock &mock, Utils::SmallStringView moduleName) +{ + static ModuleId moduleId; + incrementBasicId(moduleId); + + ON_CALL(mock, moduleId(Eq(moduleName))).WillByDefault(Return(moduleId)); + + return moduleId; +} + +TypeId createType(ProjectStorageMock &mock, + ModuleId moduleId, + Utils::SmallStringView typeName, + Utils::SmallString defaultPropertyName, + Storage::TypeTraits typeTraits, + TypeId baseTypeId = TypeId{}) +{ + static TypeId typeId; + incrementBasicId(typeId); + + static PropertyDeclarationId defaultPropertyId; + incrementBasicId(defaultPropertyId); + + ON_CALL(mock, typeId(Eq(moduleId), Eq(typeName), _)).WillByDefault(Return(typeId)); + ON_CALL(mock, type(Eq(typeId))) + .WillByDefault(Return(Storage::Info::Type{defaultPropertyId, typeTraits})); + ON_CALL(mock, propertyName(Eq(defaultPropertyId))).WillByDefault(Return(defaultPropertyName)); + + if (baseTypeId) + ON_CALL(mock, isBasedOn(Eq(typeId), Eq(baseTypeId))).WillByDefault(Return(true)); + + return typeId; +} + +TypeId createObject(ProjectStorageMock &mock, + ModuleId moduleId, + Utils::SmallStringView typeName, + Utils::SmallString defaultPropertyName, + TypeId baseTypeId = TypeId{}) +{ + return createType( + mock, moduleId, typeName, defaultPropertyName, Storage::TypeTraits::Reference, baseTypeId); +} +void setupIsBasedOn(ProjectStorageMock &mock) +{ + auto call = [&](TypeId typeId, auto... ids) -> bool { + return (mock.isBasedOn(typeId, ids) || ...); + }; + ON_CALL(mock, isBasedOn(_, _, _)).WillByDefault(call); + ON_CALL(mock, isBasedOn(_, _, _, _)).WillByDefault(call); + ON_CALL(mock, isBasedOn(_, _, _, _, _)).WillByDefault(call); + ON_CALL(mock, isBasedOn(_, _, _, _, _, _)).WillByDefault(call); + ON_CALL(mock, isBasedOn(_, _, _, _, _, _, _)).WillByDefault(call); + ON_CALL(mock, isBasedOn(_, _, _, _, _, _, _, _)).WillByDefault(call); +} + +} // namespace +} // namespace QmlDesigner + +void ProjectStorageMock::setupQtQtuick() +{ + QmlDesigner::setupIsBasedOn(*this); + + auto qmlModuleId = QmlDesigner::createModule(*this, "QML"); + auto qtQmlModelsModuleId = QmlDesigner::createModule(*this, "QtQml.Models"); + auto qtQuickModuleId = QmlDesigner::createModule(*this, "QtQuick"); + + auto qtObjectId = QmlDesigner::createObject(*this, qmlModuleId, "QtObject", "children"); + + QmlDesigner::createObject(*this, qtQmlModelsModuleId, "ListModel", "children", qtObjectId); + QmlDesigner::createObject(*this, qtQmlModelsModuleId, "ListElement", "children", qtObjectId); + + auto itemId = QmlDesigner::createObject(*this, qtQuickModuleId, "Item", "data", qtObjectId); + QmlDesigner::createObject(*this, qtQuickModuleId, "ListView", "data", itemId); +} diff --git a/tests/unit/unittest/projectstoragemock.h b/tests/unit/unittest/projectstoragemock.h index 8a3c2efd153..cb30521c959 100644 --- a/tests/unit/unittest/projectstoragemock.h +++ b/tests/unit/unittest/projectstoragemock.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// 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 @@ -14,6 +14,8 @@ class ProjectStorageMock : public QmlDesigner::ProjectStorageInterface { public: + void setupQtQtuick(); + MOCK_METHOD(void, synchronize, (QmlDesigner::Storage::Synchronization::SynchronizationPackage package), @@ -63,7 +65,6 @@ public: (const, override)); MOCK_METHOD(QmlDesigner::TypeIds, prototypeAndSelfIds, (QmlDesigner::TypeId type), (const, override)); MOCK_METHOD(QmlDesigner::TypeIds, prototypeIds, (QmlDesigner::TypeId type), (const, override)); - MOCK_METHOD(bool, isBasedOn, (QmlDesigner::TypeId typeId), (const, override)); MOCK_METHOD(bool, isBasedOn, (QmlDesigner::TypeId typeId, QmlDesigner::TypeId), (const, override)); MOCK_METHOD(bool, isBasedOn, @@ -158,3 +159,8 @@ public: MOCK_METHOD(std::vector, fetchAllSources, (), ()); }; +class ProjectStorageMockWithQtQtuick : public ProjectStorageMock +{ +public: + ProjectStorageMockWithQtQtuick() { setupQtQtuick(); } +};