diff --git a/src/plugins/qmldesigner/designercore/include/projectstorageids.h b/src/plugins/qmldesigner/designercore/include/projectstorageids.h index f259478a38d..140ed6f234e 100644 --- a/src/plugins/qmldesigner/designercore/include/projectstorageids.h +++ b/src/plugins/qmldesigner/designercore/include/projectstorageids.h @@ -82,7 +82,8 @@ enum class BasicIdType { StorageCacheIndex, FunctionDeclaration, SignalDeclaration, - EnumerationDeclaration + EnumerationDeclaration, + Import }; using TypeId = BasicId; @@ -106,4 +107,7 @@ using SourceContextIds = std::vector; using SourceId = BasicId; using SourceIds = std::vector; +using ImportId = BasicId; +using ImportIds = std::vector; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h index 22306e8c0db..a7aed8d9400 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorage.h @@ -75,6 +75,16 @@ public: transaction.commit(); } + void synchronizeImports(Storage::Imports imports) + { + Sqlite::ImmediateTransaction transaction{database}; + + synchronizeImportsAndUpdatesImportIds(imports); + synchronizeImportDependencies(createSortedImportDependecies(imports)); + + transaction.commit(); + } + TypeId upsertType(Utils::SmallStringView name, TypeId prototypeId, Storage::TypeAccessSemantics accessSemantics, @@ -255,7 +265,149 @@ public: return writeSourceId(sourceContextId, sourceName); } + auto fetchAllImports() const + { + Storage::Imports imports; + imports.reserve(128); + + auto callback = [&](Utils::SmallStringView name, int version, int sourceId, long long importId) { + auto &lastImport = imports.emplace_back(name, + Storage::VersionNumber{version}, + SourceId{sourceId}); + + lastImport.importDependencies = selectImportsForThatDependentOnThisImportIdStatement + .template values(6, importId); + + return Sqlite::CallbackControl::Continue; + }; + + selectAllImportsStatement.readCallbackWithTransaction(callback); + + return imports; + } + private: + struct ImportDependency + { + ImportDependency(ImportId id, ImportId dependencyId) + : id{id} + , dependencyId{dependencyId} + {} + + ImportDependency(long long id, long long dependencyId) + : id{id} + , dependencyId{dependencyId} + {} + + ImportId id; + ImportId dependencyId; + + friend bool operator<(ImportDependency first, ImportDependency second) + { + return std::tie(first.id, first.dependencyId) < std::tie(second.id, second.dependencyId); + } + + friend bool operator==(ImportDependency first, ImportDependency second) + { + return first.id == second.id && first.dependencyId == second.dependencyId; + } + }; + + void synchronizeImportsAndUpdatesImportIds(Storage::Imports &imports) + { + auto compareKey = [](auto &&first, auto &&second) { + auto nameCompare = Sqlite::compare(first.name, second.name); + + if (nameCompare != 0) + return nameCompare; + + return first.version.version - second.version.version; + }; + + std::sort(imports.begin(), imports.end(), [&](auto &&first, auto &&second) { + return compareKey(first, second) < 0; + }); + + auto range = selectAllImportsStatement.template range(); + + auto insert = [&](Storage::Import &import) { + import.importId = insertImportStatement.template value(import.name, + import.version.version, + &import.sourceId); + }; + + auto update = [&](const Storage::ImportView &importView, Storage::Import &import) { + if (importView.sourceId.id != import.sourceId.id) + updateImportStatement.write(&importView.importId, &import.sourceId); + import.importId = importView.importId; + }; + + auto remove = [&](const Storage::ImportView &importView) { + deleteImportStatement.write(&importView.importId); + }; + + Sqlite::insertUpdateDelete(range, imports, compareKey, insert, update, remove); + } + + std::vector createSortedImportDependecies(const Storage::Imports &imports) const + { + std::vector importDependecies; + importDependecies.reserve(imports.size() * 5); + + for (const Storage::Import &import : imports) { + for (const Storage::BasicImport &importDependency : import.importDependencies) { + auto importIdForDependency = fetchImportId(importDependency); + + if (!importIdForDependency) + throw ImportDoesNotExists{}; + + importDependecies.emplace_back(import.importId, importIdForDependency); + } + } + + std::sort(importDependecies.begin(), importDependecies.end()); + importDependecies.erase(std::unique(importDependecies.begin(), importDependecies.end()), + importDependecies.end()); + + return importDependecies; + } + + void synchronizeImportDependencies(const std::vector &importDependecies) + { + auto compareKey = [](ImportDependency first, ImportDependency second) { + auto idCompare = first.id.id - second.id.id; + + if (idCompare != 0) + return idCompare; + + return first.dependencyId.id - second.dependencyId.id; + }; + + auto range = selectAllImportDependenciesStatement.template range(); + + auto insert = [&](ImportDependency dependency) { + insertImportDependencyStatement.write(&dependency.id, &dependency.dependencyId); + }; + + auto update = [](ImportDependency, ImportDependency) {}; + + auto remove = [&](ImportDependency dependency) { + deleteImportDependencyStatement.write(&dependency.id, &dependency.dependencyId); + }; + + Sqlite::insertUpdateDelete(range, importDependecies, compareKey, insert, update, remove); + } + + ImportId fetchImportId(const Storage::BasicImport &import) const + { + if (import.version) { + return selectImportIdByNameAndVersionStatement + .template value(import.name, import.version.version); + } + + return selectImportIdByNameStatement.template value(import.name); + } + void deleteNotUpdatedTypes(const TypeIds &updatedTypeIds, const SourceIds &sourceIds) { auto updatedTypeIdValues = Utils::transform(updatedTypeIds, [](TypeId typeId) { @@ -638,6 +790,8 @@ private: if (!isInitialized) { Sqlite::ExclusiveTransaction transaction{database}; + createImportsTable(database); + createImportDependeciesTable(database); createSourceContextsTable(database); createSourcesTable(database); createTypesTable(database); @@ -791,6 +945,35 @@ private: table.initialize(database); } + + void createImportsTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("imports"); + table.addColumn("importId", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + auto &nameColumn = table.addColumn("name"); + auto &versionColumn = table.addColumn("version"); + table.addColumn("sourceId"); + + table.addUniqueIndex({nameColumn, versionColumn}); + + table.initialize(database); + } + + void createImportDependeciesTable(Database &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setUseWithoutRowId(true); + table.setName("importDependencies"); + auto &importIdColumn = table.addColumn("importId"); + auto &parentImportIdColumn = table.addColumn("parentImportId"); + + table.addPrimaryKeyContraint({importIdColumn, parentImportIdColumn}); + + table.initialize(database); + } }; public: @@ -974,6 +1157,28 @@ public: database}; WriteStatement deleteEnumerationDeclarationStatement{ "DELETE FROM enumerationDeclarations WHERE enumerationDeclarationId=?", database}; + mutable ReadWriteStatement<1> insertImportStatement{ + "INSERT INTO imports(name, version, sourceId) VALUES(?1, ?2, ?3) RETURNING importId", + database}; + WriteStatement updateImportStatement{"UPDATE imports SET sourceId=?2 WHERE importId=?1", database}; + WriteStatement deleteImportStatement{"DELETE FROM imports WHERE importId=?", database}; + mutable ReadStatement<1> selectImportIdByNameStatement{ + "SELECT importId FROM imports WHERE name=? ORDER BY version DESC LIMIT 1", database}; + mutable ReadStatement<1> selectImportIdByNameAndVersionStatement{ + "SELECT importId FROM imports WHERE name=? AND version=?", database}; + mutable ReadStatement<4> selectAllImportsStatement{ + "SELECT name, version, sourceId, importId FROM imports ORDER BY name, version", database}; + WriteStatement insertImportDependencyStatement{ + "INSERT INTO importDependencies(importId, parentImportId) VALUES(?1, ?2)", database}; + WriteStatement deleteImportDependencyStatement{ + "DELETE FROM importDependencies WHERE importId=?1 AND parentImportId=?2", database}; + mutable ReadStatement<2> selectAllImportDependenciesStatement{ + "SELECT importId, parentImportId FROM importDependencies ORDER BY importId, parentImportId", + database}; + mutable ReadStatement<2> selectImportsForThatDependentOnThisImportIdStatement{ + "SELECT name, version FROM importDependencies JOIN imports ON " + "importDependencies.parentImportId = imports.importId WHERE importDependencies.importId=?", + database}; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h index 5204512c9e4..526e107a616 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstorageexceptions.h @@ -71,4 +71,10 @@ public: const char *what() const noexcept override { return "The source id is invalid!"; } }; +class ImportDoesNotExists : std::exception +{ +public: + const char *what() const noexcept override { return "The simport does not exist!"; } +}; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h index 6e862a0286b..8486fe97285 100644 --- a/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h +++ b/src/plugins/qmldesigner/designercore/projectstorage/projectstoragetypes.h @@ -60,7 +60,7 @@ public: : version{version} {} - explicit operator bool() { return version >= 0; } + explicit operator bool() const { return version >= 0; } friend bool operator==(VersionNumber first, VersionNumber second) noexcept { @@ -432,4 +432,84 @@ public: using Types = std::vector; +class BasicImport +{ +public: + explicit BasicImport(Utils::SmallStringView name, VersionNumber version = VersionNumber{}) + : name{name} + , version{version} + {} + + explicit BasicImport(Utils::SmallStringView name, int version) + : name{name} + , version{version} + {} + + friend bool operator==(const BasicImport &first, const BasicImport &second) + { + return first.name == second.name && first.version == second.version; + } + +public: + Utils::PathString name; + VersionNumber version; +}; + +using BasicImports = std::vector; + +class Import : public BasicImport +{ +public: + explicit Import(Utils::SmallStringView name, + VersionNumber version = VersionNumber{}, + SourceId sourceId = SourceId{}, + BasicImports importDependencies = {}) + : BasicImport(name, version) + , importDependencies{std::move(importDependencies)} + , sourceId{sourceId} + {} + + explicit Import(Utils::SmallStringView name, int version, int sourceId) + : BasicImport(name, version) + , sourceId{sourceId} + {} + + friend bool operator==(const Import &first, const Import &second) + { + return static_cast(first) == static_cast(second) + && first.sourceId == second.sourceId + && first.importDependencies == second.importDependencies; + } + +public: + BasicImports importDependencies; + SourceId sourceId; + ImportId importId; +}; + +using Imports = std::vector; + +class ImportView +{ +public: + explicit ImportView(Utils::SmallStringView name, int version, int sourceId, long long importId) + : name{name} + , version{version} + , sourceId{sourceId} + , importId{importId} + {} + + friend bool operator==(const ImportView &first, const ImportView &second) + { + return first.name == second.name + && first.version == second.version & first.sourceId == second.sourceId; + } + +public: + Utils::SmallStringView name; + VersionNumber version; + SourceId sourceId; + ImportId importId; +}; + } // namespace QmlDesigner::Storage diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 777dccedc53..2ffcd4a6e8a 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -1732,6 +1732,17 @@ std::ostream &operator<<(std::ostream &out, const EnumerationDeclaration &enumer << enumerationDeclaration.enumeratorDeclarations << ")"; } +std::ostream &operator<<(std::ostream &out, const BasicImport &import) +{ + return out << "(" << import.name << ", " << import.version << ")"; +} + +std::ostream &operator<<(std::ostream &out, const Import &import) +{ + return out << "(" << import.name << ", " << import.version << ", " << import.sourceId << ", " + << import.importDependencies << ")"; +} + } // namespace Storage namespace Internal { diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index 3a754ee899d..6fe7c8e75d0 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -407,6 +407,8 @@ class ParameterDeclaration; class SignalDeclaration; class EnumerationDeclaration; class EnumeratorDeclaration; +class BasicImport; +class Import; std::ostream &operator<<(std::ostream &out, TypeAccessSemantics accessSemantics); std::ostream &operator<<(std::ostream &out, VersionNumber versionNumber); @@ -420,6 +422,8 @@ std::ostream &operator<<(std::ostream &out, const ParameterDeclaration ¶mete std::ostream &operator<<(std::ostream &out, const SignalDeclaration &signalDeclaration); std::ostream &operator<<(std::ostream &out, const EnumerationDeclaration &enumerationDeclaration); std::ostream &operator<<(std::ostream &out, const EnumeratorDeclaration &enumeratorDeclaration); +std::ostream &operator<<(std::ostream &out, const BasicImport &import); +std::ostream &operator<<(std::ostream &out, const Import &import); } // namespace Storage diff --git a/tests/unit/unittest/projectstorage-test.cpp b/tests/unit/unittest/projectstorage-test.cpp index 7549256b919..e7209f597b6 100644 --- a/tests/unit/unittest/projectstorage-test.cpp +++ b/tests/unit/unittest/projectstorage-test.cpp @@ -119,6 +119,28 @@ MATCHER_P3(IsPropertyDeclaration, && propertyDeclaration.traits == traits; } +MATCHER_P2(IsBasicImport, + name, + version, + std::string(negation ? "isn't " : "is ") + PrintToString(Storage::Import{name, version})) +{ + const Storage::BasicImport &import = arg; + + return import.name == name && import.version == version; +} + +MATCHER_P3(IsImport, + name, + version, + sourceId, + std::string(negation ? "isn't " : "is ") + + PrintToString(Storage::Import{name, version, sourceId})) +{ + const Storage::Import &import = arg; + + return import.name == name && import.version == version && &import.sourceId == &sourceId; +} + class ProjectStorage : public testing::Test { public: @@ -528,6 +550,24 @@ protected: Storage::ExportedType{"Qml.Object", Storage::Version{5, 1}}}}}; } + auto createImports() + { + importSourceId1 = sourcePathCache.sourceId(importPath1); + importSourceId2 = sourcePathCache.sourceId(importPath2); + importSourceId3 = sourcePathCache.sourceId(importPath3); + + return Storage::Imports{Storage::Import{"Qml", Storage::VersionNumber{2}, importSourceId1, {}}, + Storage::Import{"QtQuick", + Storage::VersionNumber{}, + importSourceId2, + {Storage::Import{"Qml", Storage::VersionNumber{2}}}}, + Storage::Import{"/path/to", + Storage::VersionNumber{}, + SourceId{}, + {Storage::Import{"QtQuick"}, + Storage::Import{"Qml", Storage::VersionNumber{2}}}}}; + } + protected: using ProjectStorage = QmlDesigner::ProjectStorage; Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; @@ -541,6 +581,12 @@ protected: SourceId sourceId2; SourceId sourceId3; SourceId sourceId4; + QmlDesigner::SourcePathView importPath1{"/import/path1/to"}; + QmlDesigner::SourcePathView importPath2{"/import/path2/to"}; + QmlDesigner::SourcePathView importPath3{"/import/aaaa/to"}; + SourceId importSourceId1; + SourceId importSourceId2; + SourceId importSourceId3; }; TEST_F(ProjectStorageSlowTest, FetchTypeIdByName) @@ -1953,4 +1999,273 @@ TEST_F(ProjectStorageSlowTest, SynchronizeTypesAddEnumerationDeclaration) Eq(types[0].enumerationDeclarations[2])))))); } +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddImports) +{ + Storage::Imports imports{createImports()}; + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + IsImport("/path/to", Storage::VersionNumber{}, SourceId{}))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddImportsAgain) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + IsImport("/path/to", Storage::VersionNumber{}, SourceId{}))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddMoreImports) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.push_back(Storage::Import{"QtQuick.Foo", Storage::VersionNumber{1}, importSourceId3}); + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + IsImport("QtQuick.Foo", Storage::VersionNumber{1}, importSourceId3))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddSameImportNameButDifferentVersion) +{ + Storage::Imports imports{createImports()}; + imports.push_back(Storage::Import{"Qml", Storage::VersionNumber{4}, importSourceId3}); + storage.synchronizeImports(imports); + imports.pop_back(); + imports.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}, importSourceId3}); + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + IsImport("Qml", Storage::VersionNumber{3}, importSourceId3), + IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + IsImport("/path/to", Storage::VersionNumber{}, SourceId{}))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsRemoveImport) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.pop_back(); + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsUpdateImport) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports[1].sourceId = importSourceId3; + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + IsImport("QtQuick", Storage::VersionNumber{}, importSourceId3), + IsImport("/path/to", Storage::VersionNumber{}, SourceId{}))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddImportDependecies) +{ + Storage::Imports imports{createImports()}; + + storage.synchronizeImports(imports); + + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + ElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("QtQuick", + Storage::VersionNumber{})))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddImportDependeciesWhichDoesNotExitsThrows) +{ + Storage::Imports imports{createImports()}; + imports[1].importDependencies.push_back(Storage::Import{"QmlBase", Storage::VersionNumber{2}}); + + ASSERT_THROW(storage.synchronizeImports(imports), QmlDesigner::ImportDoesNotExists); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsRemovesDependeciesForRemovedImports) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + auto last = imports.back(); + imports.pop_back(); + + storage.synchronizeImports(imports); + + last.importDependencies.pop_back(); + imports.push_back(last); + storage.synchronizeImports(imports); + ASSERT_THAT(storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + ElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre( + IsBasicImport("QtQuick", Storage::VersionNumber{})))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddMoreImportDependecies) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.push_back(Storage::Import{"QmlBase", Storage::VersionNumber{2}, importSourceId1, {}}); + imports[1].importDependencies.push_back(Storage::Import{"QmlBase", Storage::VersionNumber{2}}); + + storage.synchronizeImports(imports); + + ASSERT_THAT( + storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QmlBase", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("QmlBase", Storage::VersionNumber{2})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("QtQuick", Storage::VersionNumber{})))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsAddMoreImportDependeciesWithDifferentVersionNumber) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}, importSourceId1, {}}); + imports[1].importDependencies.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}}); + + storage.synchronizeImports(imports); + + ASSERT_THAT( + storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("Qml", Storage::VersionNumber{3}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("Qml", Storage::VersionNumber{3})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("QtQuick", Storage::VersionNumber{})))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsDependencyGetsHighestVersionIfNoVersionIsSupplied) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}, importSourceId1, {}}); + imports[1].importDependencies.push_back(Storage::Import{"Qml"}); + + storage.synchronizeImports(imports); + + ASSERT_THAT( + storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("Qml", Storage::VersionNumber{3}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("Qml", Storage::VersionNumber{3})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("QtQuick", Storage::VersionNumber{})))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsDependencyGetsOnlyTheHighestDependency) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.push_back(Storage::Import{"Qml", Storage::VersionNumber{1}, importSourceId1, {}}); + imports[1].importDependencies.push_back(Storage::Import{"Qml"}); + + storage.synchronizeImports(imports); + + ASSERT_THAT( + storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("Qml", Storage::VersionNumber{1}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("QtQuick", Storage::VersionNumber{})))))); +} + +TEST_F(ProjectStorageSlowTest, SynchronizeImportsDependencyRemoveDuplicateDependencies) +{ + Storage::Imports imports{createImports()}; + storage.synchronizeImports(imports); + imports.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}, importSourceId1, {}}); + imports[2].importDependencies.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}}); + imports[2].importDependencies.push_back(Storage::Import{"Qml", Storage::VersionNumber{2}}); + imports[2].importDependencies.push_back(Storage::Import{"Qml", Storage::VersionNumber{3}}); + imports[2].importDependencies.push_back(Storage::Import{"Qml", Storage::VersionNumber{2}}); + + storage.synchronizeImports(imports); + + ASSERT_THAT( + storage.fetchAllImports(), + UnorderedElementsAre( + AllOf(IsImport("Qml", Storage::VersionNumber{2}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("Qml", Storage::VersionNumber{3}, importSourceId1), + Field(&Storage::Import::importDependencies, IsEmpty())), + AllOf(IsImport("QtQuick", Storage::VersionNumber{}, importSourceId2), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2})))), + AllOf(IsImport("/path/to", Storage::VersionNumber{}, SourceId{}), + Field(&Storage::Import::importDependencies, + UnorderedElementsAre(IsBasicImport("Qml", Storage::VersionNumber{2}), + IsBasicImport("Qml", Storage::VersionNumber{3}), + IsBasicImport("QtQuick", Storage::VersionNumber{})))))); +} + } // namespace