mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: create folder node & add tests
This commit is contained in:
parent
fa01dbb572
commit
c89277d507
95
shared-lib/flowy-sync/src/client_folder/app_node.rs
Normal file
95
shared-lib/flowy-sync/src/client_folder/app_node.rs
Normal file
@ -0,0 +1,95 @@
|
||||
use crate::client_folder::view_node::ViewNode;
|
||||
use crate::client_folder::{get_attributes_str_value, set_attributes_str_value, AtomicNodeTree};
|
||||
use crate::errors::CollaborateResult;
|
||||
use folder_rev_model::{AppRevision, ViewRevision};
|
||||
use lib_ot::core::{NodeData, NodeDataBuilder, NodeOperation, Path, Transaction};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppNode {
|
||||
pub id: String,
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
pub(crate) path: Path,
|
||||
views: Vec<Arc<ViewNode>>,
|
||||
}
|
||||
|
||||
impl AppNode {
|
||||
pub(crate) fn from_app_revision(
|
||||
transaction: &mut Transaction,
|
||||
revision: AppRevision,
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
path: Path,
|
||||
) -> CollaborateResult<Self> {
|
||||
let app_id = revision.id.clone();
|
||||
let app_node = NodeDataBuilder::new("app")
|
||||
.insert_attribute("id", revision.id)
|
||||
.insert_attribute("name", revision.name)
|
||||
.insert_attribute("workspace_id", revision.workspace_id)
|
||||
.build();
|
||||
|
||||
transaction.push_operation(NodeOperation::Insert {
|
||||
path: path.clone(),
|
||||
nodes: vec![app_node],
|
||||
});
|
||||
|
||||
let views = revision
|
||||
.belongings
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, app)| (path.clone_with(index), app))
|
||||
.flat_map(
|
||||
|(path, app)| match ViewNode::from_view_revision(transaction, app, tree.clone(), path) {
|
||||
Ok(view_node) => Some(Arc::new(view_node)),
|
||||
Err(err) => {
|
||||
tracing::error!("create view node failed: {:?}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<Arc<ViewNode>>>();
|
||||
|
||||
Ok(Self {
|
||||
id: app_id,
|
||||
tree,
|
||||
path,
|
||||
views,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
get_attributes_str_value(self.tree.clone(), &self.path, "name")
|
||||
}
|
||||
|
||||
pub fn set_name(&self, name: &str) -> CollaborateResult<()> {
|
||||
set_attributes_str_value(self.tree.clone(), &self.path, "name", name.to_string())
|
||||
}
|
||||
|
||||
fn get_workspace_id(&self) -> Option<String> {
|
||||
get_attributes_str_value(self.tree.clone(), &self.path, "workspace_id")
|
||||
}
|
||||
|
||||
fn set_workspace_id(&self, workspace_id: String) -> CollaborateResult<()> {
|
||||
set_attributes_str_value(self.tree.clone(), &self.path, "workspace_id", workspace_id)
|
||||
}
|
||||
|
||||
fn get_view(&self, view_id: &str) -> Option<&Arc<ViewNode>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_mut_view(&mut self, view_id: &str) -> Option<&mut Arc<ViewNode>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn add_view(&mut self, revision: ViewRevision) -> CollaborateResult<()> {
|
||||
let mut transaction = Transaction::new();
|
||||
let path = self.path.clone_with(self.views.len());
|
||||
let view_node = ViewNode::from_view_revision(&mut transaction, revision, self.tree.clone(), path)?;
|
||||
let _ = self.tree.write().apply_transaction(transaction)?;
|
||||
self.views.push(Arc::new(view_node));
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn remove_view(&mut self, view_id: &str) {
|
||||
todo!()
|
||||
}
|
||||
}
|
155
shared-lib/flowy-sync/src/client_folder/folder_node.rs
Normal file
155
shared-lib/flowy-sync/src/client_folder/folder_node.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use crate::client_folder::workspace_node::WorkspaceNode;
|
||||
use crate::errors::{CollaborateError, CollaborateResult};
|
||||
use folder_rev_model::{AppRevision, ViewRevision, WorkspaceRevision};
|
||||
use lib_ot::core::{
|
||||
AttributeEntry, AttributeHashMap, AttributeValue, Changeset, Node, NodeDataBuilder, NodeOperation, NodeTree, Path,
|
||||
Transaction,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use std::string::ToString;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type AtomicNodeTree = RwLock<NodeTree>;
|
||||
|
||||
pub struct FolderNodePad {
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
workspaces: Vec<Arc<WorkspaceNode>>,
|
||||
trash: Vec<Arc<TrashNode>>,
|
||||
}
|
||||
|
||||
impl FolderNodePad {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get_workspace(&self, workspace_id: &str) -> Option<&Arc<WorkspaceNode>> {
|
||||
self.workspaces.iter().find(|workspace| workspace.id == workspace_id)
|
||||
}
|
||||
|
||||
pub fn get_mut_workspace(&mut self, workspace_id: &str) -> Option<&mut Arc<WorkspaceNode>> {
|
||||
self.workspaces
|
||||
.iter_mut()
|
||||
.find(|workspace| workspace.id == workspace_id)
|
||||
}
|
||||
|
||||
pub fn remove_workspace(&mut self, workspace_id: &str) {
|
||||
if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) {
|
||||
let mut nodes = vec![];
|
||||
let workspace_node = self.tree.read().get_node_data_at_path(&workspace.path);
|
||||
debug_assert!(workspace_node.is_some());
|
||||
|
||||
if let Some(node_data) = workspace_node {
|
||||
nodes.push(node_data);
|
||||
}
|
||||
let delete_operation = NodeOperation::Delete {
|
||||
path: workspace.path.clone(),
|
||||
nodes,
|
||||
};
|
||||
let _ = self.tree.write().apply_op(delete_operation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_workspace(&mut self, revision: WorkspaceRevision) -> CollaborateResult<()> {
|
||||
let mut transaction = Transaction::new();
|
||||
let workspace_node = WorkspaceNode::from_workspace_revision(
|
||||
&mut transaction,
|
||||
revision,
|
||||
self.tree.clone(),
|
||||
workspaces_path().clone_with(self.workspaces.len()),
|
||||
)?;
|
||||
let _ = self.tree.write().apply_transaction(transaction)?;
|
||||
self.workspaces.push(Arc::new(workspace_node));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_json(&self, pretty: bool) -> CollaborateResult<String> {
|
||||
self.tree
|
||||
.read()
|
||||
.to_json(pretty)
|
||||
.map_err(|e| CollaborateError::serde().context(e))
|
||||
}
|
||||
}
|
||||
|
||||
fn folder_path() -> Path {
|
||||
vec![0].into()
|
||||
}
|
||||
|
||||
fn workspaces_path() -> Path {
|
||||
folder_path().clone_with(0)
|
||||
}
|
||||
|
||||
fn trash_path() -> Path {
|
||||
folder_path().clone_with(1)
|
||||
}
|
||||
|
||||
pub fn get_attributes(tree: Arc<AtomicNodeTree>, path: &Path) -> Option<AttributeHashMap> {
|
||||
tree.read()
|
||||
.get_node_at_path(&path)
|
||||
.and_then(|node| Some(node.attributes.clone()))
|
||||
}
|
||||
|
||||
pub fn get_attributes_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<AttributeValue> {
|
||||
tree.read()
|
||||
.get_node_at_path(&path)
|
||||
.and_then(|node| node.attributes.get(key).cloned())
|
||||
}
|
||||
|
||||
pub fn get_attributes_str_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<String> {
|
||||
tree.read()
|
||||
.get_node_at_path(&path)
|
||||
.and_then(|node| node.attributes.get(key).cloned())
|
||||
.and_then(|value| value.str_value())
|
||||
}
|
||||
|
||||
pub fn set_attributes_str_value(
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
path: &Path,
|
||||
key: &str,
|
||||
value: String,
|
||||
) -> CollaborateResult<()> {
|
||||
let old_attributes = match get_attributes(tree.clone(), path) {
|
||||
None => AttributeHashMap::new(),
|
||||
Some(attributes) => attributes,
|
||||
};
|
||||
let mut new_attributes = old_attributes.clone();
|
||||
new_attributes.insert(key, value);
|
||||
|
||||
let update_operation = NodeOperation::Update {
|
||||
path: path.clone(),
|
||||
changeset: Changeset::Attributes {
|
||||
new: new_attributes,
|
||||
old: old_attributes,
|
||||
},
|
||||
};
|
||||
let _ = tree.write().apply_op(update_operation)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl std::default::Default for FolderNodePad {
|
||||
fn default() -> Self {
|
||||
let workspace_node = NodeDataBuilder::new("workspaces").build();
|
||||
let trash_node = NodeDataBuilder::new("trash").build();
|
||||
let folder_node = NodeDataBuilder::new("folder")
|
||||
.add_node_data(workspace_node)
|
||||
.add_node_data(trash_node)
|
||||
.build();
|
||||
|
||||
let operation = NodeOperation::Insert {
|
||||
path: folder_path(),
|
||||
nodes: vec![folder_node],
|
||||
};
|
||||
let mut tree = NodeTree::default();
|
||||
let _ = tree.apply_op(operation).unwrap();
|
||||
|
||||
Self {
|
||||
tree: Arc::new(RwLock::new(tree)),
|
||||
workspaces: vec![],
|
||||
trash: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TrashNode {
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
parent_path: Path,
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
mod app_node;
|
||||
mod builder;
|
||||
mod folder_node;
|
||||
mod folder_pad;
|
||||
mod view_node;
|
||||
mod workspace_node;
|
||||
|
||||
pub use folder_node::*;
|
||||
pub use folder_pad::*;
|
||||
|
44
shared-lib/flowy-sync/src/client_folder/view_node.rs
Normal file
44
shared-lib/flowy-sync/src/client_folder/view_node.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use crate::client_folder::AtomicNodeTree;
|
||||
use crate::errors::CollaborateResult;
|
||||
use folder_rev_model::ViewRevision;
|
||||
use lib_ot::core::{NodeDataBuilder, NodeOperation, Path, Transaction};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewNode {
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
path: Path,
|
||||
}
|
||||
|
||||
impl ViewNode {
|
||||
pub(crate) fn from_view_revision(
|
||||
transaction: &mut Transaction,
|
||||
revision: ViewRevision,
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
path: Path,
|
||||
) -> CollaborateResult<Self> {
|
||||
let view_node = NodeDataBuilder::new("view")
|
||||
.insert_attribute("id", revision.id)
|
||||
.insert_attribute("name", revision.name)
|
||||
.build();
|
||||
|
||||
transaction.push_operation(NodeOperation::Insert {
|
||||
path: path.clone(),
|
||||
nodes: vec![view_node],
|
||||
});
|
||||
|
||||
Ok(Self { tree, path })
|
||||
}
|
||||
|
||||
fn get_id(&self) -> &str {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_app_id(&self) -> &str {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_app_id(&self, workspace_id: String) {
|
||||
todo!()
|
||||
}
|
||||
}
|
104
shared-lib/flowy-sync/src/client_folder/workspace_node.rs
Normal file
104
shared-lib/flowy-sync/src/client_folder/workspace_node.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use crate::client_folder::app_node::AppNode;
|
||||
use crate::client_folder::view_node::ViewNode;
|
||||
use crate::client_folder::{get_attributes_str_value, get_attributes_value, set_attributes_str_value, AtomicNodeTree};
|
||||
use crate::errors::CollaborateResult;
|
||||
use folder_rev_model::{AppRevision, WorkspaceRevision};
|
||||
use lib_ot::core::{AttributeValue, NodeDataBuilder, NodeOperation, Path, Transaction};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkspaceNode {
|
||||
pub(crate) id: String,
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
pub(crate) path: Path,
|
||||
apps: Vec<Arc<AppNode>>,
|
||||
}
|
||||
|
||||
impl WorkspaceNode {
|
||||
pub(crate) fn from_workspace_revision(
|
||||
transaction: &mut Transaction,
|
||||
revision: WorkspaceRevision,
|
||||
tree: Arc<AtomicNodeTree>,
|
||||
path: Path,
|
||||
) -> CollaborateResult<Self> {
|
||||
let workspace_id = revision.id.clone();
|
||||
let workspace_node = NodeDataBuilder::new("workspace")
|
||||
.insert_attribute("id", revision.id)
|
||||
.insert_attribute("name", revision.name)
|
||||
.build();
|
||||
|
||||
transaction.push_operation(NodeOperation::Insert {
|
||||
path: path.clone(),
|
||||
nodes: vec![workspace_node],
|
||||
});
|
||||
|
||||
let apps = revision
|
||||
.apps
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, app)| (path.clone_with(index), app))
|
||||
.flat_map(
|
||||
|(path, app)| match AppNode::from_app_revision(transaction, app, tree.clone(), path) {
|
||||
Ok(app_node) => Some(Arc::new(app_node)),
|
||||
Err(err) => {
|
||||
tracing::warn!("Create app node failed: {:?}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<Arc<AppNode>>>();
|
||||
|
||||
Ok(Self {
|
||||
id: workspace_id,
|
||||
tree,
|
||||
path,
|
||||
apps,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
get_attributes_str_value(self.tree.clone(), &self.path, "name")
|
||||
}
|
||||
|
||||
pub fn set_name(&self, name: &str) -> CollaborateResult<()> {
|
||||
set_attributes_str_value(self.tree.clone(), &self.path, "name", name.to_string())
|
||||
}
|
||||
|
||||
pub fn get_app(&self, app_id: &str) -> Option<&Arc<AppNode>> {
|
||||
self.apps.iter().find(|app| app.id == app_id)
|
||||
}
|
||||
|
||||
pub fn get_mut_app(&mut self, app_id: &str) -> Option<&mut Arc<AppNode>> {
|
||||
self.apps.iter_mut().find(|app| app.id == app_id)
|
||||
}
|
||||
|
||||
pub fn add_app(&mut self, app: AppRevision) -> CollaborateResult<()> {
|
||||
let mut transaction = Transaction::new();
|
||||
let path = self.path.clone_with(self.apps.len());
|
||||
let app_node = AppNode::from_app_revision(&mut transaction, app, self.tree.clone(), path.clone())?;
|
||||
let _ = self.tree.write().apply_transaction(transaction);
|
||||
self.apps.push(Arc::new(app_node));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_app(&mut self, app_id: &str) {
|
||||
if let Some(index) = self.apps.iter().position(|app| app.id == app_id) {
|
||||
let app = self.apps.remove(index);
|
||||
let mut nodes = vec![];
|
||||
let app_node = self.tree.read().get_node_data_at_path(&app.path);
|
||||
debug_assert!(app_node.is_some());
|
||||
if let Some(node_data) = app_node {
|
||||
nodes.push(node_data);
|
||||
}
|
||||
let delete_operation = NodeOperation::Delete {
|
||||
path: app.path.clone(),
|
||||
nodes,
|
||||
};
|
||||
let _ = self.tree.write().apply_op(delete_operation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_apps(&self) -> Vec<Arc<AppNode>> {
|
||||
self.apps.clone()
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ impl CollaborateError {
|
||||
self
|
||||
}
|
||||
|
||||
static_error!(serde, ErrorCode::SerdeError);
|
||||
static_error!(internal, ErrorCode::InternalError);
|
||||
static_error!(undo, ErrorCode::UndoFail);
|
||||
static_error!(redo, ErrorCode::RedoFail);
|
||||
@ -51,14 +52,15 @@ impl fmt::Display for CollaborateError {
|
||||
|
||||
#[derive(Debug, Clone, Display, PartialEq, Eq)]
|
||||
pub enum ErrorCode {
|
||||
DocIdInvalid = 0,
|
||||
DocNotfound = 1,
|
||||
DocumentIdInvalid = 0,
|
||||
DocumentNotfound = 1,
|
||||
UndoFail = 200,
|
||||
RedoFail = 201,
|
||||
OutOfBound = 202,
|
||||
RevisionConflict = 203,
|
||||
RecordNotFound = 300,
|
||||
CannotDeleteThePrimaryField = 301,
|
||||
SerdeError = 999,
|
||||
InternalError = 1000,
|
||||
}
|
||||
|
||||
|
79
shared-lib/flowy-sync/tests/client_folder/folder_test.rs
Normal file
79
shared-lib/flowy-sync/tests/client_folder/folder_test.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use flowy_sync::client_folder::FolderNodePad;
|
||||
use folder_rev_model::WorkspaceRevision;
|
||||
|
||||
#[test]
|
||||
fn client_folder_create_default_folder_test() {
|
||||
let folder_pad = FolderNodePad::default();
|
||||
let json = folder_pad.to_json(false).unwrap();
|
||||
assert_eq!(
|
||||
json,
|
||||
r#"{"type":"folder","children":[{"type":"workspaces"},{"type":"trash"}]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_folder_create_default_folder_with_workspace_test() {
|
||||
let mut folder_pad = FolderNodePad::default();
|
||||
let workspace = WorkspaceRevision {
|
||||
id: "1".to_string(),
|
||||
name: "workspace name".to_string(),
|
||||
desc: "".to_string(),
|
||||
apps: vec![],
|
||||
modified_time: 0,
|
||||
create_time: 0,
|
||||
};
|
||||
folder_pad.add_workspace(workspace).unwrap();
|
||||
let json = folder_pad.to_json(false).unwrap();
|
||||
assert_eq!(
|
||||
json,
|
||||
r#"{"type":"folder","children":[{"type":"workspaces","children":[{"type":"workspace","attributes":{"id":"1","name":"workspace name"}}]},{"type":"trash"}]}"#
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
folder_pad.get_workspace("1").unwrap().get_name().unwrap(),
|
||||
"workspace name"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_folder_delete_workspace_test() {
|
||||
let mut folder_pad = FolderNodePad::default();
|
||||
let workspace = WorkspaceRevision {
|
||||
id: "1".to_string(),
|
||||
name: "workspace name".to_string(),
|
||||
desc: "".to_string(),
|
||||
apps: vec![],
|
||||
modified_time: 0,
|
||||
create_time: 0,
|
||||
};
|
||||
folder_pad.add_workspace(workspace).unwrap();
|
||||
folder_pad.remove_workspace("1");
|
||||
let json = folder_pad.to_json(false).unwrap();
|
||||
assert_eq!(
|
||||
json,
|
||||
r#"{"type":"folder","children":[{"type":"workspaces"},{"type":"trash"}]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_folder_update_workspace_name_test() {
|
||||
let mut folder_pad = FolderNodePad::default();
|
||||
let workspace = WorkspaceRevision {
|
||||
id: "1".to_string(),
|
||||
name: "workspace name".to_string(),
|
||||
desc: "".to_string(),
|
||||
apps: vec![],
|
||||
modified_time: 0,
|
||||
create_time: 0,
|
||||
};
|
||||
folder_pad.add_workspace(workspace).unwrap();
|
||||
folder_pad
|
||||
.get_workspace("1")
|
||||
.unwrap()
|
||||
.set_name("My first workspace")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
folder_pad.get_workspace("1").unwrap().get_name().unwrap(),
|
||||
"My first workspace"
|
||||
);
|
||||
}
|
3
shared-lib/flowy-sync/tests/client_folder/mod.rs
Normal file
3
shared-lib/flowy-sync/tests/client_folder/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
mod folder_test;
|
||||
mod script;
|
||||
mod workspace_test;
|
85
shared-lib/flowy-sync/tests/client_folder/script.rs
Normal file
85
shared-lib/flowy-sync/tests/client_folder/script.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use flowy_sync::client_folder::FolderNodePad;
|
||||
use folder_rev_model::{AppRevision, WorkspaceRevision};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub enum FolderNodePadScript {
|
||||
CreateApp { id: String, name: String },
|
||||
DeleteApp { id: String },
|
||||
AssertApp { id: String, expected: Option<AppRevision> },
|
||||
AssertAppContent { id: String, name: String },
|
||||
AssertNumberOfApps { expected: usize },
|
||||
}
|
||||
|
||||
pub struct FolderNodePadTest {
|
||||
folder_pad: FolderNodePad,
|
||||
}
|
||||
|
||||
impl FolderNodePadTest {
|
||||
pub fn new() -> FolderNodePadTest {
|
||||
let mut folder_pad = FolderNodePad::default();
|
||||
let workspace = WorkspaceRevision {
|
||||
id: "1".to_string(),
|
||||
name: "workspace name".to_string(),
|
||||
desc: "".to_string(),
|
||||
apps: vec![],
|
||||
modified_time: 0,
|
||||
create_time: 0,
|
||||
};
|
||||
let _ = folder_pad.add_workspace(workspace).unwrap();
|
||||
Self { folder_pad }
|
||||
}
|
||||
|
||||
pub fn run_scripts(&mut self, scripts: Vec<FolderNodePadScript>) {
|
||||
for script in scripts {
|
||||
self.run_script(script);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_script(&mut self, script: FolderNodePadScript) {
|
||||
match script {
|
||||
FolderNodePadScript::CreateApp { id, name } => {
|
||||
let revision = AppRevision {
|
||||
id,
|
||||
workspace_id: "1".to_string(),
|
||||
name,
|
||||
desc: "".to_string(),
|
||||
belongings: vec![],
|
||||
version: 0,
|
||||
modified_time: 0,
|
||||
create_time: 0,
|
||||
};
|
||||
|
||||
let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap();
|
||||
let workspace_node = Arc::make_mut(workspace_node);
|
||||
let _ = workspace_node.add_app(revision).unwrap();
|
||||
}
|
||||
FolderNodePadScript::DeleteApp { id } => {
|
||||
let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap();
|
||||
let workspace_node = Arc::make_mut(workspace_node);
|
||||
workspace_node.remove_app(&id);
|
||||
}
|
||||
|
||||
FolderNodePadScript::AssertApp { id, expected } => {
|
||||
let workspace_node = self.folder_pad.get_workspace("1").unwrap();
|
||||
let app = workspace_node.get_app(&id);
|
||||
match expected {
|
||||
None => assert!(app.is_none()),
|
||||
Some(expected_app) => {
|
||||
let app_node = app.unwrap();
|
||||
assert_eq!(expected_app.name, app_node.get_name().unwrap());
|
||||
assert_eq!(expected_app.id, app_node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
FolderNodePadScript::AssertAppContent { id, name } => {
|
||||
let workspace_node = self.folder_pad.get_workspace("1").unwrap();
|
||||
let app = workspace_node.get_app(&id).unwrap();
|
||||
assert_eq!(app.get_name().unwrap(), name)
|
||||
}
|
||||
FolderNodePadScript::AssertNumberOfApps { expected } => {
|
||||
let workspace_node = self.folder_pad.get_workspace("1").unwrap();
|
||||
assert_eq!(workspace_node.get_all_apps().len(), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
shared-lib/flowy-sync/tests/client_folder/workspace_test.rs
Normal file
34
shared-lib/flowy-sync/tests/client_folder/workspace_test.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::client_folder::script::FolderNodePadScript::*;
|
||||
use crate::client_folder::script::FolderNodePadTest;
|
||||
use flowy_sync::client_folder::FolderNodePad;
|
||||
|
||||
#[test]
|
||||
fn client_folder_create_app_test() {
|
||||
let mut test = FolderNodePadTest::new();
|
||||
test.run_scripts(vec![
|
||||
CreateApp {
|
||||
id: "1".to_string(),
|
||||
name: "my first app".to_string(),
|
||||
},
|
||||
AssertAppContent {
|
||||
id: "1".to_string(),
|
||||
name: "my first app".to_string(),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_folder_delete_app_test() {
|
||||
let mut test = FolderNodePadTest::new();
|
||||
test.run_scripts(vec![
|
||||
CreateApp {
|
||||
id: "1".to_string(),
|
||||
name: "my first app".to_string(),
|
||||
},
|
||||
DeleteApp { id: "1".to_string() },
|
||||
AssertApp {
|
||||
id: "1".to_string(),
|
||||
expected: None,
|
||||
},
|
||||
]);
|
||||
}
|
1
shared-lib/flowy-sync/tests/main.rs
Normal file
1
shared-lib/flowy-sync/tests/main.rs
Normal file
@ -0,0 +1 @@
|
||||
mod client_folder;
|
@ -258,3 +258,9 @@ impl std::convert::From<Vec<NodeOperation>> for NodeOperations {
|
||||
Self::from_operations(operations)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<NodeOperation> for NodeOperations {
|
||||
fn from(operation: NodeOperation) -> Self {
|
||||
Self::from_operations(vec![operation])
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,12 @@ impl Path {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn clone_with(&self, element: usize) -> Self {
|
||||
let mut cloned_self = self.clone();
|
||||
cloned_self.push(element);
|
||||
cloned_self
|
||||
}
|
||||
|
||||
pub fn is_root(&self) -> bool {
|
||||
self.0.len() == 1 && self.0[0] == 0
|
||||
}
|
||||
@ -47,6 +53,12 @@ impl std::ops::Deref for Path {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Path {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<usize> for Path {
|
||||
fn from(val: usize) -> Self {
|
||||
Path(vec![val])
|
||||
|
@ -4,10 +4,10 @@ use crate::errors::{OTError, OTErrorCode};
|
||||
use indextree::{Arena, FollowingSiblings, NodeId};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct NodeTreeContext {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeTree {
|
||||
arena: Arena<Node>,
|
||||
root: NodeId,
|
||||
@ -50,6 +50,20 @@ impl NodeTree {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self, pretty: bool) -> Result<String, OTError> {
|
||||
if pretty {
|
||||
match serde_json::to_string_pretty(self) {
|
||||
Ok(json) => Ok(json),
|
||||
Err(err) => Err(OTError::serde().context(err)),
|
||||
}
|
||||
} else {
|
||||
match serde_json::to_string(self) {
|
||||
Ok(json) => Ok(json),
|
||||
Err(err) => Err(OTError::serde().context(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_operations<T: Into<NodeOperations>>(operations: T, context: NodeTreeContext) -> Result<Self, OTError> {
|
||||
let operations = operations.into();
|
||||
let mut node_tree = NodeTree::new(context);
|
||||
@ -260,8 +274,8 @@ impl NodeTree {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: Arc<NodeOperation>) -> Result<(), OTError> {
|
||||
let op = match Arc::try_unwrap(op) {
|
||||
pub fn apply_op<T: Into<Arc<NodeOperation>>>(&mut self, op: T) -> Result<(), OTError> {
|
||||
let op = match Arc::try_unwrap(op.into()) {
|
||||
Ok(op) => op,
|
||||
Err(op) => op.as_ref().clone(),
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user