mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-10-31 10:03:18 +00:00 
			
		
		
		
	chore: add update node body test
This commit is contained in:
		
							parent
							
								
									2a5d9d5530
								
							
						
					
					
						commit
						1d7d4092a5
					
				| @ -39,8 +39,8 @@ impl NodeAttributes { | |||||||
|         self.0.is_empty() |         self.0.is_empty() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn delete(&mut self, key: &AttributeKey) { |     pub fn delete<K: ToString>(&mut self, key: K) { | ||||||
|         self.insert(key.clone(), AttributeValue(None)); |         self.insert(key.to_string(), AttributeValue(None)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -139,8 +139,34 @@ impl std::convert::From<bool> for AttributeValue { | |||||||
|     fn from(val: bool) -> Self { |     fn from(val: bool) -> Self { | ||||||
|         let val = match val { |         let val = match val { | ||||||
|             true => Some("true".to_owned()), |             true => Some("true".to_owned()), | ||||||
|             false => None, |             false => Some("false".to_owned()), | ||||||
|         }; |         }; | ||||||
|         AttributeValue(val) |         AttributeValue(val) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | pub struct NodeAttributeBuilder { | ||||||
|  |     attributes: NodeAttributes, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NodeAttributeBuilder { | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         Self { | ||||||
|  |             attributes: NodeAttributes::default(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn insert<K: ToString, V: Into<AttributeValue>>(mut self, key: K, value: V) -> Self { | ||||||
|  |         self.attributes.insert(key, value); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn delete<K: ToString>(mut self, key: K) -> Self { | ||||||
|  |         self.attributes.delete(key); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn build(self) -> NodeAttributes { | ||||||
|  |         self.attributes | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -26,6 +26,13 @@ impl NodeTree { | |||||||
|         Some(self.arena.get(node_id)?.get()) |         Some(self.arena.get(node_id)?.get()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn get_node_at_path(&self, path: &Path) -> Option<&Node> { | ||||||
|  |         { | ||||||
|  |             let node_id = self.node_id_at_path(path)?; | ||||||
|  |             self.get_node(node_id) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// # Examples
 |     /// # Examples
 | ||||||
|     ///
 |     ///
 | ||||||
| @ -41,7 +48,7 @@ impl NodeTree { | |||||||
|     /// let node_path = node_tree.path_of_node(node_id);
 |     /// let node_path = node_tree.path_of_node(node_id);
 | ||||||
|     /// debug_assert_eq!(node_path, root_path);
 |     /// debug_assert_eq!(node_path, root_path);
 | ||||||
|     /// ```
 |     /// ```
 | ||||||
|     pub fn node_at_path<T: Into<Path>>(&self, path: T) -> Option<NodeId> { |     pub fn node_id_at_path<T: Into<Path>>(&self, path: T) -> Option<NodeId> { | ||||||
|         let path = path.into(); |         let path = path.into(); | ||||||
|         if path.is_empty() { |         if path.is_empty() { | ||||||
|             return Some(self.root); |             return Some(self.root); | ||||||
| @ -54,7 +61,7 @@ impl NodeTree { | |||||||
|         Some(iterate_node) |         Some(iterate_node) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn path_of_node(&self, node_id: NodeId) -> Path { |     pub fn path_from_node_id(&self, node_id: NodeId) -> Path { | ||||||
|         let mut path = vec![]; |         let mut path = vec![]; | ||||||
|         let mut current_node = node_id; |         let mut current_node = node_id; | ||||||
|         // Use .skip(1) on the ancestors iterator to skip the root node.
 |         // Use .skip(1) on the ancestors iterator to skip the root node.
 | ||||||
| @ -137,7 +144,7 @@ impl NodeTree { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn apply(&mut self, transaction: Transaction) -> Result<(), OTError> { |     pub fn apply(&mut self, transaction: Transaction) -> Result<(), OTError> { | ||||||
|         for op in &transaction.operations { |         for op in transaction.operations.iter() { | ||||||
|             self.apply_op(op)?; |             self.apply_op(op)?; | ||||||
|         } |         } | ||||||
|         Ok(()) |         Ok(()) | ||||||
| @ -164,7 +171,7 @@ impl NodeTree { | |||||||
|         let (parent_path, last_path) = path.split_at(path.0.len() - 1); |         let (parent_path, last_path) = path.split_at(path.0.len() - 1); | ||||||
|         let last_index = *last_path.first().unwrap(); |         let last_index = *last_path.first().unwrap(); | ||||||
|         let parent_node = self |         let parent_node = self | ||||||
|             .node_at_path(parent_path) |             .node_id_at_path(parent_path) | ||||||
|             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; |             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; | ||||||
| 
 | 
 | ||||||
|         self.insert_nodes_at_index(parent_node, last_index, nodes) |         self.insert_nodes_at_index(parent_node, last_index, nodes) | ||||||
| @ -228,7 +235,7 @@ impl NodeTree { | |||||||
| 
 | 
 | ||||||
|     fn delete_node(&mut self, path: &Path, nodes: &[NodeData]) -> Result<(), OTError> { |     fn delete_node(&mut self, path: &Path, nodes: &[NodeData]) -> Result<(), OTError> { | ||||||
|         let mut update_node = self |         let mut update_node = self | ||||||
|             .node_at_path(path) |             .node_id_at_path(path) | ||||||
|             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; |             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; | ||||||
| 
 | 
 | ||||||
|         for _ in 0..nodes.len() { |         for _ in 0..nodes.len() { | ||||||
| @ -255,7 +262,7 @@ impl NodeTree { | |||||||
|         F: Fn(&mut Node) -> Result<(), OTError>, |         F: Fn(&mut Node) -> Result<(), OTError>, | ||||||
|     { |     { | ||||||
|         let node_id = self |         let node_id = self | ||||||
|             .node_at_path(path) |             .node_id_at_path(path) | ||||||
|             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; |             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; | ||||||
|         match self.arena.get_mut(node_id) { |         match self.arena.get_mut(node_id) { | ||||||
|             None => tracing::warn!("The path: {:?} does not contain any nodes", path), |             None => tracing::warn!("The path: {:?} does not contain any nodes", path), | ||||||
|  | |||||||
| @ -1,8 +1,10 @@ | |||||||
| use crate::core::document::operation_serde::*; | use crate::core::document::operation_serde::*; | ||||||
| use crate::core::document::path::Path; | use crate::core::document::path::Path; | ||||||
| use crate::core::{NodeAttributes, NodeBodyChangeset, NodeData}; | use crate::core::{NodeAttributes, NodeBodyChangeset, NodeData}; | ||||||
|  | use crate::errors::OTError; | ||||||
|  | use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, serde::Serialize, serde::Deserialize)] | #[derive(Clone, Serialize, Deserialize)] | ||||||
| #[serde(tag = "op")] | #[serde(tag = "op")] | ||||||
| pub enum NodeOperation { | pub enum NodeOperation { | ||||||
|     #[serde(rename = "insert")] |     #[serde(rename = "insert")] | ||||||
| @ -99,59 +101,37 @@ impl NodeOperation { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[cfg(test)] | #[derive(Serialize, Deserialize, Default)] | ||||||
| mod tests { | pub struct NodeOperationList { | ||||||
|     use crate::core::{NodeAttributes, NodeBodyChangeset, NodeBuilder, NodeData, NodeOperation, Path, TextDelta}; |     operations: Vec<NodeOperation>, | ||||||
|     #[test] | } | ||||||
|     fn test_serialize_insert_operation() { |  | ||||||
|         let insert = NodeOperation::Insert { |  | ||||||
|             path: Path(vec![0, 1]), |  | ||||||
|             nodes: vec![NodeData::new("text".to_owned())], |  | ||||||
|         }; |  | ||||||
|         let result = serde_json::to_string(&insert).unwrap(); |  | ||||||
|         assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     #[test] | impl std::ops::Deref for NodeOperationList { | ||||||
|     fn test_serialize_insert_sub_trees() { |     type Target = Vec<NodeOperation>; | ||||||
|         let insert = NodeOperation::Insert { |  | ||||||
|             path: Path(vec![0, 1]), |  | ||||||
|             nodes: vec![NodeBuilder::new("text") |  | ||||||
|                 .add_node(NodeData::new("text".to_owned())) |  | ||||||
|                 .build()], |  | ||||||
|         }; |  | ||||||
|         let result = serde_json::to_string(&insert).unwrap(); |  | ||||||
|         assert_eq!( |  | ||||||
|             result, |  | ||||||
|             r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"text"}]}]}"# |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     #[test] |     fn deref(&self) -> &Self::Target { | ||||||
|     fn test_serialize_update_operation() { |         &self.operations | ||||||
|         let insert = NodeOperation::UpdateAttributes { |     } | ||||||
|             path: Path(vec![0, 1]), | } | ||||||
|             attributes: NodeAttributes::new(), | 
 | ||||||
|             old_attributes: NodeAttributes::new(), | impl std::ops::DerefMut for NodeOperationList { | ||||||
|         }; |     fn deref_mut(&mut self) -> &mut Self::Target { | ||||||
|         let result = serde_json::to_string(&insert).unwrap(); |         &mut self.operations | ||||||
|         assert_eq!( |     } | ||||||
|             result, | } | ||||||
|             r#"{"op":"update","path":[0,1],"attributes":{},"oldAttributes":{}}"# | 
 | ||||||
|         ); | impl NodeOperationList { | ||||||
|     } |     pub fn new(operations: Vec<NodeOperation>) -> Self { | ||||||
| 
 |         Self { operations } | ||||||
|     #[test] |     } | ||||||
|     fn test_serialize_text_edit_operation() { | 
 | ||||||
|         let changeset = NodeBodyChangeset::Delta { |     pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, OTError> { | ||||||
|             delta: TextDelta::new(), |         let operation_list = serde_json::from_slice(&bytes).map_err(|err| OTError::serde().context(err))?; | ||||||
|             inverted: TextDelta::new(), |         Ok(operation_list) | ||||||
|         }; |     } | ||||||
|         let insert = NodeOperation::UpdateBody { | 
 | ||||||
|             path: Path(vec![0, 1]), |     pub fn to_bytes(&self) -> Result<Vec<u8>, OTError> { | ||||||
|             changeset, |         let bytes = serde_json::to_vec(self).map_err(|err| OTError::serde().context(err))?; | ||||||
|         }; |         Ok(bytes) | ||||||
|         let result = serde_json::to_string(&insert).unwrap(); |  | ||||||
|         assert_eq!(result, r#"{"op":"edit-body","path":[0,1],"delta":[],"inverted":[]}"#); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,26 +2,28 @@ use crate::core::document::path::Path; | |||||||
| use crate::core::{NodeAttributes, NodeData, NodeOperation, NodeTree}; | use crate::core::{NodeAttributes, NodeData, NodeOperation, NodeTree}; | ||||||
| use indextree::NodeId; | use indextree::NodeId; | ||||||
| 
 | 
 | ||||||
|  | use super::{NodeBodyChangeset, NodeOperationList}; | ||||||
|  | 
 | ||||||
| pub struct Transaction { | pub struct Transaction { | ||||||
|     pub operations: Vec<NodeOperation>, |     pub operations: NodeOperationList, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Transaction { | impl Transaction { | ||||||
|     fn new(operations: Vec<NodeOperation>) -> Transaction { |     fn new(operations: NodeOperationList) -> Transaction { | ||||||
|         Transaction { operations } |         Transaction { operations } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct TransactionBuilder<'a> { | pub struct TransactionBuilder<'a> { | ||||||
|     node_tree: &'a NodeTree, |     node_tree: &'a NodeTree, | ||||||
|     operations: Vec<NodeOperation>, |     operations: NodeOperationList, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<'a> TransactionBuilder<'a> { | impl<'a> TransactionBuilder<'a> { | ||||||
|     pub fn new(node_tree: &'a NodeTree) -> TransactionBuilder { |     pub fn new(node_tree: &'a NodeTree) -> TransactionBuilder { | ||||||
|         TransactionBuilder { |         TransactionBuilder { | ||||||
|             node_tree, |             node_tree, | ||||||
|             operations: Vec::new(), |             operations: NodeOperationList::default(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -80,23 +82,39 @@ impl<'a> TransactionBuilder<'a> { | |||||||
|         self.insert_nodes_at_path(path, vec![node]) |         self.insert_nodes_at_path(path, vec![node]) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn update_attributes_at_path(self, path: &Path, attributes: NodeAttributes) -> Self { |     pub fn update_attributes_at_path(mut self, path: &Path, attributes: NodeAttributes) -> Self { | ||||||
|         let mut old_attributes = NodeAttributes::new(); |         match self.node_tree.get_node_at_path(path) { | ||||||
|         let node = self.node_tree.node_at_path(path).unwrap(); |             Some(node) => { | ||||||
|         let node_data = self.node_tree.get_node(node).unwrap(); |                 let mut old_attributes = NodeAttributes::new(); | ||||||
|  |                 for key in attributes.keys() { | ||||||
|  |                     let old_attrs = &node.attributes; | ||||||
|  |                     if let Some(value) = old_attrs.get(key.as_str()) { | ||||||
|  |                         old_attributes.insert(key.clone(), value.clone()); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|         for key in attributes.keys() { |                 self.operations.push(NodeOperation::UpdateAttributes { | ||||||
|             let old_attrs = &node_data.attributes; |                     path: path.clone(), | ||||||
|             if let Some(value) = old_attrs.get(key.as_str()) { |                     attributes, | ||||||
|                 old_attributes.insert(key.clone(), value.clone()); |                     old_attributes, | ||||||
|  |                 }); | ||||||
|             } |             } | ||||||
|  |             None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path), | ||||||
|         } |         } | ||||||
|  |         self | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|         self.push(NodeOperation::UpdateAttributes { |     pub fn update_body_at_path(mut self, path: &Path, changeset: NodeBodyChangeset) -> Self { | ||||||
|             path: path.clone(), |         match self.node_tree.node_id_at_path(path) { | ||||||
|             attributes, |             Some(_) => { | ||||||
|             old_attributes, |                 self.operations.push(NodeOperation::UpdateBody { | ||||||
|         }) |                     path: path.clone(), | ||||||
|  |                     changeset, | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |             None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path), | ||||||
|  |         } | ||||||
|  |         self | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn delete_node_at_path(self, path: &Path) -> Self { |     pub fn delete_node_at_path(self, path: &Path) -> Self { | ||||||
| @ -104,7 +122,7 @@ impl<'a> TransactionBuilder<'a> { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn delete_nodes_at_path(mut self, path: &Path, length: usize) -> Self { |     pub fn delete_nodes_at_path(mut self, path: &Path, length: usize) -> Self { | ||||||
|         let mut node = self.node_tree.node_at_path(path).unwrap(); |         let mut node = self.node_tree.node_id_at_path(path).unwrap(); | ||||||
|         let mut deleted_nodes = vec![]; |         let mut deleted_nodes = vec![]; | ||||||
|         for _ in 0..length { |         for _ in 0..length { | ||||||
|             deleted_nodes.push(self.get_deleted_nodes(node)); |             deleted_nodes.push(self.get_deleted_nodes(node)); | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ impl OTError { | |||||||
|     static_ot_error!(duplicate_revision, OTErrorCode::DuplicatedRevision); |     static_ot_error!(duplicate_revision, OTErrorCode::DuplicatedRevision); | ||||||
|     static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict); |     static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict); | ||||||
|     static_ot_error!(internal, OTErrorCode::Internal); |     static_ot_error!(internal, OTErrorCode::Internal); | ||||||
|  |     static_ot_error!(serde, OTErrorCode::SerdeError); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl fmt::Display for OTError { | impl fmt::Display for OTError { | ||||||
|  | |||||||
| @ -1,2 +1,3 @@ | |||||||
|  | mod operation_test; | ||||||
| mod script; | mod script; | ||||||
| mod test; | mod tree_test; | ||||||
|  | |||||||
							
								
								
									
										61
									
								
								shared-lib/lib-ot/tests/node/operation_test.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								shared-lib/lib-ot/tests/node/operation_test.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | use lib_ot::core::{ | ||||||
|  |     NodeAttributeBuilder, NodeBodyChangeset, NodeBuilder, NodeData, NodeOperation, Path, TextDeltaBuilder, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn operation_insert_node_serde_test() { | ||||||
|  |     let insert = NodeOperation::Insert { | ||||||
|  |         path: Path(vec![0, 1]), | ||||||
|  |         nodes: vec![NodeData::new("text".to_owned())], | ||||||
|  |     }; | ||||||
|  |     let result = serde_json::to_string(&insert).unwrap(); | ||||||
|  |     assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn operation_insert_node_with_children_serde_test() { | ||||||
|  |     let node = NodeBuilder::new("text") | ||||||
|  |         .add_node(NodeData::new("sub_text".to_owned())) | ||||||
|  |         .build(); | ||||||
|  | 
 | ||||||
|  |     let insert = NodeOperation::Insert { | ||||||
|  |         path: Path(vec![0, 1]), | ||||||
|  |         nodes: vec![node], | ||||||
|  |     }; | ||||||
|  |     assert_eq!( | ||||||
|  |         serde_json::to_string(&insert).unwrap(), | ||||||
|  |         r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"sub_text"}]}]}"# | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | #[test] | ||||||
|  | fn operation_update_node_attributes_serde_test() { | ||||||
|  |     let operation = NodeOperation::UpdateAttributes { | ||||||
|  |         path: Path(vec![0, 1]), | ||||||
|  |         attributes: NodeAttributeBuilder::new().insert("bold", true).build(), | ||||||
|  |         old_attributes: NodeAttributeBuilder::new().insert("bold", false).build(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let result = serde_json::to_string(&operation).unwrap(); | ||||||
|  | 
 | ||||||
|  |     assert_eq!( | ||||||
|  |         result, | ||||||
|  |         r#"{"op":"update","path":[0,1],"attributes":{"bold":"true"},"oldAttributes":{"bold":"false"}}"# | ||||||
|  |     ); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn operation_update_node_body_serde_test() { | ||||||
|  |     let delta = TextDeltaBuilder::new().insert("AppFlowy...").build(); | ||||||
|  |     let inverted = delta.invert_str(""); | ||||||
|  |     let changeset = NodeBodyChangeset::Delta { delta, inverted }; | ||||||
|  |     let insert = NodeOperation::UpdateBody { | ||||||
|  |         path: Path(vec![0, 1]), | ||||||
|  |         changeset, | ||||||
|  |     }; | ||||||
|  |     let result = serde_json::to_string(&insert).unwrap(); | ||||||
|  |     assert_eq!( | ||||||
|  |         result, | ||||||
|  |         r#"{"op":"edit-body","path":[0,1],"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}"# | ||||||
|  |     ); | ||||||
|  |     //
 | ||||||
|  | } | ||||||
| @ -1,11 +1,15 @@ | |||||||
| use lib_ot::core::{NodeAttributes, NodeData, NodeTree, Path, TransactionBuilder}; | use lib_ot::core::{ | ||||||
|  |     NodeAttributes, NodeBody, NodeBodyChangeset, NodeData, NodeTree, Path, TextDelta, TransactionBuilder, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| pub enum NodeScript { | pub enum NodeScript { | ||||||
|     InsertNode { path: Path, node: NodeData }, |     InsertNode { path: Path, node: NodeData }, | ||||||
|     InsertAttributes { path: Path, attributes: NodeAttributes }, |     UpdateAttributes { path: Path, attributes: NodeAttributes }, | ||||||
|  |     UpdateBody { path: Path, changeset: NodeBodyChangeset }, | ||||||
|     DeleteNode { path: Path }, |     DeleteNode { path: Path }, | ||||||
|     AssertNumberOfChildrenAtPath { path: Option<Path>, len: usize }, |     AssertNumberOfChildrenAtPath { path: Option<Path>, len: usize }, | ||||||
|     AssertNode { path: Path, expected: Option<NodeData> }, |     AssertNode { path: Path, expected: Option<NodeData> }, | ||||||
|  |     AssertNodeDelta { path: Path, expected: TextDelta }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct NodeTest { | pub struct NodeTest { | ||||||
| @ -34,12 +38,19 @@ impl NodeTest { | |||||||
| 
 | 
 | ||||||
|                 self.node_tree.apply(transaction).unwrap(); |                 self.node_tree.apply(transaction).unwrap(); | ||||||
|             } |             } | ||||||
|             NodeScript::InsertAttributes { path, attributes } => { |             NodeScript::UpdateAttributes { path, attributes } => { | ||||||
|                 let transaction = TransactionBuilder::new(&self.node_tree) |                 let transaction = TransactionBuilder::new(&self.node_tree) | ||||||
|                     .update_attributes_at_path(&path, attributes) |                     .update_attributes_at_path(&path, attributes) | ||||||
|                     .finalize(); |                     .finalize(); | ||||||
|                 self.node_tree.apply(transaction).unwrap(); |                 self.node_tree.apply(transaction).unwrap(); | ||||||
|             } |             } | ||||||
|  |             NodeScript::UpdateBody { path, changeset } => { | ||||||
|  |                 //
 | ||||||
|  |                 let transaction = TransactionBuilder::new(&self.node_tree) | ||||||
|  |                     .update_body_at_path(&path, changeset) | ||||||
|  |                     .finalize(); | ||||||
|  |                 self.node_tree.apply(transaction).unwrap(); | ||||||
|  |             } | ||||||
|             NodeScript::DeleteNode { path } => { |             NodeScript::DeleteNode { path } => { | ||||||
|                 let transaction = TransactionBuilder::new(&self.node_tree) |                 let transaction = TransactionBuilder::new(&self.node_tree) | ||||||
|                     .delete_node_at_path(&path) |                     .delete_node_at_path(&path) | ||||||
| @ -47,7 +58,7 @@ impl NodeTest { | |||||||
|                 self.node_tree.apply(transaction).unwrap(); |                 self.node_tree.apply(transaction).unwrap(); | ||||||
|             } |             } | ||||||
|             NodeScript::AssertNode { path, expected } => { |             NodeScript::AssertNode { path, expected } => { | ||||||
|                 let node_id = self.node_tree.node_at_path(path); |                 let node_id = self.node_tree.node_id_at_path(path); | ||||||
| 
 | 
 | ||||||
|                 match node_id { |                 match node_id { | ||||||
|                     None => assert!(node_id.is_none()), |                     None => assert!(node_id.is_none()), | ||||||
| @ -66,11 +77,19 @@ impl NodeTest { | |||||||
|                     assert_eq!(len, expected_len) |                     assert_eq!(len, expected_len) | ||||||
|                 } |                 } | ||||||
|                 Some(path) => { |                 Some(path) => { | ||||||
|                     let node_id = self.node_tree.node_at_path(path).unwrap(); |                     let node_id = self.node_tree.node_id_at_path(path).unwrap(); | ||||||
|                     let len = self.node_tree.number_of_children(Some(node_id)); |                     let len = self.node_tree.number_of_children(Some(node_id)); | ||||||
|                     assert_eq!(len, expected_len) |                     assert_eq!(len, expected_len) | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|  |             NodeScript::AssertNodeDelta { path, expected } => { | ||||||
|  |                 let node = self.node_tree.get_node_at_path(&path).unwrap(); | ||||||
|  |                 if let NodeBody::Delta(delta) = node.body.clone() { | ||||||
|  |                     debug_assert_eq!(delta, expected); | ||||||
|  |                 } else { | ||||||
|  |                     panic!("Node body type not match, expect Delta"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,9 @@ | |||||||
| use crate::node::script::NodeScript::*; | use crate::node::script::NodeScript::*; | ||||||
| use crate::node::script::NodeTest; | use crate::node::script::NodeTest; | ||||||
|  | use lib_ot::core::NodeBody; | ||||||
|  | use lib_ot::core::NodeBodyChangeset; | ||||||
|  | use lib_ot::core::OperationTransform; | ||||||
|  | use lib_ot::core::TextDeltaBuilder; | ||||||
| use lib_ot::core::{NodeBuilder, NodeData, Path}; | use lib_ot::core::{NodeBuilder, NodeData, Path}; | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| @ -147,7 +151,7 @@ fn node_insert_with_attributes_test() { | |||||||
|             path: path.clone(), |             path: path.clone(), | ||||||
|             node: inserted_node.clone(), |             node: inserted_node.clone(), | ||||||
|         }, |         }, | ||||||
|         InsertAttributes { |         UpdateAttributes { | ||||||
|             path: path.clone(), |             path: path.clone(), | ||||||
|             attributes: inserted_node.attributes.clone(), |             attributes: inserted_node.attributes.clone(), | ||||||
|         }, |         }, | ||||||
| @ -175,3 +179,32 @@ fn node_delete_test() { | |||||||
|     ]; |     ]; | ||||||
|     test.run_scripts(scripts); |     test.run_scripts(scripts); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn node_update_body_test() { | ||||||
|  |     let mut test = NodeTest::new(); | ||||||
|  |     let path: Path = 0.into(); | ||||||
|  | 
 | ||||||
|  |     let s = "Hello".to_owned(); | ||||||
|  |     let init_delta = TextDeltaBuilder::new().insert(&s).build(); | ||||||
|  |     let delta = TextDeltaBuilder::new().retain(s.len()).insert(" AppFlowy").build(); | ||||||
|  |     let inverted = delta.invert(&init_delta); | ||||||
|  |     let expected = init_delta.compose(&delta).unwrap(); | ||||||
|  | 
 | ||||||
|  |     let node = NodeBuilder::new("text") | ||||||
|  |         .insert_body(NodeBody::Delta(init_delta)) | ||||||
|  |         .build(); | ||||||
|  | 
 | ||||||
|  |     let scripts = vec![ | ||||||
|  |         InsertNode { | ||||||
|  |             path: path.clone(), | ||||||
|  |             node: node.clone(), | ||||||
|  |         }, | ||||||
|  |         UpdateBody { | ||||||
|  |             path: path.clone(), | ||||||
|  |             changeset: NodeBodyChangeset::Delta { delta, inverted }, | ||||||
|  |         }, | ||||||
|  |         AssertNodeDelta { path, expected }, | ||||||
|  |     ]; | ||||||
|  |     test.run_scripts(scripts); | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 nathan
						nathan