QmlDesigner: Add persistent auxiliary storage

The persistent storage is saving the data in-between program executions.
A sqlite database is providing the backend. The model nodes need an id
to be identified.

Change-Id: I24e4ea5184c04cb6a9e3828059ca593ee41d271e
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Marco Bubke
2024-06-12 18:32:23 +02:00
parent 9c1e834eaf
commit 99e5042732
20 changed files with 635 additions and 26 deletions

View File

@@ -20,7 +20,8 @@ enum class AuxiliaryDataType {
Temporary,
Document,
NodeInstancePropertyOverwrite,
NodeInstanceAuxiliary
NodeInstanceAuxiliary,
Persistent
};
enum class View3DActionType {

View File

@@ -361,6 +361,7 @@ extend_qtc_library(QmlDesignerCore
abstractview.cpp
anchorline.cpp
annotation.cpp
auxiliarypropertystorageview.cpp auxiliarypropertystorageview.h
bindingproperty.cpp
componenttextmodifier.cpp
documentmessage.cpp

View File

@@ -20,17 +20,19 @@
#include <itemlibraryview.h>
#include <materialbrowserview.h>
#include <materialeditorview.h>
#include <model/auxiliarypropertystorageview.h>
#include <navigatorview.h>
#include <nodeinstanceview.h>
#include <propertyeditorview.h>
#include <qmldesignerplugin.h>
#include <rewriterview.h>
#include <stateseditorview.h>
#include <texteditorview.h>
#include <textureeditorview.h>
#include <qmldesignerplugin.h>
#include <coreplugin/icore.h>
#include <sqlitedatabase.h>
#include <utils/algorithm.h>
#include <advanceddockingsystem/dockwidget.h>
@@ -49,6 +51,7 @@ public:
ViewManagerData(AsynchronousImageCache &imageCache,
ExternalDependenciesInterface &externalDependencies)
: debugView{externalDependencies}
, auxiliaryDataKeyView{auxiliaryDataDatabase, externalDependencies}
, designerActionManagerView{externalDependencies}
, nodeInstanceView(QCoreApplication::arguments().contains("-capture-puppet-stream")
? capturingConnectionManager
@@ -78,6 +81,11 @@ public:
CapturingConnectionManager capturingConnectionManager;
QmlModelState savedState;
Internal::DebugView debugView;
Sqlite::Database auxiliaryDataDatabase{
Utils::PathString{Core::ICore::userResourcePath("auxiliary_data.db").toString()},
Sqlite::JournalMode::Wal,
Sqlite::LockingMode::Normal};
AuxiliaryPropertyStorageView auxiliaryDataKeyView;
DesignerActionManagerView designerActionManagerView;
NodeInstanceView nodeInstanceView;
ContentLibraryView contentLibraryView;
@@ -201,7 +209,8 @@ QList<AbstractView *> ViewManager::views() const
QList<AbstractView *> ViewManager::standardViews() const
{
#ifndef QTC_USE_QML_DESIGNER_LITE
QList<AbstractView *> list = {&d->edit3DView,
QList<AbstractView *> list = {&d->auxiliaryDataKeyView,
&d->edit3DView,
&d->formEditorView,
&d->textEditorView,
&d->assetsLibraryView,

View File

@@ -244,6 +244,9 @@ QTextStream &operator<<(QTextStream &stream, AuxiliaryDataType type)
case AuxiliaryDataType::Temporary:
stream << "Temporary";
break;
case AuxiliaryDataType::Persistent:
stream << "Persistent";
break;
}
return stream;

View File

@@ -249,6 +249,7 @@ public:
const QList<DocumentMessage> &warnings);
QList<ModelNode> selectedNodes(AbstractView *view) const;
void setSelectedModelNodes(const QList<ModelNode> &selectedNodeList);
void clearMetaInfoCache();

View File

@@ -152,10 +152,10 @@ public:
void destroy();
QString id() const;
void ensureIdExists();
[[nodiscard]] QString validId();
void setIdWithRefactoring(const QString &id);
void setIdWithoutRefactoring(const QString &id);
void ensureIdExists() const;
[[nodiscard]] QString validId() const;
void setIdWithRefactoring(const QString &id) const;
void setIdWithoutRefactoring(const QString &id) const;
static bool isValidId(const QString &id);
static QString getIdValidityErrorMessage(const QString &id);
@@ -192,6 +192,7 @@ public:
void removeAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView name) const;
bool hasAuxiliaryData(AuxiliaryDataKeyView key) const;
bool hasAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView name) const;
bool hasAuxiliaryData(AuxiliaryDataType type) const;
AuxiliaryDatasForType auxiliaryData(AuxiliaryDataType type) const;
AuxiliaryDatasView auxiliaryData() const;

View File

@@ -427,14 +427,7 @@ QList<Internal::InternalNode::Pointer> toInternalNodeList(const QList<ModelNode>
*/
void AbstractView::setSelectedModelNodes(const QList<ModelNode> &selectedNodeList)
{
QList<ModelNode> unlockedNodes;
for (const auto &modelNode : selectedNodeList) {
if (!ModelUtils::isThisOrAncestorLocked(modelNode))
unlockedNodes.push_back(modelNode);
}
model()->d->setSelectedNodes(toInternalNodeList(unlockedNodes));
model()->setSelectedModelNodes(selectedNodeList);
}
void AbstractView::setSelectedModelNode(const ModelNode &modelNode)

View File

@@ -0,0 +1,201 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "auxiliarypropertystorageview.h"
#include <sqlitedatabase.h>
#include <QDataStream>
#include <QIODevice>
namespace QmlDesigner {
namespace {
struct Initializer
{
Initializer(Sqlite::Database &database, bool isInitialized)
{
if (!isInitialized) {
createAuxiliaryPropertiesTable(database);
}
database.setIsInitialized(true);
}
void createAuxiliaryPropertiesTable(Sqlite::Database &database)
{
Sqlite::StrictTable table;
table.setUseIfNotExists(true);
table.setUseWithoutRowId(true);
table.setName("auxiliaryProperties");
auto &filePathColumn = table.addColumn("filePath", Sqlite::StrictColumnType::Text);
auto &idColumn = table.addColumn("id", Sqlite::StrictColumnType::Text);
auto &keyColumn = table.addColumn("key", Sqlite::StrictColumnType::Text);
table.addColumn("value", Sqlite::StrictColumnType::Blob);
table.addPrimaryKeyContraint({filePathColumn, idColumn, keyColumn});
table.initialize(database);
}
};
struct Entry
{
Entry(Utils::SmallStringView nodeId, Utils::SmallStringView key, Sqlite::BlobView value)
: nodeId{nodeId}
, key{key}
, value{value}
{}
Utils::SmallStringView nodeId;
Utils::SmallStringView key;
Sqlite::BlobView value;
};
struct ChangeEntry
{
ChangeEntry(Utils::SmallStringView id, Utils::SmallStringView name)
: id{id}
, name{name}
{}
friend bool operator<(const ChangeEntry &first, const ChangeEntry &second)
{
return std::tie(first.id, first.name) < std::tie(second.id, second.name);
}
Utils::SmallString id;
Utils::SmallString name;
};
} // namespace
struct AuxiliaryPropertyStorageView::Data
{
Data(Sqlite::Database &database)
: database{database}
{
exclusiveTransaction.commit();
}
Sqlite::Database &database;
Sqlite::ExclusiveNonThrowingDestructorTransaction<Sqlite::Database> exclusiveTransaction{database};
Initializer initializer{database, database.isInitialized()};
Sqlite::WriteStatement<4> upsertValue{
"INSERT INTO auxiliaryProperties(filePath, id, key, value) "
"VALUES(?1, ?2, ?3, ?4) "
"ON CONFLICT DO UPDATE SET value=excluded.value",
database};
Sqlite::ReadStatement<3, 1> selectValues{
"SELECT id, key, value FROM auxiliaryProperties WHERE filePath=?1", database};
Sqlite::WriteStatement<0> removeValues{"DELETE FROM auxiliaryProperties", database};
Sqlite::WriteStatement<3> removeValue{
"DELETE FROM auxiliaryProperties WHERE filePath=?1 AND id=?2 AND key=?3", database};
std::set<ChangeEntry, std::less<>> changeEntries;
};
AuxiliaryPropertyStorageView::AuxiliaryPropertyStorageView(
Sqlite::Database &database, ExternalDependenciesInterface &externalDependencies)
: AbstractView{externalDependencies}
, d{std::make_unique<Data>(database)}
{}
AuxiliaryPropertyStorageView::~AuxiliaryPropertyStorageView() = default;
void AuxiliaryPropertyStorageView::modelAttached(Model *model)
{
auto entryRange = d->selectValues.rangeWithTransaction<Entry>(
Utils::PathString{model->fileUrl().path()});
for (const auto entry : entryRange) {
auto node = model->modelNodeForId(QString{entry.nodeId});
if (!node)
continue;
auto array = QByteArray::fromRawData(entry.value.cdata(), entry.value.ssize());
QDataStream dataStream{array};
QVariant value;
dataStream >> value;
node.setAuxiliaryDataWithoutLock({AuxiliaryDataType::Persistent, entry.key}, value);
}
AbstractView::modelAttached(model);
}
void AuxiliaryPropertyStorageView::modelAboutToBeDetached(Model *model)
{
Utils::PathString filePath{model->fileUrl().path()};
auto update = [&] {
for (const auto &[id, name] : d->changeEntries) {
QByteArray array;
QDataStream dataStream{&array, QIODevice::WriteOnly};
auto node = model->modelNodeForId(id.toQString());
auto value = node.auxiliaryData(AuxiliaryDataType::Persistent, name);
if (value) {
dataStream << *value;
Sqlite::BlobView blob{array};
d->upsertValue.write(filePath, id, name, blob);
} else {
d->removeValue.write(filePath, id, name);
}
}
};
if (d->changeEntries.size()) {
Sqlite::withImmediateTransaction(d->database, update);
d->changeEntries.clear();
}
AbstractView::modelAboutToBeDetached(model);
}
void AuxiliaryPropertyStorageView::nodeIdChanged(const ModelNode &node,
const QString &newIdArg,
const QString &oldIdArg)
{
Utils::SmallString newId = newIdArg;
Utils::SmallString oldId = oldIdArg;
for (auto entry : node.auxiliaryData()) {
if (entry.first.type != AuxiliaryDataType::Persistent)
continue;
d->changeEntries.emplace(oldId, entry.first.name);
d->changeEntries.emplace(newId, entry.first.name);
}
}
void AuxiliaryPropertyStorageView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
if (removedNode.hasId()) {
Utils::SmallString id = removedNode.id();
for (auto entry : removedNode.auxiliaryData()) {
if (entry.first.type != AuxiliaryDataType::Persistent)
continue;
d->changeEntries.emplace(id, entry.first.name);
}
}
}
void AuxiliaryPropertyStorageView::auxiliaryDataChanged(const ModelNode &node,
AuxiliaryDataKeyView key,
const QVariant &)
{
if (isAttached() && key.type == AuxiliaryDataType::Persistent)
d->changeEntries.emplace(Utils::SmallString{node.id()}, key.name);
}
void AuxiliaryPropertyStorageView::resetForTestsOnly()
{
Sqlite::withImmediateTransaction(d->database, [&] { d->removeValues.execute(); });
}
} // namespace QmlDesigner

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <abstractview.h>
#include <memory.h>
namespace QmlDesigner {
class AuxiliaryPropertyStorageView final : public AbstractView
{
public:
AuxiliaryPropertyStorageView(Sqlite::Database &database,
ExternalDependenciesInterface &externalDependencies);
~AuxiliaryPropertyStorageView();
void modelAttached(Model *model) override;
void modelAboutToBeDetached(Model *model) override;
void nodeIdChanged(const ModelNode &node, const QString &newId, const QString &oldId) override;
void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
void auxiliaryDataChanged(const ModelNode &node,
AuxiliaryDataKeyView type,
const QVariant &data) override;
struct Data;
void resetForTestsOnly();
private:
std::unique_ptr<Data> d;
};
} // namespace QmlDesigner

View File

@@ -51,6 +51,14 @@ auto find(Type &&auxiliaryDatas, AuxiliaryDataKeyView key)
});
}
template<typename Type>
auto find(Type &&auxiliaryDatas, AuxiliaryDataType type)
{
return std::find_if(auxiliaryDatas.begin(), auxiliaryDatas.end(), [&](const auto &element) {
return element.first.type == type;
});
}
} // namespace
std::optional<QVariant> InternalNode::auxiliaryData(AuxiliaryDataKeyView key) const
@@ -99,6 +107,13 @@ bool InternalNode::hasAuxiliaryData(AuxiliaryDataKeyView key) const
return found != m_auxiliaryDatas.end();
}
bool InternalNode::hasAuxiliaryData(AuxiliaryDataType type) const
{
auto found = find(m_auxiliaryDatas, type);
return found != m_auxiliaryDatas.end();
}
AuxiliaryDatasForType InternalNode::auxiliaryData(AuxiliaryDataType type) const
{
AuxiliaryDatasForType data;

View File

@@ -76,6 +76,7 @@ public:
bool setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data);
bool removeAuxiliaryData(AuxiliaryDataKeyView key);
bool hasAuxiliaryData(AuxiliaryDataKeyView key) const;
bool hasAuxiliaryData(AuxiliaryDataType type) const;
AuxiliaryDatasForType auxiliaryData(AuxiliaryDataType type) const;
AuxiliaryDatasView auxiliaryData() const { return std::as_const(m_auxiliaryDatas); }

View File

@@ -4,6 +4,7 @@
#include "model.h"
#include "internalnode_p.h"
#include "model_p.h"
#include "modelutils.h"
#include <modelnode.h>
#include "../projectstorage/sourcepath.h"
@@ -2039,6 +2040,18 @@ QList<ModelNode> Model::selectedNodes(AbstractView *view) const
return d->toModelNodeList(d->selectedNodes(), view);
}
void Model::setSelectedModelNodes(const QList<ModelNode> &selectedNodeList)
{
QList<ModelNode> unlockedNodes;
for (const auto &modelNode : selectedNodeList) {
if (!ModelUtils::isThisOrAncestorLocked(modelNode))
unlockedNodes.push_back(modelNode);
}
d->setSelectedNodes(toInternalNodeList(unlockedNodes));
}
void Model::clearMetaInfoCache()
{
d->m_nodeMetaInfoCache.clear();
@@ -2064,7 +2077,6 @@ SourceId Model::fileUrlSourceId() const
*/
void Model::setFileUrl(const QUrl &url)
{
QTC_ASSERT(url.isValid() && url.isLocalFile(), qDebug() << "url:" << url; return);
Internal::WriteLocker locker(d.get());
d->setFileUrl(url);
}

View File

@@ -80,13 +80,13 @@ QString ModelNode::id() const
return m_internalNode->id;
}
void ModelNode::ensureIdExists()
void ModelNode::ensureIdExists() const
{
if (!hasId())
setIdWithoutRefactoring(model()->generateNewId(simplifiedTypeName()));
}
QString ModelNode::validId()
QString ModelNode::validId() const
{
ensureIdExists();
@@ -162,7 +162,7 @@ bool ModelNode::hasId() const
return !m_internalNode->id.isEmpty();
}
void ModelNode::setIdWithRefactoring(const QString &id)
void ModelNode::setIdWithRefactoring(const QString &id) const
{
if (isValid()) {
if (model()->rewriterView() && !id.isEmpty()
@@ -174,7 +174,7 @@ void ModelNode::setIdWithRefactoring(const QString &id)
}
}
void ModelNode::setIdWithoutRefactoring(const QString &id)
void ModelNode::setIdWithoutRefactoring(const QString &id) const
{
Internal::WriteLocker locker(m_model.data());
if (!isValid())
@@ -601,7 +601,8 @@ static QList<ModelNode> descendantNodes(const ModelNode &node)
static void removeModelNodeFromSelection(const ModelNode &node)
{
// remove nodes from the active selection
QList<ModelNode> selectedList = node.view()->selectedModelNodes();
auto model = node.model();
QList<ModelNode> selectedList = model->selectedNodes(node.view());
const QList<ModelNode> descendants = descendantNodes(node);
for (const ModelNode &descendantNode : descendants)
@@ -609,7 +610,7 @@ static void removeModelNodeFromSelection(const ModelNode &node)
selectedList.removeAll(node);
node.view()->setSelectedModelNodes(selectedList);
model->setSelectedModelNodes(selectedList);
}
/*! \brief complete removes this ModelNode from the Model
@@ -995,6 +996,8 @@ void ModelNode::setAuxiliaryData(AuxiliaryDataType type,
void ModelNode::setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data) const
{
if (isValid()) {
if (key.type == AuxiliaryDataType::Persistent)
ensureIdExists();
Internal::WriteLocker locker(m_model.data());
m_model->d->setAuxiliaryData(internalNode(), key, data);
}
@@ -1002,21 +1005,32 @@ void ModelNode::setAuxiliaryData(AuxiliaryDataKeyView key, const QVariant &data)
void ModelNode::setAuxiliaryDataWithoutLock(AuxiliaryDataKeyView key, const QVariant &data) const
{
if (isValid())
if (isValid()) {
if (key.type == AuxiliaryDataType::Persistent)
ensureIdExists();
m_model->d->setAuxiliaryData(internalNode(), key, data);
}
}
void ModelNode::setAuxiliaryDataWithoutLock(AuxiliaryDataType type,
Utils::SmallStringView name,
const QVariant &data) const
{
if (isValid())
if (isValid()) {
if (type == AuxiliaryDataType::Persistent)
ensureIdExists();
m_model->d->setAuxiliaryData(internalNode(), {type, name}, data);
}
}
void ModelNode::removeAuxiliaryData(AuxiliaryDataKeyView key) const
{
if (isValid()) {
if (key.type == AuxiliaryDataType::Persistent)
ensureIdExists();
Internal::WriteLocker locker(m_model.data());
m_model->d->removeAuxiliaryData(internalNode(), key);
}
@@ -1040,6 +1054,14 @@ bool ModelNode::hasAuxiliaryData(AuxiliaryDataType type, Utils::SmallStringView
return hasAuxiliaryData({type, name});
}
bool ModelNode::hasAuxiliaryData(AuxiliaryDataType type) const
{
if (!isValid())
return false;
return m_internalNode->hasAuxiliaryData(type);
}
AuxiliaryDatasForType ModelNode::auxiliaryData(AuxiliaryDataType type) const
{
if (!isValid())

View File

@@ -534,6 +534,42 @@ std::ostream &operator<<(std::ostream &out, FlagIs flagIs)
return out;
}
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallStringView> &key)
{
return out << "(" << key.name << ", " << key.type << ")";
}
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallString> &key)
{
return out << "(" << key.name << ", " << key.type << ")";
}
std::ostream &operator<<(std::ostream &out, AuxiliaryDataType type)
{
switch (type) {
case AuxiliaryDataType::None:
out << "None";
break;
case AuxiliaryDataType::Temporary:
out << "Temporary";
break;
case AuxiliaryDataType::Document:
out << "Document";
break;
case AuxiliaryDataType::NodeInstancePropertyOverwrite:
out << "NodeInstancePropertyOverwrite";
break;
case AuxiliaryDataType::NodeInstanceAuxiliary:
out << "NodeInstanceAuxiliary";
break;
case AuxiliaryDataType::Persistent:
out << "Persistent";
break;
}
return out;
}
namespace Cache {
std::ostream &operator<<(std::ostream &out, const SourceContext &sourceContext)

View File

@@ -4,6 +4,7 @@
#pragma once
#include <designercore/model/modelresourcemanagementfwd.h>
#include <qmlpuppetcommunication/interfaces/nodeinstanceglobal.h>
#include <utils/cpplanguage_details.h>
#include <utils/smallstringio.h>
@@ -126,6 +127,8 @@ class NodeMetaInfo;
class PropertyMetaInfo;
struct CompoundPropertyMetaInfo;
enum class FlagIs : unsigned int;
template<typename NameType>
class BasicAuxiliaryDataKey;
std::ostream &operator<<(std::ostream &out, const ModelNode &node);
std::ostream &operator<<(std::ostream &out, const VariantProperty &property);
@@ -142,6 +145,9 @@ std::ostream &operator<<(std::ostream &out, const NodeMetaInfo &metaInfo);
std::ostream &operator<<(std::ostream &out, const PropertyMetaInfo &metaInfo);
std::ostream &operator<<(std::ostream &out, const CompoundPropertyMetaInfo &metaInfo);
std::ostream &operator<<(std::ostream &out, FlagIs flagIs);
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallStringView> &key);
std::ostream &operator<<(std::ostream &out, const BasicAuxiliaryDataKey<Utils::SmallString> &key);
std::ostream &operator<<(std::ostream &out, AuxiliaryDataType type);
namespace Cache {
class SourceContext;

View File

@@ -4,6 +4,8 @@
#pragma once
#include <ostream>
#include <span>
#include <utility>
#include <vector>
namespace std {
@@ -24,4 +26,41 @@ ostream &operator<<(ostream &out, const vector<T> &vector)
return out;
}
template<typename T, size_t Extent>
ostream &operator<<(ostream &out, const span<T, Extent> &span)
{
out << "[";
for (auto current = span.begin(); current != span.end(); ++current) {
out << *current;
if (std::next(current) != span.end())
out << ", ";
}
out << "]";
return out;
}
template<typename First, typename Second>
ostream &operator<<(ostream &out, const pair<First, Second> &pair)
{
out << "{";
out << pair.first;
out << ", ";
out << pair.second;
out << "}";
return out;
}
inline ostream &operator<<(ostream &out, std::byte byte)
{
return out << std::hex << static_cast<int>(byte) << std::dec;
}
} // namespace std

View File

@@ -80,6 +80,7 @@ add_qtc_library(TestDesignerCore OBJECT
metainfo/nodemetainfo.cpp
model/abstractproperty.cpp
model/abstractview.cpp
model/auxiliarypropertystorageview.cpp model/auxiliarypropertystorageview.h
model/annotation.cpp
model/bindingproperty.cpp
model/import.cpp

View File

@@ -1,6 +1,7 @@
# qmldesigner/designercore/model
extend_qtc_test(unittest
SOURCES
auxiliarypropertystorageview-test.cpp
import-test.cpp
model-test.cpp
modelnode-test.cpp

View File

@@ -0,0 +1,230 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <googletest.h>
#include <mocks/externaldependenciesmock.h>
#include <mocks/modelresourcemanagementmock.h>
#include <mocks/projectstoragemock.h>
#include <mocks/sourcepathcachemock.h>
#include <model/auxiliarypropertystorageview.h>
namespace {
using QmlDesigner::AuxiliaryDataKey;
using QmlDesigner::AuxiliaryDataType;
using QmlDesigner::ModelNode;
template<typename NameMatcher>
auto AuxiliaryProperty(AuxiliaryDataType type, const NameMatcher &nameMatcher, const QVariant &value)
{
return Pair(AllOf(Field(&AuxiliaryDataKey::type, type),
Field(&AuxiliaryDataKey::name, nameMatcher)),
value);
}
class AuxiliaryPropertyStorageView : public ::testing::Test
{
protected:
struct StaticData
{
Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory};
};
~AuxiliaryPropertyStorageView() { view.resetForTestsOnly(); }
static void SetUpTestSuite() { staticData = std::make_unique<StaticData>(); }
static void TearDownTestSuite() { staticData.reset(); }
inline static std::unique_ptr<StaticData> staticData;
Sqlite::Database &database = staticData->database;
NiceMock<SourcePathCacheMockWithPaths> pathCacheMock{"/path/foo.qml"};
NiceMock<ProjectStorageMockWithQtQtuick> projectStorageMock{pathCacheMock.sourceId};
NiceMock<ModelResourceManagementMock> resourceManagementMock;
QmlDesigner::Imports imports = {QmlDesigner::Import::createLibraryImport("QtQuick")};
QmlDesigner::Model model{{projectStorageMock, pathCacheMock},
"Item",
imports,
pathCacheMock.path.toQString(),
std::make_unique<ModelResourceManagementMockWrapper>(
resourceManagementMock)};
QmlDesigner::Model model2{{projectStorageMock, pathCacheMock},
"Item",
imports,
pathCacheMock.path.toQString(),
std::make_unique<ModelResourceManagementMockWrapper>(
resourceManagementMock)};
NiceMock<ExternalDependenciesMock> externalDependenciesMock;
QmlDesigner::AuxiliaryPropertyStorageView view{database, externalDependenciesMock};
ModelNode rootNode = model.rootModelNode();
ModelNode rootNode2 = model2.rootModelNode();
QmlDesigner::AuxiliaryDataKeyView persistentFooKey{AuxiliaryDataType::Persistent, "foo"};
QmlDesigner::AuxiliaryDataKeyView temporaryFooKey{AuxiliaryDataType::Temporary, "foo"};
};
TEST_F(AuxiliaryPropertyStorageView, store_persistent_auxiliary_property)
{
// arrange
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root");
// act
rootNode.setAuxiliaryData(persistentFooKey, "text");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text")));
}
TEST_F(AuxiliaryPropertyStorageView, store_color_auxiliary_property)
{
// arrange
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root");
QColor color{Qt::red};
// act
rootNode.setAuxiliaryData(persistentFooKey, color);
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", color)));
}
TEST_F(AuxiliaryPropertyStorageView, do_not_store_temporary_auxiliary_property)
{
// arrange
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root");
// act
rootNode.setAuxiliaryData(temporaryFooKey, "text");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Temporary, "foo", "text"))));
}
TEST_F(AuxiliaryPropertyStorageView, do_not_load_node_without_id)
{
// arrange
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
// act
rootNode.setAuxiliaryData(persistentFooKey, "text");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
}
TEST_F(AuxiliaryPropertyStorageView, do_not_store_node_without_id)
{
// arrange
model.attachView(&view);
rootNode2.setIdWithoutRefactoring("root");
// act
rootNode.setAuxiliaryData(persistentFooKey, "text");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
}
TEST_F(AuxiliaryPropertyStorageView, do_not_load_from_different_document)
{
// arrange
model2.setFileUrl(QUrl{"/path/foo2.qml"});
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root");
// act
rootNode.setAuxiliaryData(persistentFooKey, "text");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
}
TEST_F(AuxiliaryPropertyStorageView, removing_node_erases_entries)
{
// arrange
auto node = model.createModelNode("Item");
node.setIdWithoutRefactoring("node1");
node.setAuxiliaryData(persistentFooKey, "some value");
auto node2 = model2.createModelNode("Item");
node2.setIdWithoutRefactoring("node1");
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root");
// act
node.destroy();
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(node2.auxiliaryData(),
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "some value"))));
}
TEST_F(AuxiliaryPropertyStorageView, changing_node_id_is_saving_data_under_new_id)
{
// arrange
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root2");
rootNode.setAuxiliaryData(persistentFooKey, "text");
model.detachView(&view);
model.attachView(&view);
// act
rootNode.setIdWithoutRefactoring("root2");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text")));
}
TEST_F(AuxiliaryPropertyStorageView, changing_node_id_is_removing_data_from_old_id)
{
// arrange
model.attachView(&view);
rootNode.setIdWithoutRefactoring("root");
rootNode2.setIdWithoutRefactoring("root");
rootNode.setAuxiliaryData(persistentFooKey, "text");
model.detachView(&view);
model.attachView(&view);
// act
rootNode.setIdWithoutRefactoring("root2");
// assert
model.detachView(&view);
model2.attachView(&view);
ASSERT_THAT(rootNode2.auxiliaryData(),
Not(Contains(AuxiliaryProperty(AuxiliaryDataType::Persistent, "foo", "text"))));
}
} // namespace

View File

@@ -9,10 +9,12 @@
#include "conditionally-disabled-tests.h"
#include "../printers/gtest-creator-printing.h"
#include "../printers/gtest-qt-printing.h"
#include "../printers/gtest-std-printing.h"
#include "../printers/gtest-qt-printing.h"
#include "../printers/gtest-creator-printing.h"
#include "../utils/google-using-declarations.h"
#include "../matchers/unittest-matchers.h"