From e7a013cbfc836df84f12a8f039ec3951703acd54 Mon Sep 17 00:00:00 2001 From: Marco Bubke Date: Mon, 11 Jul 2022 15:43:59 +0200 Subject: [PATCH] QmlDesiger: ProjectStorage provides properties for typeId It returns all properties of the type and the prototype chain in a sorted order. Task-nubmer: QDS-7276 Change-Id: I95c16abd16d9d0f1fdf68d3425780a888dc056b7 Reviewed-by: Tim Jenssen --- .../projectstorage/projectstorage.h | 24 ++- tests/unit/unittest/projectstorage-test.cpp | 152 ++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index f3af0e6d062..1578a7f9537 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -149,6 +149,18 @@ public: .template valueWithTransaction(&moduleId, exportedTypeName); } + PropertyDeclarationIds propertyIds(TypeId typeId) const + { + return selectPropertyDeclarationIdsForTypeStatement + .template valuesWithTransaction(32, &typeId); + } + + Utils::optional propertyName(PropertyDeclarationId propertyDeclarationId) const + { + return selectPropertyNameStatement.template optionalValueWithTransaction( + &propertyDeclarationId); + } + PropertyDeclarationId fetchPropertyDeclarationByTypeIdAndName(TypeId typeId, Utils::SmallStringView name) { @@ -2658,7 +2670,7 @@ public: "aliasPropertyDeclarationId IS NULL RETURNING typeId, propertyDeclarationId, " "propertyImportedTypeNameId", database}; - ReadStatement<1, 1> selectPropertyNameStatement{ + mutable ReadStatement<1, 1> selectPropertyNameStatement{ "SELECT name FROM propertyDeclarations WHERE propertyDeclarationId=?", database}; WriteStatement<2> updatePropertyDeclarationTypeStatement{ "UPDATE propertyDeclarations SET propertyTypeId=?2 WHERE propertyDeclarationId=?1", database}; @@ -2845,6 +2857,16 @@ public: " moduleExportedImportId " "FROM imports", database}; + mutable ReadStatement<1, 1> selectPropertyDeclarationIdsForTypeStatement{ + "WITH RECURSIVE " + " typeChain(typeId) AS (" + " VALUES(?1)" + " UNION ALL " + " SELECT prototypeId FROM types JOIN typeChain " + " USING(typeId) WHERE prototypeId IS NOT NULL)" + "SELECT propertyDeclarationId FROM typeChain JOIN propertyDeclarations " + " USING(typeId) ORDER BY propertyDeclarationId", + database}; }; extern template class ProjectStorage; } // namespace QmlDesigner diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index b4f83ef0d1e..4c8c7eb6074 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -176,6 +176,51 @@ MATCHER_P4(IsPropertyDeclaration, && propertyDeclaration.traits == traits; } +class HasNameMatcher +{ +public: + using is_gtest_matcher = void; + + HasNameMatcher(const QmlDesigner::ProjectStorage &storage, + Utils::SmallStringView name) + : storage{storage} + , name{name} + {} + + bool MatchAndExplain(QmlDesigner::PropertyDeclarationId id, std::ostream *listener) const + { + auto propertyName = storage.propertyName(id); + bool success = propertyName && *propertyName == name; + + if (success) + return true; + + if (listener) { + if (propertyName) + *listener << "name is '" << *propertyName << "', not '" << name << "'"; + else + *listener << "there is no '" << name << "'"; + } + + return false; + } + + void DescribeTo(std::ostream *os) const { *os << "is '" << name << "'"; } + + void DescribeNegationTo(std::ostream *os) const { *os << "is not '" << name << "'"; } + +private: + const QmlDesigner::ProjectStorage &storage; + Utils::SmallStringView name; +}; + +#define HasName(name) Matcher(HasNameMatcher{storage, name}) + +MATCHER(IsSorted, std::string(negation ? "isn't sorted" : "is sorted")) +{ + return std::is_sorted(begin(arg), end(arg)); +} + class ProjectStorage : public testing::Test { protected: @@ -577,6 +622,59 @@ protected: return package; } + auto createPackageWithProperties() + { + SynchronizationPackage package; + + package.imports.emplace_back(qmlModuleId, Storage::Version{}, sourceId1); + + package.types.push_back(Storage::Type{ + "QObject", + Storage::ImportedType{}, + TypeAccessSemantics::Reference, + sourceId1, + {Storage::ExportedType{qmlModuleId, "Object", Storage::Version{}}}, + {Storage::PropertyDeclaration{"data", + Storage::ImportedType{"Object"}, + Storage::PropertyDeclarationTraits::IsList}, + Storage::PropertyDeclaration{"children", + Storage::ImportedType{"Object"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly}}}); + package.types.push_back(Storage::Type{ + "QObject2", + Storage::ImportedType{"Object"}, + TypeAccessSemantics::Reference, + sourceId1, + {Storage::ExportedType{qmlModuleId, "Object2", Storage::Version{}}}, + {Storage::PropertyDeclaration{"data2", + Storage::ImportedType{"Object3"}, + Storage::PropertyDeclarationTraits::IsList}, + Storage::PropertyDeclaration{"children2", + Storage::ImportedType{"Object3"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly}}}); + package.types.push_back(Storage::Type{ + "QObject3", + Storage::ImportedType{"Object2"}, + TypeAccessSemantics::Reference, + sourceId1, + {Storage::ExportedType{qmlModuleId, "Object3", Storage::Version{}}}, + {Storage::PropertyDeclaration{"data3", + Storage::ImportedType{"Object2"}, + Storage::PropertyDeclarationTraits::IsList}, + Storage::PropertyDeclaration{"children3", + Storage::ImportedType{"Object2"}, + Storage::PropertyDeclarationTraits::IsList + | Storage::PropertyDeclarationTraits::IsReadOnly}}}); + + package.updatedSourceIds.push_back(sourceId1); + + shuffle(package.types); + + return package; + } + auto createModuleExportedImportSynchronizationPackage() { SynchronizationPackage package; @@ -5047,4 +5145,58 @@ TEST_F(ProjectStorage, GetNoTypeIdWithCompleteVersionForWrongMajorVersion) ASSERT_FALSE(typeId); } +TEST_F(ProjectStorage, GetProperties) +{ + auto package{createPackageWithProperties()}; + storage.synchronize(package); + auto itemTypeId = fetchTypeId(sourceId1, "QObject3"); + + auto propertyIds = storage.propertyIds(itemTypeId); + + ASSERT_THAT(propertyIds, + UnorderedElementsAre(HasName("data"), + HasName("children"), + HasName("data2"), + HasName("children2"), + HasName("data3"), + HasName("children3"))); +} + +TEST_F(ProjectStorage, GetPropertiesAreReturnedSorted) +{ + auto package{createPackageWithProperties()}; + storage.synchronize(package); + auto itemTypeId = fetchTypeId(sourceId1, "QObject3"); + + auto propertyIds = storage.propertyIds(itemTypeId); + + ASSERT_THAT(propertyIds, IsSorted()); +} + +TEST_F(ProjectStorage, GetNoPropertiesPropertiesFromDerivedTypes) +{ + auto package{createPackageWithProperties()}; + storage.synchronize(package); + auto itemTypeId = fetchTypeId(sourceId1, "QObject2"); + + auto propertyIds = storage.propertyIds(itemTypeId); + + ASSERT_THAT(propertyIds, + UnorderedElementsAre(HasName("data"), + HasName("children"), + HasName("data2"), + HasName("children2"))); +} + +TEST_F(ProjectStorage, GetNoPropertiesForWrongTypeId) +{ + auto package{createPackageWithProperties()}; + storage.synchronize(package); + auto itemTypeId = fetchTypeId(sourceId1, "WrongObject"); + + auto propertyIds = storage.propertyIds(itemTypeId); + + ASSERT_THAT(propertyIds, IsEmpty()); +} + } // namespace