/**************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "googletest.h" #include "filesystemmock.h" #include "projectmanagermock.h" #include "projectstoragemock.h" #include "qmldocumentparsermock.h" #include "qmltypesparsermock.h" #include #include #include #include #include namespace { namespace Storage = QmlDesigner::Storage; using QmlDesigner::FileStatus; using QmlDesigner::SourceId; using QmlDesigner::Storage::TypeAccessSemantics; MATCHER_P5(IsStorageType, import, typeName, prototype, accessSemantics, sourceId, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::Type{import, typeName, prototype, accessSemantics, sourceId})) { const Storage::Type &type = arg; return type.import == import && type.typeName == typeName && type.accessSemantics == accessSemantics && type.sourceId == sourceId && Storage::TypeName{prototype} == type.prototype; } MATCHER_P3(IsPropertyDeclaration, name, typeName, traits, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::PropertyDeclaration{name, typeName, traits})) { const Storage::PropertyDeclaration &propertyDeclaration = arg; return propertyDeclaration.name == name && Storage::TypeName{typeName} == propertyDeclaration.typeName && propertyDeclaration.traits == traits; } MATCHER_P(IsExportedType, name, std::string(negation ? "isn't " : "is ") + PrintToString(Storage::ExportedType{name})) { const Storage::ExportedType &type = arg; return type.name == name; } class ProjectStorageUpdater : public testing::Test { public: ProjectStorageUpdater() { ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 2, 421})); ON_CALL(projectStorageMock, fetchSourceDependencieIds(Eq(qmlDirPathSourceId))) .WillByDefault(Return(QmlDesigner::SourceIds{qmltypesPathSourceId, qmltypes2PathSourceId})); QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; ON_CALL(projectManagerMock, qtQmlDirs()).WillByDefault(Return(QStringList{"/path/qmldir"})); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillByDefault(Return(qmldir)); } protected: NiceMock projectManagerMock; NiceMock fileSystemMock; NiceMock projectStorageMock; NiceMock qmlTypesParserMock; NiceMock qmlDocumentParserMock; QmlDesigner::FileStatusCache fileStatusCache{fileSystemMock}; Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; QmlDesigner::ProjectStorage storage{database, database.isInitialized()}; QmlDesigner::SourcePathCache> sourcePathCache{ storage}; QmlDesigner::ProjectUpdater updater{projectManagerMock, fileSystemMock, projectStorageMock, fileStatusCache, sourcePathCache, qmlDocumentParserMock, qmlTypesParserMock}; SourceId objectTypeSourceId{sourcePathCache.sourceId("/path/Object")}; Storage::Type objectType{Storage::Import{"Qml", 2}, "QObject", Storage::NativeType{}, Storage::TypeAccessSemantics::Reference, objectTypeSourceId, {Storage::ExportedType{"Object"}, Storage::ExportedType{"Obj"}}}; SourceId qmltypesPathSourceId = sourcePathCache.sourceId("/path/example.qmltypes"); SourceId qmltypes2PathSourceId = sourcePathCache.sourceId("/path/example2.qmltypes"); SourceId qmlDirPathSourceId = sourcePathCache.sourceId("/path/qmldir"); }; TEST_F(ProjectStorageUpdater, GetContentForQmlDirPathsIfFileStatusIsDifferent) { SourceId qmlDir1PathSourceId = sourcePathCache.sourceId("/path/one/qmldir"); SourceId qmlDir2PathSourceId = sourcePathCache.sourceId("/path/two/qmldir"); SourceId qmlDir3PathSourceId = sourcePathCache.sourceId("/path/three/qmldir"); ON_CALL(projectManagerMock, qtQmlDirs()) .WillByDefault( Return(QStringList{"/path/one/qmldir", "/path/two/qmldir", "/path/three/qmldir"})); ON_CALL(fileSystemMock, fileStatus(_)).WillByDefault([](auto sourceId) { return FileStatus{sourceId, 21, 421}; }); ON_CALL(projectStorageMock, fetchFileStatus(_)).WillByDefault([](auto sourceId) { return FileStatus{sourceId, 2, 421}; }); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDir3PathSourceId))) .WillByDefault(Return(FileStatus{qmlDir3PathSourceId, 21, 421})); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmlDir3PathSourceId))) .WillByDefault(Return(FileStatus{qmlDir3PathSourceId, 21, 421})); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/one/qmldir")))); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/two/qmldir")))); updater.update(); } TEST_F(ProjectStorageUpdater, RequestFileStatusFromFileSystem) { EXPECT_CALL(fileSystemMock, fileStatus(Ne(qmlDirPathSourceId))).Times(AnyNumber()); EXPECT_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId))); updater.update(); } TEST_F(ProjectStorageUpdater, GetContentForQmlTypes) { QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))); updater.update(); } TEST_F(ProjectStorageUpdater, GetContentForQmlTypesIfProjectStorageFileStatusIsInvalid) { QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); ON_CALL(projectStorageMock, fetchFileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{})); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))); updater.update(); } TEST_F(ProjectStorageUpdater, DontGetContentForQmlTypesIfFileSystemFileStatusIsInvalid) { QString qmldir{"module Example\ntypeinfo example.qmltypes\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))).WillByDefault(Return(FileStatus{})); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))).Times(0); updater.update(); } TEST_F(ProjectStorageUpdater, ParseQmlTypes) { QString qmldir{"module Example\ntypeinfo example.qmltypes\ntypeinfo example2.qmltypes\n"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))).WillByDefault(Return(qmldir)); QString qmltypes{"Module {\ndependencies: []}"}; QString qmltypes2{"Module {\ndependencies: [foo]}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example2.qmltypes")))) .WillByDefault(Return(qmltypes2)); EXPECT_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)); EXPECT_CALL(qmlTypesParserMock, parse(qmltypes2, _, _, _)); updater.update(); } TEST_F(ProjectStorageUpdater, DISABLED_SynchronizeIsEmptyForNoChange) { EXPECT_CALL(projectStorageMock, synchronize(IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty())); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeQmlTypes) { auto qmlDirPathSourceId = sourcePathCache.sourceId("/path/qmldir"); auto qmltypesPathSourceId = sourcePathCache.sourceId("/path/example.qmltypes"); QString qmltypes{"Module {\ndependencies: []}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)) .WillByDefault([&](auto, auto &importDependencies, auto &types, auto &sourceIds) { types.push_back(objectType); }); EXPECT_CALL(projectStorageMock, synchronize(_, _, ElementsAre(Eq(objectType)), UnorderedElementsAre(qmlDirPathSourceId, qmltypesPathSourceId), _)); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeQmlTypesAreEmptyIfFileDoesNotChanged) { QString qmltypes{"Module {\ndependencies: []}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/example.qmltypes")))) .WillByDefault(Return(qmltypes)); ON_CALL(qmlTypesParserMock, parse(qmltypes, _, _, _)) .WillByDefault([&](auto, auto &importDependencies, auto &types, auto &sourceIds) { types.push_back(objectType); }); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypesPathSourceId))) .WillByDefault(Return(FileStatus{qmltypesPathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmltypes2PathSourceId))) .WillByDefault(Return(FileStatus{qmltypes2PathSourceId, 2, 421})); ON_CALL(fileSystemMock, fileStatus(Eq(qmlDirPathSourceId))) .WillByDefault(Return(FileStatus{qmlDirPathSourceId, 2, 421})); EXPECT_CALL(projectStorageMock, synchronize(IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty(), IsEmpty())); updater.update(); } TEST_F(ProjectStorageUpdater, GetContentForQmlDocuments) { QString qmldir{"module Example\nFirstType 1.0 First.qml\nFirstTypeV2 2.2 " "First.2.qml\nSecondType 2.1 OldSecond.qml\nSecondType 2.2 Second.qml\n"}; EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))) .WillRepeatedly(Return(qmldir)); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.qml")))); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.2.qml")))); EXPECT_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/Second.qml")))); updater.update(); } TEST_F(ProjectStorageUpdater, ParseQmlDocuments) { QString qmldir{"module Example\nFirstType 1.0 First.qml\nFirstTypeV2 2.2 " "First.2.qml\nSecondType 2.1 OldSecond.qml\nSecondType 2.2 Second.qml\n"}; QString qmlDocument1{"First{}"}; QString qmlDocument2{"Second{}"}; QString qmlDocument3{"Third{}"}; ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))).WillByDefault(Return(qmldir)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.qml")))) .WillByDefault(Return(qmlDocument1)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.2.qml")))) .WillByDefault(Return(qmlDocument2)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/Second.qml")))) .WillByDefault(Return(qmlDocument3)); EXPECT_CALL(qmlDocumentParserMock, parse(qmlDocument1)); EXPECT_CALL(qmlDocumentParserMock, parse(qmlDocument2)); EXPECT_CALL(qmlDocumentParserMock, parse(qmlDocument3)); updater.update(); } TEST_F(ProjectStorageUpdater, SynchronizeQmlDocuments) { QString qmldir{"module Example\nFirstType 1.0 First.qml\nFirstTypeV2 2.2 " "First.2.qml\nSecondType 2.1 OldSecond.qml\nSecondType 2.2 Second.qml\n"}; QString qmlDocument1{"First{}"}; QString qmlDocument2{"Second{}"}; QString qmlDocument3{"Third{}"}; auto qmlDocumentSourceId1 = sourcePathCache.sourceId("/path/First.qml"); auto qmlDocumentSourceId2 = sourcePathCache.sourceId("/path/First.2.qml"); auto qmlDocumentSourceId3 = sourcePathCache.sourceId("/path/Second.qml"); Storage::Type firstType; firstType.prototype = Storage::ExportedType{"Object"}; Storage::Type secondType; secondType.prototype = Storage::ExportedType{"Object2"}; Storage::Type thirdType; thirdType.prototype = Storage::ExportedType{"Object3"}; auto firstQmlDocumentSourceId = sourcePathCache.sourceId("/path/First.qml"); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/qmldir")))).WillByDefault(Return(qmldir)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.qml")))) .WillByDefault(Return(qmlDocument1)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/First.2.qml")))) .WillByDefault(Return(qmlDocument2)); ON_CALL(fileSystemMock, contentAsQString(Eq(QString("/path/Second.qml")))) .WillByDefault(Return(qmlDocument3)); ON_CALL(qmlDocumentParserMock, parse(qmlDocument1)).WillByDefault(Return(firstType)); ON_CALL(qmlDocumentParserMock, parse(qmlDocument2)).WillByDefault(Return(secondType)); ON_CALL(qmlDocumentParserMock, parse(qmlDocument3)).WillByDefault(Return(thirdType)); EXPECT_CALL(projectStorageMock, synchronize(_, _, Contains(AllOf(IsStorageType(Storage::Import{"Example", 1}, "First.qml", Storage::ExportedType{"Object"}, TypeAccessSemantics::Reference, firstQmlDocumentSourceId), Field(&Storage::Type::exportedTypes, ElementsAre(IsExportedType("FirstType"))))), UnorderedElementsAre(qmlDirPathSourceId, qmlDocumentSourceId1, qmlDocumentSourceId2, qmlDocumentSourceId3), _)); updater.update(); } } // namespace