Selaa lähdekoodia

chore: add update node body test

nathan 2 vuotta sitten
vanhempi
commit
1d7d4092a5

+ 29 - 3
shared-lib/lib-ot/src/core/document/attributes.rs

@@ -39,8 +39,8 @@ impl NodeAttributes {
         self.0.is_empty()
     }
 
-    pub fn delete(&mut self, key: &AttributeKey) {
-        self.insert(key.clone(), AttributeValue(None));
+    pub fn delete<K: ToString>(&mut self, key: K) {
+        self.insert(key.to_string(), AttributeValue(None));
     }
 }
 
@@ -139,8 +139,34 @@ impl std::convert::From<bool> for AttributeValue {
     fn from(val: bool) -> Self {
         let val = match val {
             true => Some("true".to_owned()),
-            false => None,
+            false => Some("false".to_owned()),
         };
         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
+    }
+}

+ 13 - 6
shared-lib/lib-ot/src/core/document/node_tree.rs

@@ -26,6 +26,13 @@ impl NodeTree {
         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
     ///
@@ -41,7 +48,7 @@ impl NodeTree {
     /// let node_path = node_tree.path_of_node(node_id);
     /// 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();
         if path.is_empty() {
             return Some(self.root);
@@ -54,7 +61,7 @@ impl NodeTree {
         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 current_node = node_id;
         // 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> {
-        for op in &transaction.operations {
+        for op in transaction.operations.iter() {
             self.apply_op(op)?;
         }
         Ok(())
@@ -164,7 +171,7 @@ impl NodeTree {
         let (parent_path, last_path) = path.split_at(path.0.len() - 1);
         let last_index = *last_path.first().unwrap();
         let parent_node = self
-            .node_at_path(parent_path)
+            .node_id_at_path(parent_path)
             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
 
         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> {
         let mut update_node = self
-            .node_at_path(path)
+            .node_id_at_path(path)
             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
 
         for _ in 0..nodes.len() {
@@ -255,7 +262,7 @@ impl NodeTree {
         F: Fn(&mut Node) -> Result<(), OTError>,
     {
         let node_id = self
-            .node_at_path(path)
+            .node_id_at_path(path)
             .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?;
         match self.arena.get_mut(node_id) {
             None => tracing::warn!("The path: {:?} does not contain any nodes", path),

+ 29 - 49
shared-lib/lib-ot/src/core/document/operation.rs

@@ -1,8 +1,10 @@
 use crate::core::document::operation_serde::*;
 use crate::core::document::path::Path;
 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")]
 pub enum NodeOperation {
     #[serde(rename = "insert")]
@@ -99,59 +101,37 @@ impl NodeOperation {
     }
 }
 
-#[cfg(test)]
-mod tests {
-    use crate::core::{NodeAttributes, NodeBodyChangeset, NodeBuilder, NodeData, NodeOperation, Path, TextDelta};
-    #[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"}]}"#);
+#[derive(Serialize, Deserialize, Default)]
+pub struct NodeOperationList {
+    operations: Vec<NodeOperation>,
+}
+
+impl std::ops::Deref for NodeOperationList {
+    type Target = Vec<NodeOperation>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.operations
     }
+}
+
+impl std::ops::DerefMut for NodeOperationList {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.operations
+    }
+}
 
-    #[test]
-    fn test_serialize_insert_sub_trees() {
-        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"}]}]}"#
-        );
+impl NodeOperationList {
+    pub fn new(operations: Vec<NodeOperation>) -> Self {
+        Self { operations }
     }
 
-    #[test]
-    fn test_serialize_update_operation() {
-        let insert = NodeOperation::UpdateAttributes {
-            path: Path(vec![0, 1]),
-            attributes: NodeAttributes::new(),
-            old_attributes: NodeAttributes::new(),
-        };
-        let result = serde_json::to_string(&insert).unwrap();
-        assert_eq!(
-            result,
-            r#"{"op":"update","path":[0,1],"attributes":{},"oldAttributes":{}}"#
-        );
+    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, OTError> {
+        let operation_list = serde_json::from_slice(&bytes).map_err(|err| OTError::serde().context(err))?;
+        Ok(operation_list)
     }
 
-    #[test]
-    fn test_serialize_text_edit_operation() {
-        let changeset = NodeBodyChangeset::Delta {
-            delta: TextDelta::new(),
-            inverted: TextDelta::new(),
-        };
-        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":[],"inverted":[]}"#);
+    pub fn to_bytes(&self) -> Result<Vec<u8>, OTError> {
+        let bytes = serde_json::to_vec(self).map_err(|err| OTError::serde().context(err))?;
+        Ok(bytes)
     }
 }

+ 37 - 19
shared-lib/lib-ot/src/core/document/transaction.rs

@@ -2,26 +2,28 @@ use crate::core::document::path::Path;
 use crate::core::{NodeAttributes, NodeData, NodeOperation, NodeTree};
 use indextree::NodeId;
 
+use super::{NodeBodyChangeset, NodeOperationList};
+
 pub struct Transaction {
-    pub operations: Vec<NodeOperation>,
+    pub operations: NodeOperationList,
 }
 
 impl Transaction {
-    fn new(operations: Vec<NodeOperation>) -> Transaction {
+    fn new(operations: NodeOperationList) -> Transaction {
         Transaction { operations }
     }
 }
 
 pub struct TransactionBuilder<'a> {
     node_tree: &'a NodeTree,
-    operations: Vec<NodeOperation>,
+    operations: NodeOperationList,
 }
 
 impl<'a> TransactionBuilder<'a> {
     pub fn new(node_tree: &'a NodeTree) -> TransactionBuilder {
         TransactionBuilder {
             node_tree,
-            operations: Vec::new(),
+            operations: NodeOperationList::default(),
         }
     }
 
@@ -80,23 +82,39 @@ impl<'a> TransactionBuilder<'a> {
         self.insert_nodes_at_path(path, vec![node])
     }
 
-    pub fn update_attributes_at_path(self, path: &Path, attributes: NodeAttributes) -> Self {
-        let mut old_attributes = NodeAttributes::new();
-        let node = self.node_tree.node_at_path(path).unwrap();
-        let node_data = self.node_tree.get_node(node).unwrap();
-
-        for key in attributes.keys() {
-            let old_attrs = &node_data.attributes;
-            if let Some(value) = old_attrs.get(key.as_str()) {
-                old_attributes.insert(key.clone(), value.clone());
+    pub fn update_attributes_at_path(mut self, path: &Path, attributes: NodeAttributes) -> Self {
+        match self.node_tree.get_node_at_path(path) {
+            Some(node) => {
+                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());
+                    }
+                }
+
+                self.operations.push(NodeOperation::UpdateAttributes {
+                    path: path.clone(),
+                    attributes,
+                    old_attributes,
+                });
             }
+            None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path),
         }
+        self
+    }
 
-        self.push(NodeOperation::UpdateAttributes {
-            path: path.clone(),
-            attributes,
-            old_attributes,
-        })
+    pub fn update_body_at_path(mut self, path: &Path, changeset: NodeBodyChangeset) -> Self {
+        match self.node_tree.node_id_at_path(path) {
+            Some(_) => {
+                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 {
@@ -104,7 +122,7 @@ impl<'a> TransactionBuilder<'a> {
     }
 
     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![];
         for _ in 0..length {
             deleted_nodes.push(self.get_deleted_nodes(node));

+ 1 - 0
shared-lib/lib-ot/src/errors.rs

@@ -37,6 +37,7 @@ impl OTError {
     static_ot_error!(duplicate_revision, OTErrorCode::DuplicatedRevision);
     static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict);
     static_ot_error!(internal, OTErrorCode::Internal);
+    static_ot_error!(serde, OTErrorCode::SerdeError);
 }
 
 impl fmt::Display for OTError {

+ 2 - 1
shared-lib/lib-ot/tests/node/mod.rs

@@ -1,2 +1,3 @@
+mod operation_test;
 mod script;
-mod test;
+mod tree_test;

+ 61 - 0
shared-lib/lib-ot/tests/node/operation_test.rs

@@ -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}]}"#
+    );
+    //
+}

+ 24 - 5
shared-lib/lib-ot/tests/node/script.rs

@@ -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 {
     InsertNode { path: Path, node: NodeData },
-    InsertAttributes { path: Path, attributes: NodeAttributes },
+    UpdateAttributes { path: Path, attributes: NodeAttributes },
+    UpdateBody { path: Path, changeset: NodeBodyChangeset },
     DeleteNode { path: Path },
     AssertNumberOfChildrenAtPath { path: Option<Path>, len: usize },
     AssertNode { path: Path, expected: Option<NodeData> },
+    AssertNodeDelta { path: Path, expected: TextDelta },
 }
 
 pub struct NodeTest {
@@ -34,12 +38,19 @@ impl NodeTest {
 
                 self.node_tree.apply(transaction).unwrap();
             }
-            NodeScript::InsertAttributes { path, attributes } => {
+            NodeScript::UpdateAttributes { path, attributes } => {
                 let transaction = TransactionBuilder::new(&self.node_tree)
                     .update_attributes_at_path(&path, attributes)
                     .finalize();
                 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 } => {
                 let transaction = TransactionBuilder::new(&self.node_tree)
                     .delete_node_at_path(&path)
@@ -47,7 +58,7 @@ impl NodeTest {
                 self.node_tree.apply(transaction).unwrap();
             }
             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 {
                     None => assert!(node_id.is_none()),
@@ -66,11 +77,19 @@ impl NodeTest {
                     assert_eq!(len, expected_len)
                 }
                 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));
                     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");
+                }
+            }
         }
     }
 }

+ 34 - 1
shared-lib/lib-ot/tests/node/test.rs → shared-lib/lib-ot/tests/node/tree_test.rs

@@ -1,5 +1,9 @@
 use crate::node::script::NodeScript::*;
 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};
 
 #[test]
@@ -147,7 +151,7 @@ fn node_insert_with_attributes_test() {
             path: path.clone(),
             node: inserted_node.clone(),
         },
-        InsertAttributes {
+        UpdateAttributes {
             path: path.clone(),
             attributes: inserted_node.attributes.clone(),
         },
@@ -175,3 +179,32 @@ fn node_delete_test() {
     ];
     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);
+}